Mostrando entradas con la etiqueta symfony. Mostrar todas las entradas
Mostrando entradas con la etiqueta symfony. Mostrar todas las entradas
lunes, 8 de septiembre de 2014
Symfony - Día 21: La Memoria Cache
Hoy, vamos a hablar de la memoria cache. El framework Symfony ha incorporado muchas estratégias sobre la memoria cache. Por ejemplo, los archivos de configuración YAML se convierten primero en PHP y luego pasan al cache sobre el sistema de ficheros. También hemos visto que los módulos de administración generados por el generador se guardan en cache para un mejor rendimiento.
Pero hoy, vamos a hablar de otro cache: el cache HTML. Para mejorar el rendimiento de tu sitio web, puedes poner en el cache todas las páginas HTML o sólo partes de ellas.
Creación de un nuevo Entorno
De forma predeterminada, la característica del cache de la plantilla de Symfony está habilitado en el fichero de configuración
settings.yml
para el entorno prod
, pero no para test
ni dev
:prod: .settings: cache: true dev: .settings: cache: false test: .settings: cache: false
Como tenemos que probar la función de cache antes de ir a producción, podemos activar la memoria cache para el entorno
dev
o crear un nuevo entorno. Recordemos que un entorno se define por su nombre (una cadena), un controlador frontal asociado, y opcionalmente, un conjunto de valores de configuración.
Para jugar con la memoria cache del sistema en Jobeet, vamos a crear un entorno
cache
, similar al entorno prod
, pero con la información disponible del log y debug del entorno dev
. Crea el controlador frontal asociado con el nuevo entorno cache
copiando el controlador frontal dev
enweb/frontend_dev.php
a web/frontend_cache.php
:// web/frontend_cache.php if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) { die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.'); } require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php'); $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'cache', true); sfContext::createInstance($configuration)->dispatch();
Eso es todo lo que hay para él. El nuevo entorno
cache
ahora es utilizable. La única diferencia es el segundo argumento del método getApplicationConfiguration()
que es el nombre del entorno, cache
.
Puede probar el entorno
cache
en tu navegador, llamando su controlador:http://www.jobeet.com.localhost/frontend_cache.php/
El controlador frontal tiene un script que comienza con un código que garantiza que el controlador frontal es sólo llamado desde una dirección IP local. Esta medida de seguridad es para proteger el controlador frontal de ser llamado en servidores de producción. Hablaremos sobre esto en más detalles en el tutorial de mañana.
Por ahora, el entorno
cache
hereda de la configuración por defecto. Edita el archivo de configuración settings.yml
para agregar la configuración específica de entorno cache
:# apps/frontend/config/settings.yml cache: .settings: error_reporting: <?php echo (E_ALL | E_STRICT)."\n" ?> web_debug: true cache: true etag: false
En estos ajustes, la función de cache de plantillas de Symfony se ha activado con el item
cache
y el web debug toolbar se ha activado con el web_debug
.
Como la configuración por defecto cachea todos los ajustes en la memoria caché, necesitas limpiarla antes de poder ver los cambios en tu navegador:
$ php symfony cc
Ahora, si actualizas tu navegador, el web debug toolbar deben estar presentes en la esquina superior derecha de la página, ya que es el caso para entornos
dev
.Configuración del Cache
El caché de las plantillas de Symfony se puede configurar con el archivo de configuración
cache.yml
. La configuración por defecto para la aplicación se encuentra enapps/frontend/config/cache.yml
:default: enabled: false with_layout: false lifetime: 86400
De forma predeterminada, ya que todas las páginas pueden contener información dinámica, la memoria caché es desactivada globalmente (
enabled: false
). No tenemos que cambiar esta configuración, porque permitiríamos pone en cache página por página.
El item
lifetime
define del lado del servidor el tiempo de vida de la memoria cache en segundos (86400
segundos equivalen a un día).
También puedes trabajar a la inversa: habilitar el cache globalmente y luego, deshabilitarlo en determinadas páginas que no se deben poner en cache. >Depende de que representa menos trabajo para tu aplicación.
Páginas en el Cache
Dado que la página principal Jobeet será probablemente la página más visitada del sitio web, en lugar de pedir los datos a la base de datos cada vez que un usuario accede a ella, ésta puede estar en el cache.
Crea un archivo
cache.yml
para el módulo `sfJobeetJob:# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml index: enabled: true with_layout: true
El
cache.yml
tiene las mismas propiedades que cualquier otro archivo de configuración de Symfony como view.yml
. Esto significa, por ejemplo, que puedes habilitar el cache para todas las acciones de un módulo mediante el uso de la clave especial all
.
Si actualizas tu navegador, verás que Symfony ha decorado la página con un cuadro que indica que el contenido se ha puesto en el cache:
El cuadro da una valiosa información acerca del cache para la depuración, como la vida útil del cache, y la duración hasta el momento de ella.
Si actualizas la página de nuevo, el color de la caja cambia de verde a amarillo, lo que indica que la página se ha recuperado del cache:
Observa también que no hay consultas a la base de datos en el segundo caso, como se muestra en la web debug toolbar.
Incluso si el idioma se puede cambiar por usuario, el cache aún funciona ya que el lenguaje esta incluído en la URL.
Cuando una página es cacheable, y si el cache aún no existe, Symfony almacena el objeto response en el cache al final de la petición. Para todos los demás de las peticiones, Symfony se enviará la respuesta del cache sin llamar al controlador:
Esto tiene un gran impacto en el rendimiento ya que puedes medirlo por tí mismo mediante el uso de herramientas como JMeter.
Una petición entrante con parámetros
GET
o enviada con método POST
, PUT
, oDELETE
nunca será puesta en el cache por Symfony, independientemente de la configuración.
La página de creación de puestos de trabajo también puede ser puesta en el cache:
# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml new: enabled: true index: enabled: true all: with_layout: true
Ya que las dos páginas se pueden guardar en el cache con el layout, hemos creado una scción
all
que define la configuración por defecto para todos las acciones de sfJobeetJob
.Limpiando el Cache
Si deseas borrar el cache, puedes usar la tarea
cache:clear
:$ php symfony cc
La tarea
cache:clear
limpia todo lo que Symfony guarda bajo el directorio principal cache/
. También tiene opciones para selectivamente limpiar algunas partes del cache. Para sólo limpiar el cache de las plantillas para el entorno cache
, usa las opciones --type
y --env
:$ php symfony cc --type=template --env=cache
En lugar de limpiar el cache cada vez que hagas un cambio, puendes también deshabilitar el cache agregando cualquier cadena de consulta a la URL, o usando el boton "Ignore cache" de la barra de herramientas de depuración web:
La Acción en el Cache
A veces, no se puede guardar en cache toda la página entera, pero la acción en si misma puede ser guardada en el cache. Dicho de otra manera, puedes guardar todo menos el layout.
Para la aplicación Jobeet, no podemos guardar en cache toda la página entera debido a la barra "history job".
Cambia la configuración para el cache del module
job
de acuerdo a:# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml new: enabled: true index: enabled: true all: with_layout: false
Al cambiar el
with_layout
a false
, has desactivado el layout en el cache.
Limpiar el cache:
$ php symfony cc
Actualiza tu navegador para ver la diferencia:
Incluso si el flujo de la petición es bastante similar en el diagrama simplificado, guardar cache sin el layout es mucho más intensivo el uso de recursos.
Partial y Componente en el Cache
Para sitios web muy dinámicos, a veces es incluso imposible guardar en cache toda la plantilla de la acción. Para esos casos, necesitas una manera de configurar el cache a nivel fino. Afortunadamente, los partials y componentes También se puede poner en el cache.
Vamos a guardar en el cache el componente
language
creando un archivo cache.yml
para el módulo sfJobeetLanguage
:# plugins/sfJobeetPlugin/modules/sfJobeetLanguage/config/cache.yml _language: enabled: true
La configuración del cache para un partial o un componente es tan simple como añadir un ítem con su nombre. Con la opción
with_layout
no se tiene en cuenta para este tipo de cache ya que no tiene ningún sentido:
Contextual o no?
El mismo componente o partial puede ser usado en diferentes plantillas. El partial job
_list.php
por ejemplo, se utiliza en los módulos job
y category
. Ya que la visualización es siempre la misma, el partial no depende del contexto en el que se utiliza y el cache es el mismo para todas las plantillas (el cache es obviamente todavía diferente para un conjunto diferente de parámetros).
Pero a veces, un partial o un componente tiene una salida diferente, sobre la base de la acción en la que se incluye (piensa en una barra lateral de un blog, por ejemplo, la cuál es ligeramente diferente para la página principal del blog y la página del artículo). En estos casos el partial o componente es contextual, y el cache debe estar configurado en consecuencia mediante el establecimiento de la opción
contextual
a true
:_sidebar: enabled: true contextual: true
Formularios en el Cache
El almacenamiento de la página de creación de empleo en el cache es problemática ya que contiene un formulario. Para comprender mejor el problema, ve a la página "Post a Job" en tu navegador. Entonces, limpia tu cookie de sesión, y trata de enviar un puesto de trabajo. Debes ver un mensaje de error con una alerta de "CSRF attack":
¿Por qué? Como se ha configurado un CSRF secreto cuando se creó el frontend, Symfony incorpora un token CSRF en todos sus formularios. Para protegerte contra los ataques CSRF, este token es único para un determinado usuario, así como para un determinado formulario.
La primera vez que la página es mostrada, el HTML generado se almacena en el cache con el token del usuario actual. Si otro usuario viene después, la página desde el cache se mostrará con el token CSRF del primer usuario. Cuándo se envía el formulario, los tokens no coinciden, y se lanza un mensaje de error.
¿Cómo podemos solucionar el problema, ya que parece legítimo guardar el formulario en el cache? El formulario de creación job no depende del usuario, y eso no cambia nada para el usuario actual. En tal caso, ninguna protección CSRF se necesita, y podemos quitar el token CSRF:
// plugins/sfJobeetPlugin/lib/form/doctrine/PluginJobeetJobForm.class.php abstract PluginJobeetJobForm extends BaseJobeetJobForm { public function configure() { $this->disableLocalCSRFProtection(); } }
Después de hacer este cambio, limpiar el cache y re-intentar el mismo escenario que el anterior para demostrar que funciona como se espera ahora.
La misma configuración debe aplicarse al formulario language ya que figura en el layout y se almacenará en el cache. Como el valor por defecto
sfLanguageForm
es usado, en lugar de crear una nueva clase, sólo para eliminar el token CSRF, vamos a hacerlo de la acción y componente del módulo sfJobeetLanguage
:// plugins/sfJobeetPlugin/modules/sfJobeetLanguage/actions/components.class.php class sfJobeetLanguageComponents extends sfComponents { public function executeLanguage(sfWebRequest $request) { $this->form = new sfFormLanguage($this->getUser(), array('languages' => array('en', 'fr'))); $this->form->disableLocalCSRFProtection(); } } // plugins/sfJobeetPlugin/modules/sfJobeetLanguage/actions/actions.class.php class sfJobeetLanguageActions extends sfActions { public function executeChangeLanguage(sfWebRequest $request) { $form = new sfFormLanguage($this->getUser(), array('languages' => array('en', 'fr'))); $form->disableLocalCSRFProtection(); // ... } }
The
disableLocalCSRFProtection()
method disables the CSRF token for this form.Removiendo el Cache
Cada vez que un usuario envia y activa un puesto de trabajo, la página principal debe ser refrescada para listar el nuevo puesto de trabajo.
Como no necesitamos que el puesto de trabajo aparezca en tiempo real en la página de inicio, la mejor estrategia es reducir el tiempo de vida del cache a algo aceptable:
# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml index: enabled: true lifetime: 600
En lugar de la configuración por defecto de un día, el cache para la página principal se eliminará automáticamente cada diez minutos.
Pero si deseas actualizar la página web tan pronto como un usuario activa un nuevo puesto de trabajo, modifiqua el método
executePublish()
del módulo sfJobeetJob
para añadir manualmente la limpieza del cache:// plugins/sfJobeetPlugin/modules/sfJobeetJob/actions/actions.class.php public function executePublish(sfWebRequest $request) { $request->checkCSRFProtection(); $job = $this->getRoute()->getObject(); $job->publish(); if ($cache = $this->getContext()->getViewCacheManager()) { $cache->remove('sfJobeetJob/index?sf_culture=*'); $cache->remove('sfJobeetCategory/show?id='.$job->getJobeetCategory()->getId()); } $this->getUser()->setFlash('notice', sprintf('Your job is now online for %s days.', sfConfig::get('app_active_days'))); $this->redirect($this->generateUrl('job_show_user', $job)); }
El cache es gestionado por la clase
sfViewCacheManager
. El método remove()
remueve el cache asociado con una URI interna. Para eliminar el cache para todos los posibles parámetros de una variable, utiliza el *
como valor. El sf_culture=*
que usamos en código anterior significa que Symfony eliminará del cache la página principal del Inglés y del Francés.
Ya que el administrador del cache es
null
cuando la memoria cache está desactivada, hemos envuelto la eliminación del cache en un bloque if
.Probando el Cache
Antes de comenzar, tenemos que cambiar la configuración para el entorno
test
para habilitar la capa cache:# apps/frontend/config/settings.yml test: .settings: error_reporting: <?php echo ((E_ALL | E_STRICT) ^ E_NOTICE)."\n" ?> cache: true web_debug: false etag: false
Vamos a probar la página para la creación de puestos de trabajo:
// test/functional/frontend/jobActionsTest.php $browser-> info(' 7 - Job creation page')-> get('/fr/')-> with('view_cache')->isCached(true, false)-> createJob(array('category_id' => Doctrine_Core::getTable('CategoryTranslation')->findOneBySlug('programming')->getId()), true)-> get('/fr/')-> with('view_cache')->isCached(true, false)-> with('response')->checkElement('.category_programming .more_jobs', '/23/') ;
El tester
view_cache
se utiliza para probar el cache. El método isCached()
toma dos Booleanos:- Si la página debe estar en cache o no
- Si el cache es con layout o no
Incluso con todas las herramientas proporcionadas por el framework de pruebas funcionales, a veces es más fácil de diagnosticar problemas en el navegador. Es muy fácil lograrlo. Crea un controlador frontal para el entorno
test
. Los logs guardados en log/frontend_test.log
también pueden ser muy útiles.Referencia: http://symfony.com/legacy/doc/jobeet/1_4/es/21?orm=Doctrine
martes, 2 de septiembre de 2014
Symfony - Día 20: Los Plugins
Ayer aprendímos a internacionalizar y localizar tus aplicaciones symfony. Una vez más, gracias al ICU standard y a un montón de helpers, Symfony lo hace muy fácil.
Hoy, vamos a hablar de plugins: lo que son, lo que puede tener un plugin, y para que pueden ser utilizados.
Los Plugins
Un Plugin Symfony
Un plugin ofrece una manera de empaquetar y distribuir un conjunto de archivos del proyecto. Al igual que un proyecto, un plugin puede tener clases, helpers, configuraciones, tareas, módulos, esquemas, e incluso recursos Web (CSS, JavaScript, etc.).
Plugins Privados
El primer uso de los plugins es facilitar el intercambio de código entre aplicaciones, o incluso entre distintos proyectos. Recuerda que las aplicaciones symfony sólo comparten el modelo. Los Plugins dan una manera de compartir más componentes entre aplicaciones.
Si necesitas volver a utilizar el mismo esquema para los diferentes proyectos o los mismos módulos, colócalos en un plugin. Como un plugin es solo un directorio, puedes moverlo con bastante facilidad mediante la creación de un repositorio SVN y el uso de
svn:externals
, o con sólo copiar los archivos de un proyecto a otro.
Nosotros llamamos a estos "plugins privados" porque su uso está restringido a una sola empresa o a un solo desarrollador. No están a disposición del público.
Puedes incluso crear un paquete de tus plugins privados, crear tu propio symfony plugin channel, e instalarlos via la tarea
plugin:install
.Plugins Públicos
Los Public Plugins están disponibles para la comunidad para descargar e instalar. Durante este tutorial, tenemos que usar un par de plugins públicos:
sfDoctrineGuardPlugin
ysfFormExtraPlugin
.
Son exactamente los mismos que los plugins privados. La única diferencia es que cualquiera puede instalar para sus proyectos. Aprenderás más adelante sobre la manera de publicar y alojar uno público en el sitio web de plugins de Symfony.
Una Forma Diferente de Organización del Código
Hay una manera más de pensar en plugins y usarlos. Olvídate de la reutilización y el intercambio. Los Plugins se puede utilizar como una manera diferente para organizar el código. En lugar de organizar los archivos por capas: todos los modelos en
lib/model/
, las plantillas entemplates/
, ...; los archivos están juntos por su característica: todos los archivos job juntos (el modelo, módulos y plantillas), todos los archivos CMS juntos, y así.Estructura de Archivos de un Plugin
Un plugin es sólo una estructura de directorios con los archivos organizados en una estructura previamente definida, según la naturaleza de los archivos. Hoy, pasaremos la mayor parte del código que hemos escrito para Jobeet en un
sfJobeetPlugin
. El layout básico que se utilizará es el siguiente:sfJobeetPlugin/
config/
sfJobeetPluginConfiguration.class.php // Plugin initialization
routing.yml // Routing
doctrine/
schema.yml // Database schema
lib/
Jobeet.class.php // Classes
helper/ // Helpers
filter/ // Filter classes
form/ // Form classes
model/ // Model classes
task/ // Tasks
modules/
job/ // Modules
actions/
config/
templates/
web/ // Assets like JS, CSS, and images
El Plugin Jobeet
Inicializar un plugin es tan sencillo como crear un nuevo directorio bajo el directorio
plugins/
. Para Jobeet, vamos a crear un directorio sfJobeetPlugin
:$ mkdir plugins/sfJobeetPlugin
Then, activate the
sfJobeetPlugin
in config/ProjectConfiguration.class.php
file.public function setup() { $this->enablePlugins(array( 'sfDoctrinePlugin', 'sfDoctrineGuardPlugin', 'sfFormExtraPlugin', 'sfJobeetPlugin' )); }
Todos los plugins deben terminar con
Plugin
. También es una buena costumbre usar el prefijo sf
, aunque no es obligatorio.El Modelo
Primero, mueve el archivo
config/doctrine/schema.yml
a plugins/sfJobeetPlugin/config/
:$ mkdir plugins/sfJobeetPlugin/config/
$ mkdir plugins/sfJobeetPlugin/config/doctrine
$ mv config/doctrine/schema.yml plugins/sfJobeetPlugin/config/doctrine/schema.yml
Todos los comandos son para ambientes Unix. Si usas Windows, puedes arrastrar y soltar los archivos en el Explorador de Windows. Y si utilizas Subversion, o cualquier otra herramienta para la gestión de tu código, usa las herramientas incorporadas que ofrecen (como
svn mv
para mover archivos).
Mueve el modelo, formulario, filtros a
plugins/sfJobeetPlugin/lib/
:$ mkdir plugins/sfJobeetPlugin/lib/
$ mv lib/model/ plugins/sfJobeetPlugin/lib/
$ mv lib/form/ plugins/sfJobeetPlugin/lib/
$ mv lib/filter/ plugins/sfJobeetPlugin/lib/
$ rm -rf plugins/sfJobeetPlugin/lib/model/doctrine/sfDoctrineGuardPlugin
$ rm -rf plugins/sfJobeetPlugin/lib/form/doctrine/sfDoctrineGuardPlugin
$ rm -rf plugins/sfJobeetPlugin/lib/filter/doctrine/sfDoctrineGuardPlugin
Remove the
plugins/sfJobeetPlugin/lib/form/BaseForm.class.php
file.$ rm plugins/sfJobeetPlugin/lib/form/BaseForm.class.php
Después de mover los modelos, formularios y filtros las clases deben ser renombradas, hacerlas abstractas y con el prefijo
Plugin
.
Solo usa el prefijo
Plugin
en las clases auto-generadas y no en todas las clases. Por ejemplo no uses el prefijo en las clases que haces a mano. Solo las auto-generadas requieren el prefijo.
Aquí está un ejemplo en el que movemos las clases
JobeetAffiliate
y JobeetAffiliateTable
.$ mv plugins/sfJobeetPlugin/lib/model/doctrine/JobeetAffiliate.class.php plugins/sfJobeetPlugin/lib/model/doctrine/PluginJobeetAffiliate.class.php
Y el código debe ser actualizado:
abstract class PluginJobeetAffiliate extends BaseJobeetAffiliate { public function save(Doctrine_Connection $conn = null) { if (!$this->getToken()) { $this->setToken(sha1($object->getEmail().rand(11111, 99999))); } parent::save($conn); } // ... }
Ahora vamos a pasar la clase
JobeetAffiliateTable
:$ mv plugins/sfJobeetPlugin/lib/model/doctrine/JobeetAffiliateTable.class.php plugins/sfJobeetPlugin/lib/model/doctrine/PluginJobeetAffiliateTable.class.php
La definición de la clase debe tener la siguiente apariencia:
abstract class PluginJobeetAffiliateTable extends Doctrine_Table { // ... }
Ahora hacer lo mismo para las clases forms y filter . Cambiar el nombre de ellos para incluir un prefijo con la palabra
Plugin
.
Asegúrate de quitar el directorio
base
en plugins/sfJobeetPlugin/lib/*/doctrine
para los directorios form
, filter
, y model
.
Ejemplo:
$ rm -rf plugins/sfJobeetPlugin/lib/form/doctrine/base
$ rm -rf plugins/sfJobeetPlugin/lib/filter/doctrine/base
$ rm -rf plugins/sfJobeetPlugin/lib/model/doctrine/base
Una vez que se ha mudado, cambiado de nombre y se eliminan algunas clases forms, filters y model corre las tarea para la re-construcción de todas las clases.
$ php symfony doctrine:build --all-classes
Ahora te darás cuenta que algunos de los nuevos directorios creados para mantener los modelos creados a partir de el esquema estan incluido en el
sfJobeetPlugin
enlib/model/doctrine/sfJobeetPlugin
.
Este directorio contiene los modelos de nivel superior y la base de clases generada por el esquema. Por ejemplo, el modelo
JobeetJob
tiene ahora esta estructura de clases.JobeetJob
(extiende dePluginJobeetJob
) enlib/model/doctrine/sfJobeetPlugin/JobeetJob.class.php
: Nivel superior donde toda la funcionalidad del modélo puede guardarse. Esto es donde puedas agregar y sobreescribir funcionalidades que ya vienen con los modelos del plugin.PluginJobeetJob
(extiende deBaseJobeetJob
) enplugins/sfJobeetPlugin/lib/model/doctrine/PluginJobeetJob.class.php
: Esta clase contiene todas las funciones específicas del plugin. Puedes sobreescribir la funcionalidad en esta clasa y la base modificando la claseJobeetJob
.BaseJobeetJob
(extiende desfDoctrineRecord
) enlib/model/doctrine/sfJobeetPlugin/base/BaseJobeetJob.class.php
: Clase base que es generada desde el archivo yaml del esquema cada vez que ejecutesdoctrine:build --model
.JobeetJobTable
(extiende dePluginJobeetJobTable
) enlib/model/doctrine/sfJobeetPlugin/JobeetJobTable.class.php
: Lo mismo que para la claseJobeetJob
excepto que esta e la instancia deDoctrine_Table
que obtendrás cuando llames aDoctrine_Core::getTable('JobeetJob')
.PluginJobeetJobTable
(extiende deDoctrine_Table
) enlib/model/doctrine/sfJobeetPlugin/JobeetJobTable.class.php
: Esta clase contiene todas las funciones específicas para la instancia deDoctrine_Table
que obtendrás cuando llames aDoctrine_Core::getTable('JobeetJob')
.
Con esta estructura que has generado tienes la posibilidad de personalizar los modelos de un plugin editando la clase de nivel superior
JobeetJob
. Puede personalizar el esquema y añadir columnas, añadir relaciones sobreescribiendo los métodos setTableDefinition()
y setUp()
.
Cuando mueves las clases classes, asegurate de modificar el método
configure()
al método setup()
y que llame a parent::setup()
. Debajo hay un ejemplo.abstract class PluginJobeetAffiliateForm extends BaseJobeetAffiliateForm { public function setup() { parent::setup(); } // ... }
Tenemos que asegurarnos de que nuestro plugin no tiene clases base para todas los Doctrine forms. Estos archivos son globales para un proyecto y se volverán a generar con la
doctrine:build --forms
y doctrine:build --filters
.
Quitar los archivos del plugin:
$ rm plugins/sfJobeetPlugin/lib/form/doctrine/BaseFormDoctrine.class.php
$ rm plugins/sfJobeetPlugin/lib/filter/doctrine/BaseFormFilterDoctrine.class.php
También puedes mover el
Jobeet.class.php
al plugin:$ mv lib/Jobeet.class.php plugins/sfJobeetPlugin/lib/
Como hemos pasado archivos, borrar la caché:
$ php symfony cc
Si usas un acelerador PHP como APC y cosas extrañas pasan en este momento, reinicia Apache.
Ahora que todos los archivos de los modelos se han movido al plugin, ejecutar las pruebas para comprobar que todo funciona bien todavía:
$ php symfony test:all
Los controladores y las Vistas
El siguiente paso lógico es mover los módulos al plugin:
$ mv apps/frontend/modules plugins/sfJobeetPlugin/
Para evitar colisiones con el nombre del módulo, siempre es una buena costumbre poner un prefijo a los nombres de los módulos con el nombre del plugin:
$ mkdir plugins/sfJobeetPlugin/modules/
$ mv apps/frontend/modules/affiliate plugins/sfJobeetPlugin/modules/sfJobeetAffiliate
$ mv apps/frontend/modules/api plugins/sfJobeetPlugin/modules/sfJobeetApi
$ mv apps/frontend/modules/category plugins/sfJobeetPlugin/modules/sfJobeetCategory
$ mv apps/frontend/modules/job plugins/sfJobeetPlugin/modules/sfJobeetJob
$ mv apps/frontend/modules/language plugins/sfJobeetPlugin/modules/sfJobeetLanguage
Por cada módulo, también tienes que cambiar el nombre de la clase en todo los archivos
actions.class.php
y components.class.php
(por ejemplo, la clase affiliateActions
necesiat ser renombrada a sfJobeetAffiliateActions
).
Las llamadas
include_partial()
y include_component()
también deben ser modificadas en las siguientes plantillas:sfJobeetAffiliate/templates/_form.php
(changeaffiliate
tosfJobeetAffiliate
)sfJobeetCategory/templates/showSuccess.atom.php
sfJobeetCategory/templates/showSuccess.php
sfJobeetJob/templates/indexSuccess.atom.php
sfJobeetJob/templates/indexSuccess.php
sfJobeetJob/templates/searchSuccess.php
sfJobeetJob/templates/showSuccess.php
apps/frontend/templates/layout.php
Actualiza las acciones
search
y delete
:// plugins/sfJobeetPlugin/modules/sfJobeetJob/actions/actions.class.php class sfJobeetJobActions extends sfActions { public function executeSearch(sfWebRequest $request) { $this->forwardUnless($query = $request->getParameter('query'), 'sfJobeetJob', 'index'); $this->jobs = Doctrine_Core::getTable('JobeetJob') ->getForLuceneQuery($query); if ($request->isXmlHttpRequest()) { if ('*' == $query || !$this->jobs) { return $this->renderText('No results.'); } return $this->renderPartial('sfJobeetJob/list', array('jobs' => $this->jobs)); } } public function executeDelete(sfWebRequest $request) { $request->checkCSRFProtection(); $jobeet_job = $this->getRoute()->getObject(); $jobeet_job->delete(); $this->redirect('sfJobeetJob/index'); } // ... }
Ahora, modifica el archivo
routing.yml
para tomar en cuenta estos cambios:# apps/frontend/config/routing.yml affiliate: class: sfDoctrineRouteCollection options: model: JobeetAffiliate actions: [new, create] object_actions: { wait: GET } prefix_path: /:sf_culture/affiliate module: sfJobeetAffiliate requirements: sf_culture: (?:fr|en) api_jobs: url: /api/:token/jobs.:sf_format class: sfDoctrineRoute param: { module: sfJobeetApi, action: list } options: { model: JobeetJob, type: list, method: getForToken } requirements: sf_format: (?:xml|json|yaml) category: url: /:sf_culture/category/:slug.:sf_format class: sfDoctrineRoute param: { module: sfJobeetCategory, action: show, sf_format: html } options: { model: JobeetCategory, type: object, method: doSelectForSlug } requirements: sf_format: (?:html|atom) sf_culture: (?:fr|en) job_search: url: /:sf_culture/search.:sf_format param: { module: sfJobeetJob, action: search, sf_format: html } requirements: sf_culture: (?:fr|en) job: class: sfDoctrineRouteCollection options: model: JobeetJob column: token object_actions: { publish: PUT, extend: PUT } prefix_path: /:sf_culture/job module: sfJobeetJob requirements: token: \w+ sf_culture: (?:fr|en) job_show_user: url: /:sf_culture/job/:company_slug/:location_slug/:id/:position_slug class: sfDoctrineRoute options: model: JobeetJob type: object method_for_query: retrieveActiveJob param: { module: sfJobeetJob, action: show } requirements: id: \d+ sf_method: GET sf_culture: (?:fr|en) change_language: url: /change_language param: { module: sfJobeetLanguage, action: changeLanguage } localized_homepage: url: /:sf_culture/ param: { module: sfJobeetJob, action: index } requirements: sf_culture: (?:fr|en) homepage: url: / param: { module: sfJobeetJob, action: index }
Si tratas de navegar por la página web Jobeet ahora, tendrás excepciones diciendo que los módulos no están habilitados. Como los plugins son compartidos entre todas las aplicaciones en un proyecto, necesitas específicamente habilitar el módulo que necesitas para una aplicación determinada en su archivo de configuración
settings.yml
:# apps/frontend/config/settings.yml all: .settings: enabled_modules: - default - sfJobeetAffiliate - sfJobeetApi - sfJobeetCategory - sfJobeetJob - sfJobeetLanguage
El último paso de la migración es arreglar las pruebas funcionales donde probamos por el nombre del módulo.
Las Tareas
Las Tareas puede ser trasladadas al plugin con bastante facilidad:
$ mv lib/task plugins/sfJobeetPlugin/lib/
Los Archivos i18n
Un plugin puede tener Archivos XLIFF:
$ mv apps/frontend/i18n plugins/sfJobeetPlugin/
Las Rutas
Un plugin también puede contener reglas de enrutamiento:
$ mv apps/frontend/config/routing.yml plugins/sfJobeetPlugin/config/
Los Recursos
Incluso si es un poco contra-intuitivo, un plugin también puede contener Recursos web como imágenes, hojas de estilo, y JavaScripts. Como no queremos distribuir el Jobeet plugin, en realidad no tiene sentido, pero es posible mediante la creación de un directorio
plugins/sfJobeetPlugin/web/
.
Un recurso del plugin debe ser accesible en el directorio
web/
del proyecto para ser visibles desde un navegador. La tarea plugin:publish-assets
se encarga de ello creando enlaces simbólicos en plataformas Unix y copiando los archivos en plataformas Windows:$ php symfony plugin:publish-assets
El Usuario
Moviendo los métodos de las clase
myUser
que tratan con los historiales es un poco más implicado. Se podría crear una clase JobeetUser
y hacer que myUser
herede de ella. Pero hay una forma mejor, sobre todo si varios plugins desean agregar nuevos métodos a la clase.
Los objetos del Nucléo de Symfony notifican eventos durante su ciclo de vida para que se puedan escuchar. En nuestro caso, tenemos que escuchar al evento
user.method_not_found
, que ocurre cuando un método indefinido se llama en el objeto sfUser
.
Cuando Symfony es inicializado, todos los plugins también se inicializan si tienen una clase de configuración plugin:
// plugins/sfJobeetPlugin/config/sfJobeetPluginConfiguration.class.php class sfJobeetPluginConfiguration extends sfPluginConfiguration { public function initialize() { $this->dispatcher->connect('user.method_not_found', array('JobeetUser', 'methodNotFound')); } }
Las notificaciones de los Eventos son gestionados por (
sfEventDispatcher
), el objeto event dispatcher. Registrar un listener es tan simple como llamar a connect()
. El método connect()
conecta un nombre del evento a un PHP ejecutable.
Un PHP callable es una variable PHP que puede ser utilizada por la función
call_user_func()
y devuelve true
cuando pasa a la función is_callable()
. Una cadena representa una función , y un array puede representar un método de objeto o un método de clase.
Con el código anterior en el lugar, el objeto
myUser
llamará al método estáticomethodNotFound()
de la clase JobeetUser
siempre que sea incapaz de encontrar un método. Es entonces cuando el método methodNotFound()
se procesa.
Remueve todos los métodos de la clase
myUser
y crear la clase JobeetUser
:// apps/frontend/lib/myUser.class.php class myUser extends sfBasicSecurityUser { } // plugins/sfJobeetPlugin/lib/JobeetUser.class.php class JobeetUser { static public function methodNotFound(sfEvent $event) { if (method_exists('JobeetUser', $event['method'])) { $event->setReturnValue(call_user_func_array( array('JobeetUser', $event['method']), array_merge(array($event->getSubject()), $event['arguments']) )); return true; } } static public function isFirstRequest(sfUser $user, $boolean = null) { if (is_null($boolean)) { return $user->getAttribute('first_request', true); } else { $user->setAttribute('first_request', $boolean); } } static public function addJobToHistory(sfUser $user, JobeetJob $job) { $ids = $user->getAttribute('job_history', array()); if (!in_array($job->getId(), $ids)) { array_unshift($ids, $job->getId()); $user->setAttribute('job_history', array_slice($ids, 0, 3)); } } static public function getJobHistory(sfUser $user) { $ids = $user->getAttribute('job_history', array()); if (!empty($ids)) { return Doctrine_Core::getTable('JobeetJob') ->createQuery('a') ->whereIn('a.id', $ids) ->execute(); } return array(); } static public function resetJobHistory(sfUser $user) { $user->getAttributeHolder()->remove('job_history'); } }
Cuando el dispatcher llama al método
methodNotFound()
, este pasa un objeto sfEvent
.
Si existe el método en la clase
JobeetUser
, este se llama y su valor devuelto es subsecuentemente devuelto al notificador. Si no, Symfony tratará con el próximo listener registrado o lanzará una Rxcepción.
El método
getSubject()
regresa el notificador del evento, que en este caso es el actual objetomyUser
.
Como siempre cuando creas nuevas clases, no olvides de limpiar el cache antes de navegar o ejecutar las pruebas:
$ php symfony cc
Arquitectura por defecto vs. Arquitectura de los Plugins
Utilizando la arquitectura de plugin te permite organizar el código de una manera diferente:
Uso de Plugins
Al iniciar la aplicación de una nueva funcionalidad, o si tratas de resolver un problema clásico de la Web, hay probabilidad de que alguien ya ha resuelto el mismo problema y tal vez empaqueto la solución como un plugin symfony. Para buscar un plugin público symfony, ve a la sección
plugin del sitio web Symfony.
plugin del sitio web Symfony.
Como un plugin esta auto-contenido en un directorio, hay varias manera de instalarlo:
- Usando la tarea
plugin:install
(esto solo funciona si el desarrollador ha creado un plugin package y lo ha subido al sitio web de Symfony) - Descarga el package/paquete y manualmente descomprimirlo bajo el directorio
plugins/
(también es necesario que el desarrollador haya subido un package) - La creación de un
svn:externals
enplugins/
para el plugin (esto solo funciona si el desarrollador ha alojado su plugin en Subversion)
Las dos últimas formas son fáciles, pero le falta cierta flexibilidad. La primera te permite instalar la versión más reciente de acuerdo con la versión del proyecto Symfony, fácil de actualizar a la última versión estable, y administrar fácilmente las dependencias entre plugins.
Contribuyendo con un Plugin
Empaquetar un Plugin
Para crear un plugin package, es necesario agregar algunos archivos obligatorios a la estructura de directorios del plugin. En primer lugar, crear un archivo
README
en la raíz de directorios del plugin y explicar cómo instalar el plugin, lo que proporciona, y lo que no. The README
file must be formatted with the Markdown format. Este archivo se utilizará en el sitio web Symfony como la principal pieza de la documentación. Puede probar la conversión de tu README a HTML utilizando el symfony plugin dingus.
Tareas para Crear Plugins
Si te encuentras con frecuencia en la creación de plugins privados y / o públicos, considera tomar las ventajas de algunas de las tasks en el sfTaskExtraPlugin. Este plugin, mantenida por el equipo de Symfony, incluye una serie de tareas que te ayudan a agilizar el ciclo de vida del plugin:
generate:plugin
plugin:package
También necesita crear un archivo
LICENSE
. La elección de una licencia no es una tarea fácil, pero la sección de symfony plugin sólo lista plugins que se liberan bajo una licencia similar a la de Symfony (MIT, BSD, LGPL, y PHP). El contenido de LICENSE
se mostrará bajo la pestaña licencia de la página de tu plugin.
El último paso es crear un archivo
package.xml
en la raíz del directorio del plugin. Estepackage.xml
sigue el PEAR package syntax.
La mejor manera de aprender la sintaxis
package.xml
es ciertamente hacer una copia del usado por un plugin existente.
El archivo
package.xml
se compone de varias partes, como puedes ver en este ejemplo:<!-- plugins/sfJobeetPlugin/package.xml --> <?xml version="1.0" encoding="UTF-8"?> <package packagerversion="1.4.1" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd" > <name>sfJobeetPlugin</name> <channel>plugins.symfony-project.org</channel> <summary>A job board plugin.</summary> <description>A job board plugin.</description> <lead> <name>Fabien POTENCIER</name> <user>fabpot</user> <email>fabien.potencier@symfony-project.com</email> <active>yes</active> </lead> <date>2008-12-20</date> <version> <release>1.0.0</release> <api>1.0.0</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <license uri="http://www.symfony-project.com/license"> MIT license </license> <notes /> <contents> <!-- CONTENT --> </contents> <dependencies> <!-- DEPENDENCIES --> </dependencies> <phprelease> </phprelease> <changelog> <!-- CHANGELOG --> </changelog> </package>
El
<contents>
contiene los archivos que hay que poner en el package:<contents> <dir name="/"> <file role="data" name="README" /> <file role="data" name="LICENSE" /> <dir name="config"> <file role="data" name="config.php" /> <file role="data" name="schema.yml" /> </dir> <!-- ... --> </dir> </contents>
El
<dependencies>
referencias de todas las dependencias pueda tener el plugin: PHP, Symfony, y también otros plugins. Esta información es utilizada por la tarea plugin:install
para instalar el plugin y su mejor versión para el proyecto y también para instalar las dependencias necesarias en caso de ser necesario.<dependencies> <required> <php> <min>5.0.0</min> </php> <pearinstaller> <min>1.4.1</min> </pearinstaller> <package> <name>symfony</name> <channel>pear.symfony-project.com</channel> <min>1.3.0</min> <max>1.5.0</max> <exclude>1.5.0</exclude> </package> </required> </dependencies>
Siempre debes declarar una dependencia a Symfony, como lo hemos hecho aquí. Declarar un mínimo y un máximo de versión permite a la tarea
plugin:install
saber que versión de Symfony es obligatoria ya que las versiones puede tener algo diferente en sus APIs.
Declara una dependencia con otro plugin también es posible:
<package> <name>sfFooPlugin</name> <channel>plugins.symfony-project.org</channel> <min>1.0.0</min> <max>1.2.0</max> <exclude>1.2.0</exclude> </package>
El
<changelog>
es opcional pero nos da información útil sobre lo que ha cambiado entre versiones. Esta información está disponible bajo la pestaña "Changelog" y también en el plugin feed.<changelog> <release> <version> <release>1.0.0</release> <api>1.0.0</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <license uri="http://www.symfony-project.com/license"> MIT license </license> <date>2008-12-20</date> <license>MIT</license> <notes> * fabien: First release of the plugin </notes> </release> </changelog>
Alojar un Plugin en el Sitio Web Symfony
Si desarrollas un Plugin útil y quiere compartirlo con la comunidad Symfony, crea una cuenta symfony si no tiene uno ya, entonces crear un nuevo plugin.
Te convertirás automáticamente en el administrador del plugin y verás una pestaña "admin" en la interfaz. En esta pestaña, usted encontrará todo lo que necesita para gestionar tu plugin y cargar tus packages.
El plugin FAQ contiene una gran cantidad de información útil para los desarrolladores de plugin.
Suscribirse a:
Entradas
(
Atom
)
Sígueme en las Redes Sociales
Donaciones
Datos personales
Entradas populares
-
En este apartado vamos a explicar como ejercutar archivos PHP a través del terminal de Ubuntu. Lo primero que tendríamos que hacer es inst...
-
En este blog voy a comentar un tema que se utilizan en casi todas las páginas web que existen, y es el tema de la paginación. La paginaci...
-
Este post trata de la integración de la librería PHPExcel en Codeigniter, aunque se podría aplicar a cualquier librería, como por ejemplo mP...
-
Ejemplo para añadir o sumar un número determinado de hora/s, minuto/s, segundo/s a una fecha en php. Con la función strtotime se puede ...
-
Este tema es uno de los temas primordiales sobre el framework Codeigniter, ya que en alguna ocación nos hemos visto obligados a recoger dato...
© Espacio Daycry - Espacio de programación 2013 . Powered by Bootstrap , Blogger templates and RWD Testing Tool