jueves, 16 de julio de 2009

Usando plugins para modularizar aplicaciones

Después de una temporada de alejamiento "forzoso" del desarrollo con CakePHP he decidido afrontar un par de nuevos proyectos utilizando nuevos enfoques en mi metodología de trabajo.

El primero de ellos es la modularización del código en base a plugins. El otro es la introducción del unit-testing y del desarrollo dirigido por tests.

Hoy voy a escribir sobre los plugins, por qué me he decidido a replantear mis aplicaciones con ellos y qué detalles he descubierto que hay que tener en cuenta para hacerlos funcionar bien.

Modularizar con plugins

Una queja habitual de la estructura de CakePHP es que no se pueden organizar los modelos, controladores y vistas de forma significativa, pues todos los modelos tienen que ir juntos en su carpeta, al igual que todos los controladores, etc.

Los plugins nos permiten abordar ese problema ya que con ellos agrupamos los modelos, controladores y vistas (y sus extensiones) que guarden alguna relación. Cada plugin es como una mini-aplicación CakePHP y en su interior reproduce su estructura general.

Los plugins pueden ser bastante autocontenidos, de modo que es sencillo reutilizarlos en nuevos proyectos. Supongamos, por ejemplo, que hemos creado un sistema de gestión de usuarios y grupos con el que nos encontramos muy a gusto. Si lo tenemos como plugin, tan sólo debemos copiar la carpeta que lo contiene en la nueva aplicación para disponer de toda la funcionalidad.

En cambio, si no está en forma de plugin, es más fácil perder la pista de algún archivo a la hora de copiarlo, en especial si se manejan varios modelos, etc.

Mi objetivo al usar plugins

Pues mi objetivo es muy sencillo. Se trata de tener "piezas" de código relativamente independientes que puedan servir para montar una diversidad de aplicaciones. De este modo dispondré de una especie de "superframework" con módulos reutilizables. Cada aplicación o proyecto tendría partes propias, y partes comunes. De este modo, al avanzar en uno, estoy contribuyendo a otros. Y, por otro lado, me puedo concentrar en el núcleo de una aplicación específica.

Y en caso de trabajar en equipo, es fácil distribuir el trabajo.

Por otra parte, los plugins son también una buena manera de crear bibliotecas de behaviors, components o helpers, que puedan reutilizarse en varios proyectos. Lo bueno, es que puedes agruparlos por algún criterio útil. Por, ejemplo, una biblioteca de helpers relacionados con la interfaz de usuario. O un módulo con un behavior para añadir comentarios a cualquier otro objeto, que incluye la gestión de los mismos.

Creando un plugin

Lo mejor es utilizar cake bake para crear la estructura básica del plugin.

cake bake plugin nombre_del_plugin

Lo más importante a tener en cuenta es que los modelos y controladores dentro de un plugin no deberían descender de AppModel o de AppController, sino indirectamente, a través de AppPluginModel o AppPluginController. Cake Bake se ocupa de eso por nosotros.

También podemos generar modelos y controladores básicos mediante cake bake

cake bake plugin NombreDelPlugin model NombreDelModelo

Accediendo a las acciones de un plugin

Las URL que nos llevan a un plugin son las típicas de cake prefijadas con el nombre del plugin:

/plugin/controller/action/param

Como, por ejemplo:

/content/posts/edit/first_post
/admin/tickets/tickets/edit/123

Como puedes ver, también es posible usar el admin routing con plugins.

Por otra parte, es perfectamente posible llamar a las acciones de los plugins con RequestAction.

Creando URL

En el caso de que crees las URL para link o redirect con el formato array, debes incluir la clave 'plugin' con valor true.

array('admin' => true, 'plugin' => true, 'action' => 'index')

Asociaciones con modelos dentro de un plugin

Cuando necesitamos asociar un modelo con otro que está en un plugin, tenemos que indicárselo. Puede ser en la forma simple:

var $hasMany = array('Plugin.Modelo');

O si tienes que especificar parámetros de la asociación, incluyendo la clave

'className' = 'Plugin.Modelo',

Recuerda que ésta, y las demás indicaciones, se aplican incluso cuando los modelos asociados están en el mismo plugin.

Usando behaviors, components o helper de un plugin

Decláralos como siempre, añadiendo antes el nombre del plugin, como en este ejemplo:

var $actsAs = array('Comments.Commentable);

Usando elementos de un plugin

Entre los parámetros que pasamos al elemento, hay que indicar la clave 'plugin' y el nombre del plugin.

element('ejemplo', array('plugin' => 'el_plugin')); ?>

Importando modelos o controladores

Hay que indicar el nombre del plugin, para que importe el objeto correcto.

App::import('Model', 'Plugin.Model');
App::import('Controller', 'Plugin.Controller');

Testing con plugins

Algunas cosas que he observado que es necesario tener en cuenta son:

Los controladores que usamos para testar deben incluir el parámetro plugin (gracias a Mark Story)

class TestPostsController extends PostsController {
var $plugin = 'Content';
}

Y hay que tener en cuenta el apartado anterior para importar los objetos que estén en plugins.

La declaración de las fixtures, también lo tiene en cuenta:

var $fixtures = array('plugin.content.post);

El test suite de CakePHP trata cada plugin aisladamente, lo que resulta muy cómodo y casi te evita tener que hacer grupos de tests por funcionalidad u otro criterio.

Shells

Puedes empaquetar shells en plugins. Crea la ruta vendors/shell dentro de la carpeta del plugin que se trate y pon ahí los shells que desees. La utilidad de línea de comandos cake sabrá acceder a ellos.

Por supuesto, hay más información en el Cookbook.

5 comentarios:

walter dijo...

En los primeros párrafos comentas sobre el ordenamiento de modelos y controladores. Solo quería acotar, que es posible organizar los archivos de modelos y controladores dentro de carpetas.
Por Ejemplo:
models/users/user.php
models/users/group.php
models/users/rol.php
models/blogs/blog.php
models/blogs/entry.php
models/blogs/comment.php

lo mismo para controllers.

Muy bueno el artículo.
Saludos

Frankie dijo...

Buena observación, walter.

La ventaja de los plugin sobre esto, es que puedes agrupar todos los elementos relacionados con una función en un solo sitio,

jordicakephp dijo...

Buenos días a todos, from Barcelona. Muy bueno tu post Fran, como siempre ;-).

El caso es que voy a embarcarme por primera vez en la programación de un plugin para hacer un foro y tengo algunas dudas que comparto con vosotros :-D

¿Qué tal es esta idea? ¿Cómo implementáis vuestros foros en CakePHP? ¿Lo hacéis con soluciones prefabricadas, como SMF, o preferís hacerlo vosotros? ¿Si lo hacéis vosotros, qué metodología seguís? ¿Creéis que vale la pena hacerlo en un plugin?

Gracias de antemano y saludos. Ah, ¡y estamos en línea!

Didier Gurrola dijo...

Hola Fran que tal, te tengo una pregunta, espero de favor obtener tu respuesta.

mira la cuestion es la siguiente

tengo una estructura de trabajo en cake 2.0 similar a esta

- App
- Config
- Console
- Controller
- Lib
- Model
- View
- Etc.
- Cake
- Lib
- Plugins
- Calendario
-ControllerP
-controller1
-controller2
-controller3
-ModelP
-model1
-model1
-model1
-BehaviorP
-View
-view1
-view1
-view1
-EtcP
- Plugin2
- Plugin3
- PluginN
- Vendor
- etc....


como puedo hacer para acceder a los metodos de los controladores del plugin desde mi APP?


- Plugins
- Calendario
-ControllerP
-controller1
-controller2
-controller3


no se si se pueda hacer lo que quiero !!!


si pongo en el navegador

http://localhost/nombre_proyecto/plugin/controller/view

si la dezpliega, pero quisiera acceder a ella mediante alguna forma en la aplicacion principal



de antemano te agradezco eltiempo en que te tomas en leer esta cuestion. Gracias y que tengas un muy buen dia

Fran Iglesias dijo...

Hola Didier, si las cosas no han cambiado en Cake 2.0 respecto a 1.3, cuando quieres llamar a un método de un controlador "programáticamente" lo haces usando el método requestAction, que en principio puedes usar desde cualquier objeto (model, view o controller).

El primer parámetro es la URL, mejor expresada como array. Por ejemplo:

array('plugin' => 'MiPlugin', 'controller' => 'MiController', 'action' => 'MiMetodo')

En un segundo parámetro pasas un array con los parámetros bajo la clave 'pass' o 'named' según corresponda. Si pasas una clave 'return' te devuelve la vista, y, si no, te devuelve como una función normal (siempre que tengas un return lo-que-sea, claro).

No sé si me he explicado bien.