Carlos Robles frikiblog

17/02/2014
by carlosrobles
2 Comments

¿Eclipse o Android Studio?

Llevo años programando en eclipse, y defendiendolo frente a todos los demás. Al principio usaba NetBeans y me gustaba mucho, pero raiz de empezar con android, me pase incondicionalmente a eclipse. Desde Junio del año pasado que salió a la luz Android Studio, me he estado planteando si habría alguna razón para que todos esos modernos lo estuviesen usando, y si sería mas una cosa para los nuevos, o tambien lo estarían adoptando los que ya trabajan con eclipse. En cualquier caso no le encontraba mayores beneficios y sí algun inconveniente, como por ejemplo:

  • Cambiar es un royo
  • No es eclipse, y eclipse mola

Pero ahora, unos meses despues, debo reconocer que me he pasado al lado oscuro. Mi último proyecto está ya empezado en Android Studio, me estoy familiarizando con el, y todo apunta a que será el entorno que use desde ahora. Razones:

  • Es el futuro
  • En breve será lo que más y tal vez lo único que el equipo de Android recomiende.
  • Está basado en IntelliJ IDEA, uno de los IDE para java de primer nivel (entre los mejores, con Eclipse, netbeans, y Oracle JDeveloper), y es de JetBrains, cuyo PHPStorm es mi favorito sin ninguna duda para programar PHP (aunque para Zend utilizo el Zend Studio, que es eclipse, pero eso es otro tema)
  • Y lo que más, y lo que realmente me ha hecho dar el salto, es su nuevo forma de construir los apk. Más serio, mas versatil, mas potente, mas actual, y mas parecido a un proyecto en java. Y es que Android estudio utiliza Gradle. Las ventajas son claras:
    • facilita muchísimo reusar código y recursos
    • Facilita configurar, extender y personalizar el proceso.
    • Facilita la distribucion del código y por tanto trabajar en equipos
    • Gestiona las dependencias de una forma cómoda y potente (esta basado en Maven)
    • Nos permite compilar desde linea de comandos, lo cual nos puede salvar en una máquina en la que no tenemos todo el entorno montado
    • Y lo más importante: Hace increiblemente fácil crear distintas versiones de la aplicación, por ejemplo para hacer una distribución multi-apk, para distintos dispositivos, o una version gratis y otra de pago, o una version de prueba que carga distintos recursos, apunta a unos webservices distintos, usa estádisticas distintas, etc.

En definitiva, estamos ante una herramienta con un potencial mucho mayor especialmente de cara a entornos empresariales.

Actualización

Si has empezado tu proyecto en eclipse, y quieres pasarlo a Android Studio, es muy sencillo (podemos verlo en inglés aquí):

Exportar desde eclipse

  • Actualizada el plugin ADT a la ultima versión (necesitas al menos la 22). Las instrucciones aquí
  • En Eclipse, selecciona File > Export.
  • Abre el apartado Android, verás que aparece una nueva opción,  Generate Gradle build files. Seleccionala
  • Elige tu proyecto y pulsa Finish

Con esto ya está. como Android Studio trabaja con Gradle, al crear los archivos de gradle, ya tenemos todo lo necesario para importar el proyecto dentro de Android Studio, o incluso para construirlo fuera de cualquier entorno si hiciese falta.

Importarlo a Android Studio

  • Cierra todos los proyectos
  • Click en  Import Project.
  • Busca el proyecto en la ruta normal del workspace de eclipse
  • Dentro de la carpeta del proyecto, selecciona el archivo  build.gradle y pulsa OK.
  • En el siguiente dialogo, selecciona Use gradle wrapper y pulsa OK. (no te preocupes de las demás opciones.)

Despues, no está de más leerse los consejos básicos  que nos da la gente de android.

29/01/2014
by carlosrobles
2 Comments

Por qué no poner una splash screen a tu aplicación

Desde los inicios de los tiempos, me he preguntado sobre la necesidad o no de poner esas pantallas de presentación en las aplicaciones, donde sólo se pone el logo y poco más. Hay varias razones que me hacen pensar que es mejor no hacerlo:

  • Android, que tiene muy cuidado su manual de estilo y sus patrones de diseño de UI/UX, no tiene en todo su código ni una sola clase que esté destinada a este menester. ¿Lo han olvidado?. No creo.
  • Hacen perder tiempo, cuando abro una aplicación, es para usarla. No para que me recuerden cómo se llama o quien la hizo.
  • Las aplicaciones más populares, y las aplicaciones de desarrolladores destacados, no lo tienen. Por ejemplo foursquare, instagram o facebook no lo tienen. Si no te has identifciado pueden ponerte una pantalla de login con el logo, pero nunca una pantalla inutil con solo el logo; y si estás identificado entran directamente.

Lo puedo entender en juegos que tardan un siglo en cargar, pero no en una aplicacion normal.

En realidad, haciendo estadística, solo se ven estas pantallas en aplicaciones de desarrolladores menores, como un vestigio de algo del pasado de lo que no han escapado, porque no tienen demasiada obsesión por estar al día, o porque (como yo) no sabemos decir firmemente que no a los cliente que cree que algo es la ultima moda, pero que sabemos que ya no.

Me he encontrado este post que me da la razón, y añade unas cuantas razones interesantes: http://cyrilmottier.com/2012/05/03/splash-screens-are-evil-dont-use-them/

15/01/2014
by carlosrobles
1 Comment

Usar bases de datos MySQL desde Android

Si necesitas conectarte a un servidor MySQL desde tu aplicación Android, lo más apropiado por diferentes razones, utilizar un archivo PHP remoto que acceda a la base de datos y devuelva la información a la aplicación, por ejemplo en JSON.

Si no quieres hacer esto, o no tienes esta opción, puedes utilizar un driver JDBC, como harías con una aplicación java. Para ello sigue estos pasos:

  • Descargate el driver de aquí: http://dev.mysql.com/downloads/connector/j/3.0.html
  • Si en tu proyecto no tienes una carpeta “libs”, creala ahora.
  • Descomprime el archivo que te has descargado.
  • Mueve o copia el archivo mysql-connector-java-3.0.17-ga-bin.jar
  • Añadelo al build Path, para ello:
    • Clic derecho en el proyecto >  Build Path >Configure Build Path
    • Pestaña Libraries >  Add JAR
    • Busca la carpeta libs y selecciona el archivo Jar.

Una vez está el driver listo, pasamos a la parte de programacion. Como sabemos, Android obliga a hacer las operaciones de acceso a red en un hilo independiente, por tanto debemos elegir una de las formas habituales de crear un nuevo hilo. Por ejemplo, con un AsynkTask. En ese caso añadiriamos este código al método doInBackground.


private static final String url = "jdbc:mysql://{URL_SERVER}/{BASE_DATOS}";
private static final String user = "usuario";
private static final String password = "contraseña";

		int count=0;

		try {
			// The newInstance() call is a work around for some broken Java implementations
                        //con esto nos aseguramos de que se crean los recursos estaticos necesarios
                        Class.forName("com.mysql.jdbc.Driver").newInstance();
			Connection con = DriverManager.getConnection(url, user, password);
			Statement st = con.createStatement();
			ResultSet rs = st
					.executeQuery("QUERY A EJECUTAR");
			while (rs.next()) {

                                 //en cada iteracion tenemos un resultado concreto
                                 //si la query es un "select una_columna, otra_columna from ..." recuerda que rs tiene funciones tales como
				//rs.getString(0) //devuelve una_columna, haciendo cast a string
                                //rs.getDouble(1) //devuelve otra_columna, haciendo cast a Double
                                //etc, que reciben el indice de los valor, y nos devueven el valor haciendo el cast correspondiente.


				publishProgress(++count);
				// Terminar lo antes posible si se ha llamadao al cancel() del asynctask
				if (isCancelled())
					break;

			}

		}

		catch (Exception e) {
			e.printStackTrace();
		}

10/10/2013
by carlosrobles
0 comments

Emuladores de dispositivos móviles

Para probar como se ven las webs que estamos desarrollando, ademas de hacer pruebas reales en algun dispositivo físico, está bien tener algun emulador a mano.

Yo desde PC, como siempre he sido muy de fiarme de Opera, lo que suelo utilizar es Opera Mobile Emulator.  http://dev.opera.com/articles/view/opera-mobile-emulator/

Recomiendo echarle un vistazo, muy intuitivo.

Además, en su afán de sacar productos para satisfacer a los mas curiosos, despues del ya conocido Opera Next  (que ofrece los últimos ajustes de precisión del navegador para que podamos seguir su evolucion probar las funciones más recientes.) dan un paso más alla y nos vienen con Opera Developer que es una version para desarrolladores y creadores de extensiones que permite probar experimentos que puede que aparezcan en versiones futuras.  Una de las cosas que me encanta es poder sobreescribir caracteristicas del sistema. por ejemplo podemos cargar una página simulando que en lugar de un ordenador estamos en un movil concreto, con unas dimensiones de pantalla concretas. Para ello hacemos clic derecho en la pagina > inspeccionar elemento (se habre el dragonfly como de costumbre) y abajo a la derecha esta el icono de settings, que nos habre una ventana con varias opciones. Dentro de la pestaña overrides veremos un mundo de posibilidades. Podemos usar este navegador para simular casi cualquier cosa.

Nos queda un caso un poco pesado, que es iPhone, aunque hay muchos emuladores, y los de opera mayormente van bien, hay algun detalle concreto al renderizar, que no es fiel del todo.

Para iphone despues de probar muchos programas, han ganado la batalla algunos emuladores online. Pongo mis preferidos:

http://interactiveiphone.com

http://iphone4simulator.com

http://www.testiphone.com

Hay que tener en cuenta que estos sistemas suelen crear un iframe en el cual se carga realmente el contenido. por eso si hacemos cambios en el css, aunque actualicemos el emulador muchas veces los recursos no se actualizan y lo seguimos viendo igual. Solucionarlo es tan sencillo como hacer clic en el boton derecho, y pedirle a nuestro navegador que actualice el contenido del iframe, dependiendo del navegador sera algo como “reload frame”, o “frame > reload”

21/09/2013
by carlosrobles
0 comments

Form dentro de otro form

Hay veces en las que puedes necesitar tener un formulario dentro de otro, algo de este tipo

<form action="a">
    <input.../>
    <form action="b">
        <input.../>
        <input.../>
        <input.../>
    </form>
    <input.../>
</form>

por ejemplo porque el formulario interior es una imagen, que envía por ajax, mientras que el resto es un formulario normal.

Lo primero que hay que saber es que esto no se puede hacer. Anidar forms está especificamente prohibido, como podemos ver en la especicificacion XHTML oficial de W3C, Section B. “Element Prohibitions”, states that:

 “form must not contain other form elements.” 
http://www.w3.org/TR/xhtml1/#prohibitions

Dependiendo del navegador, el resultado será uno u otro. Lo mas probable es que nos descarte la apertura del form interno, y su etiqueta de cierre, nos este cerrando en realidad el form princial.

Lo primero que se nos puede ocurrir,  es sacar el formulario fuera, y simplemente posicionarlo donde queremos con css .
Esto en muchos casos será una solucion, aunque no ideal, al menos válida.

Pero nos encontraremos algun escenario en que necesitamos que los elementos del form interior esten realmente dentro del primer form,  por ejemplo porque tenemos un sistema de pestañas , en el que mediante JS creemos tabs a partir de las distintas divisiones. si nuestro formulario está fuera, por mucho que con CSS lo coloquemos dentro, estará fuera de los tabs y el resultado no será el esperado.

Se nos podria ocurrir otro rodeo, como por ejemplo anidarlo de forma que el segundo form quede fuera del primero, pero dentro de la divisiion adecuada, algo del tipo,  pero tampoco es correcto 

<div class="tabs">
<div class="tab1"><form action="b">
</form><form action="a"></form></div>
<div class="tab2"></div>
</div>

Con esto las tabs funcionarán correctamente, pero el formulario a, definido dentro de la division tab1, debería cerrarse tambien dentro de tab1. por tanto volvemos a un HTML no válido.

En fin, no tenemos más remedio que dejar el formulario interno totalmente fuera.

Esto nos deja unas cuantas posibilidades.

  1. Podemos  usar el atributo form de los elementos input, algo nuevo en HTML5, que no funcionará en todos los navegadores
    <form id="fileuploads" action="http://yo.ur/target/" method="POST"></form>
    <form id="mainform">
       <div id="first_tab">
       </div>
       <div id="second_tab">
       </div>
       <div id="fileuploads">
            <input type="text" name="desc" form="fileuploads">
            <input type="file" name="file" form="fileuploads">
            <input type="submit" value="Submit" form="fileuploads">
       </div>
    </form>
    
  2. Algo parecido, podemos dejar solo los campos, y en el momento del submit, desabilitamos el externo,  creamos un formulario en el aire que envuelva los campos internos, y lo enviamos con  $(form_id).submit o document.getElementById(form_id).submit
    pongo este ejemplo de código que he visto en http://stackoverflow.com/questions/3430214/form-inside-a-form-is-that-alright

     $('#div_form').wrap('<form id="form2" action="/la/acion/" method="post" target="_blank"></form>')
    

    con esto podremos trabajar bien con cualquier tipo de elemento, pero no es del todo limpio y puede que nos falle en algun naegador.

  3. Si solo tenemos campos de texto, podemos crear el formulario interno totalmente en el aire. Es decir, no existe su etiqueta form. Ponemos los campos dentro del externo, y calculamos el input del usuario en variables js que despues enviaremos tambien por javascript, por ajax o con un post. Podemos ver un ejemplo completo y muy bien explicado aqui:
    http://blog.avirtualhome.com/how-to-create-nested-forms/
    básicamente, el código seria algo como :

    <html>
    <head>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript"><!--
    jQuery(document).ready(function() {
    jQuery('#search').click( submit_search );
    jQuery('#nestedform).find('input').keydown(keypressed);
    }
    );
    function submit_search( event ) {
    var values = new Array;
    event.preventDefault;
    values[0] = jQuery('#item').attr('value');
    if (values[0]) {
    do_submit('#output',values);
    } else {
    alert ('No Item given');
    }
    return false;
    }
    
    function keypressed( event ) {
    var charcode = (event.which) ? event.which : window.event.keyCode ;
    if ( charcode == 13 ) {
    return submit_search( event );
    }
    return true;
    }
    function do_submit(submit_output, submit_values) {
    jQuery(submit_output).hide();
    jQuery.post(
    "search.php",
    { action: 'search_values[]': submit_values },
    function(data, textStatus) {
    jQuery(submit_output).html(data.substr(0,data.length));
    });
    jQuery(submit_output).show();
    }
    );
    }
    // --></script>
    </head>
    <body>
    <form method="post" action="post.php">
    <textarea rows="5" cols="20" wrap="physical" name="post">
    <input type="submit" value="Post">
    <div id="nestedform">
    Item: <input type="text" size="10" id="text" name="item">
    <input type="button" id="search" value="Search">
    <div id="output"></div>
    </div>
    </form>
    </body>
    </html>
    

    Esto nos dará problemas con inputs complejos, como por ejemplo los de type=file

  4. Y llegados aquí tenemos 3 opciones, pero ninguna acaba de ser completa. Después de este vistazo rápido, y de ver que no encontraba ninguna solución, me he visto en la obligación de desarrollar la primera solución que me vino a la mente, pero que me daba mucha pereza.  Es una solucion similar a la 3, pero distinta, y más completa: Se trata de crear el formulario fuera, es decir, hay un formulario fisicamente, y ademas duplicar los campos en el formulario de dentro, y hacer que estos actuen como proxy del formulario de fuera. Es decir, para los campos te texto capturamos el input y se lo asignamos al campo real de fuera, y para los demas, como file submit, button, etc, capuramos el clic, y lanzamos el evento clic del campo asociado.
    <script>
     $("#avatar_form").ajaxForm(options); //no explico mas sobre esto, seria una forma normal de capturar el formulario para enviarlo por ajax
    $('#nestedinput).keydown(keypressed);
    function pickFile(){
    
     $('#file_browse').click();
     }
     function sendFile(){
    
     $('#avatar_form').submit();
     return false;
     }
    function keypressed( event ) {
    $("#outter_input").attr("value",$("#nested_input".attr("value"));
    }
    
    </script>
    <form id="avatar_form" style="visibility: hidden; position: absolute;"  action="/perfil/jugador/addfoto" method="post"
    enctype="multipart/form-data">
    
    <input type="file" id="file_browse" name="foto">
    <input type="text"  id="outter_input" value="">
    </form>
    <form action="" method="POST" name="perfil\profesional" id="perfil\profesional">
    <div class="tabss" >
       <div class="tab1" >
    
    <div id="avatar"  >
    
    <button   id="boton_enviar" onclick="sendFile()"
    style="display: none">subir imagen</button>
    </div>
    
    <div class="file_browse_tip" onclick="sendFile()">Clic para cambiar imagen</div>
    <input type="text"  id="nested_input" value="">
    
    </div>
    
    <input type="text" name="text1" value="">
    <input type="text" name="text2" value="">
     </div> <div class="tab2" >
    <input type="text" name="text3" value="">
    <input type="text" name="text4" value="">
     </div>
     </div >
     <input name="submit" type="submit" id="submitbutton" value="Guardar">
    
     </div>
     </form>
    
    

    Y esto nos soluciona perfectamente todos los casos y de una forma muy cómoda para tratar con el resto de eventos asociados al formulario, como validacion, etc.

06/09/2013
by carlosrobles
0 comments

doctrine: crear asociaciones a través de la id, en lugar del objeto

Cuando tenemos una entidad asociada a otra: por ejemplo, tenemos estas entidades:

use Doctrine\ORM\Mapping as ORM;
/**
 *  Provincia
 *
 * @ORM\Table(name=" provincia")
 * @ORM\Entity
 */
class  Provincia
{
    /**
     * @var string
     *
     * @ORM\Column(name="nombre", type="string", length=45, nullable=true)
     */
    private $nombre;
//...
}
/**
 * Localidad
 *
 * @ORM\Table(name=" localidad")
 * @ORM\Entity
 */
class  Localidad
{   
    /**
     * @var \ Provincia
     *
     * @ORM\ManyToOne(targetEntity="Provincia")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="provincia_id", referencedColumnName="id")
     * })
     */
    private $provincia;
//..
}

por lo general, si tenemos una localidad, y queremos asociarla a una provincia, necesitariamos tener un objeto de la clase provincia, sacado del repositorio por ejemplo si corresponde a una id que nos llega de un formulario,

$em=$this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$provincia = $em->getRepository("Provincia")->findOneById($idprovincia);
$localidad->setProvincia($provincia);

Esto se puede volver muy tedioso. Por suerte, tenemos un pequeño atajo:

$localidad->setProvincia($em->getReference('Provincia',$idprovincia ));

que viene siendo lo mismo, pero nos ahorramos un par de pasos, que suele ser un alivio cuando hay mucho intercambio con la base de datos.

05/09/2013
by carlosrobles
0 comments

Warning: strpos(): Empty delimiter in \doctrine\common\lib\Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain.php on line 160

Me vengo encontrando este error en Zend Framework 2 con doctrine, cuando tengo una clase que hereda de otra que no representa una entidad de la base de datos.

Despues de un rato de volverme loco a ver donde se originaba, veo que el error es muy obvio. Lo cuento.

Vemos que en el archivo mencionado en el titulo, tenemos esta función

  public function isTransient($className)
    {
        /* @var $driver MappingDriver */
        foreach ($this->drivers AS $namespace => $driver) {

            if (strpos($className, $namespace) === 0) {
                return $driver->isTransient($className);
            }
        }
        if ($this->defaultDriver !== null) {
            return $this->defaultDriver->isTransient($className);
        }
        return true;
    }

el error lo está dando en

strpos($className, $namespace)

Que al parecer, esta recibiendo un namespace vacion. vemos que estamos recorriendo los drivers, que son un array asociativo, donde la clave es el namespace y el valor es el driver asociado.

Los drivers, habitualmente se configuran en alguno de los archivos normales de configuracion de la aplicacion y del módulo, concretamente de esta forma

'doctrine' => array (
				'driver' => array (
						'mi_driver' => array (
								'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
								'cache' => 'array',
								'paths' => array (
										__DIR__ . '/../src/DBAL/Entity' 
								) 
						),
						
 
						'orm_default' => array (
								'drivers' => array (
										"NAMESPACE"=>"mi_driver", "NAMESPACE2"=>"mi_driver",

								) 
						) 
				) 
		) 
);

basicamente estamos creando configuraciones de drivers en [doctrine][driver], y despues en [orm_default][drivers] asociamos los distintos namespaces a alguno de los drivers configurados.

Pues bien, resulta qe en mi sección de drivers me encuentro:

	'orm_default' => array (
								'drivers' => array (
										'DBAL\Entity' => 'futdin_annotation_driver',
										'' => 'futdin_annotation_driver',
										'FooUser\Entity' => 'zfcuser_entity' 
								) 

y vemos que el error se ha introducido a mano, porque uno de los namespaces está vacio.
Recuerdo que cuando empecé esté proyecto, y había leido en algun lugar que se puede configurar un namespace vacio que representará el driver por defecto para cualquier namespace no explicitamente configurado, pero sencillamente esto no es verdad!.
Por tanto, tenemos que eliminar esa linea, y todo vuelve a funcionar normalmente.

23/08/2013
by carlosrobles
0 comments

Doctrine, entities con anotaciones, valores por defecto para las columnas.

Al menos de momento no hay una forma de indicar a Doctrine en las anotaciones el valor por defecto de una columna, pero hay una forma aún más coherente: definirlo directamente en la clase., como valor inicial de la variable en cuestión.

 /**
* User
* @ORM\Table(name="user")
* @ORM\Entity
*/

class  User  {

/**
* @var string
* @ORM\Column(name="nombre", type="string", length=45, nullable=true)
*/
private $nombre="NOMBRE POR DEFECTO";
...
 

15/08/2013
by carlosrobles
0 comments

Acceder a un View Helper desde el Action Controller

Los View Helpers son una especie de plugins que proporciona funcionalidad que podemos usar cuando estamos creando una vista en Zend Framework 2. Se explica con detalle aquí

Estaremos acostumbrados a usar unos cuantos, como por ejemplo docType, headLink, o los distintos Form View Helpers.

Por lo general se usan en la vista y solo en la vista, pero hay ocasiones en las que tenemos que introducir demasiada lógica, y preferimos sacar algunas operaciones al controlador. Por suerte desde el controlador ya podemos acceder a los view helpers que utilizará la vista.

He buscado un montón por internet, y como no es algo muy habitual, nno hay demasiada información. Despues de muchas pruebas, encuentro que la forma de hacerlo es:


public function indexAction()
 {
 $sm = $this->getEvent()->getApplication()->getServiceManager();
 $helper = $sm->get('viewhelpermanager')->get('NOMBRE_DEL_HELPER');

...

por ejemplo, si queremos acceder al view helper FormLabel, para por ejemplo cambiar el text domain del translator que se encarga de traducir las etiquetas de los formularios que se rendericen, el código seria asi:


public function indexAction()
{
$sm = $this->getEvent()->getApplication()->getServiceManager();
$helper = $sm->get('viewhelpermanager')->get('FormLabel');
$helper->setTranslatorTextDomain('MI_TEXT_DOMAIN');

...

Si esto es una buena forma de trabajar, se escapa del alcance de este post. Yo personalmente siempre soy partidario de sacar absolutamente toda la lógica fuera de la vista.

14/08/2013
by carlosrobles
0 comments

Controller Plugins, métodos mágicos, y funciones donde no las hay

Trabajando en ZF2 estamos acostumbrados a oir hablar de plugins .
Los plugins son clases que encierran funcionalidad que queremos añadir a nuestro controlador, y permiten hacerlo de una forma escalable y modular. El concepto es muy bueno.
ZF2 cuenta con una serie de plugins predeterminados, a los que tenemos accesos siempre que extendamos AbstractActionController o AbstractRestfulController, o implementando el método setPluginManager en el controlador.
Además, podemos registrar nuestros propios plugins a traves del plugin manager. Esto ya se aleja del proposito de este post.

Hay distintas formas de acceder a los plugins. Si el controlador implementa setPluginManager(), getPluginManager() y el método plugin(), podemos conseguir cualquier plugin pasando su nombre corto a la funcion plugin() del controlador. Esta para mi es la forma más natural, y diría que la más correcta.

$plugin = $this->plugin('url');

Así, en $plugin tenemos el objeto que nos interesa, con todas las funciones y propiedades que necesitamos.

Pero la gente de Zend, que le gusta complicar las cosas para ponernoslas facil, ha creado una nueva capa por conveniencia, que nos permite acceder a las funciones del plugin sin recuperar el objeto. Una locura muy útil pero menos intuitiva.

Voy a explicar un poco esto, especialmente porque a veces puede resultar complicado seguir el código de las cosas que estamos haciendo si no tenemos claro este funcionamiento.

Para los plugins ZF2 utiliza el Overloading de PHP, que es una novedad desde la versión 5, que no es lo mismo que el overloading en otros lenguajes. En Php, Overloading permite crear dinamicamete propiedades y métodos en objetos. ¿Qué significa esto? Pues que por ejemplo podemos llamar desde el controlador a la funcion

$this->params($string)

que todos hemos usados, o siguiendo con el ejemplo anterior llamar a

 $plugin = $this->url();

cuando en realidad en ninguna parte del código de nuestro controlador o sus superclases están definidas esas funciones. Si tratamos de seguir el código para ver donde se declaran, nos podemos volver locos. Estos métodos se han creado dinamicamente.

¿Como se crean métodos dinamicamente?

Gracias a los métodos mágicos de php. En concreto, tanto AbstractActionController como AbstractRestfulController implementan el método

public function __call($name, $arguments)

Cuando una clase implementa este método, se produce la forma más mágica de overloading. Si en un objeto de esa clase, se llama a un método que no existe, la funcion __call() es llamada a cambio, recibiendo como parámetros el nombre de la funcion que fue llamada, y los parametros que se le enviaron.

Gracias a esto, cuando desde nuestro controlador llamamos a la función url, que no existe, en realidad lo que está pasando es que en el método __call, el controlador está buscando el objeto apropiado.

Pero aún hay otra vuelta te tuerca.
volvamos a nuestros ejemplos:

$this->params($string)
$plugin = $this->url();

La funcion url() creada dinamicamente, nos devuelve un objeto, pero como sabemos si hemos trabajado un poco con ZF2, vemos que lla funcion params, nos devuelve directamente el valor del parametro que le pedimos.
Esto es porque la funcion __call del controlador, hace una doble labor, primero obtiene el plugin que se llama como la funcion que se ha llamado, es decir, exactamente como si hubiesemos llamado a $plugin = $this->plugin($method), y despues, comprueba si el valor devuelto, se puede llamar directamente como si fuese una funcion, es decir, comprueba si is_callable. Si lo es, ejecuta la función, y devuelve su resultado. Si no lo es, lo devuelve como objeto.
Veamos la implementación de la funcion __call, que encontramos en Zend\Mvc\Controller\AbstractController

  /**
     * Method overloading: return/call plugins
     *
     * If the plugin is a functor, call it, passing the parameters provided.
     * Otherwise, return the plugin instance.
     *
     * @param  string $method
     * @param  array  $params
     * @return mixed
     */
 public function __call($method, $params)
    {
        $plugin = $this->plugin($method);
        if (is_callable($plugin)) {
            return call_user_func_array($plugin, $params);
        }

        return $plugin;
    }

Y para terminar…como puede ser que una función que devuelve objetos, nos esté devolviendo un callable?

Normalmente un callable es

  1. el nombre de una funcion,
  2. un objeto y un metodo,
  3. o el nombre de una clase y un metodo estático.Pues gracias a los métodos mágicos, tenemos una nueva forma de callable:
  4. Un objeto que puede ser llamado como una función. Es decir, como si hiciesmos $a=new A(); y luego llamasemos a $a(); De igual forma que hacemos con variables que contienen el nombre de una funcion, pero con la diferencia de que aqui $a no es un string, sino un objeto.

En concreto, esto lo conseguimos con el método magico __invoke, que cuando una clase lo implementa,  es llamado cuando un script intenta llamar a un objeto como si fuera una función, como hemos descrito arriba.

Por ejemplo, sabemos que el plugin params,  implementa la función  __invoke como podemos en ver en la API del plugin: http://framework.zend.com/apidoc/2.0/classes/Zend.Mvc.Controller.Plugin.Params.html

En concreto, está es la implementación, que podemos   encontrar en Zend\Mvc\Controller\Plugin\Params

/**
 * Grabs a param from route match by default.
 *
 * @param string $param
 * @param mixed $default
 * @return mixed
 */
 public function __invoke($param = null, $default = null)
 {
 if ($param === null) {
 return $this;
 }
 return $this->fromRoute($param, $default);
 }

En resumen

Cuando en nuestro controlador estamos llamando a $this->params() en realidad el método mágico __call, esta recibiendo la llamada, llamado a $this->plugin(“params”), y recibiendo un objeto $p, de la clase Params, y a continuación llamando a $p(), que en realidad está haciendo que se ejecute el metodo __invoke() del objeto $p.

Magia en cadena.

Para ver los plugins disponibles por defecto y aprender alguna cosa concreta más, podeis ir a ladocumentacion de ZF sobre los plugins