Autoloading en Zend Framework
by Christian Soronellas on octubre 9, 2011
Últimamente en emagister, me encuentro dedicado entre otras cosas a ajustar el rendimiento del autoloading de clases en Zend Framework ya qué es una posible mejora que puede reportar un enorme ROI en impacto de negocio. De acuerdo, quizá no directamente en impacto de negocio pero sí de manera indirecta. Probablemente un sitio web que pueda servir más páginas a más usuarios puede ver sensiblemente aumentado sus ratios de conversión. Y eso, creo que puede poner contentos a los chicos del área de negocio. Bien, empecemos.
El componente Zend_Loader
Este quizá sea uno de los componentes más usados en todo el framework, ya que cómo su propio nombre indica es el encargado de toda la carga de clases. Pero una de las cosas que más me desagrada, ya no del componente sino del propio framework en sí, es la manera qué tiene de usarlo, pues algunos de sus subcomponentes (cómo el Zend_Loader_PluginLoader) son instanciados directamente por otras clases produciendo un pequeño acople que a veces resulta un poco molesto. Y más si te dedicas a investigar cómo mejorar precisamente este aspecto.
Pros y contras de usar Zend_Loader
Por una parte, Zend_Loader es un componente muy cómodo para desarrollar sobretodo si estamos usando Zend_Loader_Autoloader. Tiene una integración perfecta con Zend_Controller_Front permitiendo la carga de clases automática dentro de módulos MVC, lo que cómo he dicho es muy cómodo pues el desarrollador ya no tiene que preocuparse de la carga de clases y puede centrarse en otros aspectos de la aplicación.
Cómo contra, está la penalización en rendimiento que puede ocasionar el utilizar según que partes. Por ejemplo, usar Zend_Loader::loadClass tiene una fuerte penalización en rendimiento debido a que siempre tiene que calcular el path a la clase que se le está pidiendo. Cálculos cómo diferenciar un namespace de PHP 5.3 de un namespace de PEAR, comprobar que ciertos caracteres no aparezcan en el nombre de archivo, jugar con el include_path y finalmente incluir la clase. Por suerte este componente ya ha sido deprecado a partir de la versión 1.8 de Zend Framework y a partir de la versión 2.0 va a dejar de existir, además de que se desaconseja encarecidamente su uso en favor del componente Zend_Loader_Autoloader.
Otro componente qué también penaliza bastante en rendimiento es el Zend_Loader_PluginLoader, es decir el responsable de la carga de plugins de Zend Framework y ampliamente usado (y acoplado :) por otros componentes. Este componente es capaz de registrar “prefijos” de classe y por cada uno de ellos asociarle uno o más paths donde puede existir el componente. El coding standards de Zend Framework dicta que los prefijos de clase sería todo el árbol de carpetas en el que se encuentra el componente separados por “_”. Así por ejemplo en el componente Zend_View_Abstract el prefijo de clase sería “Zend_View”. una vez visto esto y analizando un poco a fondo el componente Zend_Loader_PluginLoader se puede ver que al intentar cargar un plugin se recorre todos los prefijos de clases y sus paths asociados buscando un clase que encaje (por medio de Zend_Loader::isReadable, qué también penaliza). Por ejemplo para cargar el plugin (inventado) “Css” teniendo los prefijos registrados “Mi_Libreria_Personalizada” y “Zend_View_Helper” con 5 paths por cada prefijo, podría llegar a hacer 10 entradas en disco por cada petición HTTP.
Tips para mejorar el autoloading en Zend Framework
Voy a hacer una breve síntesis de los métodos qué existen para mejorar la carga de clases y de plugins en Zend Framework. Todo este material se puede encontrar en el sitio web de Zend Framework y por Google, y aquí lo voy a agrupar un poco.
Optimizar el include_path
Hay varias cosas que se pueden hacer para optimizar el include_path. En primer lugar, siempre usar paths absolutos o paths relativos a un path absoluto para hacer mayor uso de las directivas realpath_cache_*, ya que así no obligamos al intérprete de PHP a resolver el path relativo y además vamos a ganar algo en rendimiento ya que vamos a hacer que internamente PHP use el cache de paths absolutos.
<?php $paths = array( realpath(__DIR__ . '/../library'), '.' ); set_include_path(implode(PATH_SEPARATOR, $paths));
Además también podemos reducir el numero de paths dentro de include_path para reducir las posibilidades de búsqueda de clases, definir el path a Zend Framework lo antes posible en la lista de paths de include_path y/o no definir el directorio actual cómo directorio de búsqueda en include_path. Así que si aún no sigues estas reglas, ya lo sabes! ;)
Eliminar require_once innecesarios
Las aplicaciones web actuales deberían todas hacer uso del autoloading de clases. Si aún no lo estás usando, plantéatelo pues es algo bastante necesario ya que unifica los procesos de carga de clases y ayuda a establecer un orden de directorios.
Zend Framework por defecto en casi todas las definiciones de clase usa los require_once, y usando el componente Zend_Loader_Autoloader ya no son necesarios. Y no solo eso sino que de esta manera estamos usando lazy loading para la carga de clases lo que sin duda nos va a dar alguna mejora de rendimiento.
# Script para remover los "require_once" innecesarios en *nix % cd path/to/ZendFramework/library % find . -name '*.php' -not -wholename '*/Loader/Autoloader.php' \ -not -wholename '*/Application.php' -print0 | \ xargs -0 sed --regexp-extended --in-place 's/(require_once)/\/\/ \1/g'
# Script para remover los "require_once" innecesarios en mac (el sed no funciona exactamente igual) % cd path/to/ZendFramework/library % find . -name '*.php' -not -wholename '*/Loader/Autoloader.php' \ -not -wholename '*/Application.php' -print0 | \ xargs -0 sed -E -i '.bak' 's/(require_once)/\/\/ \1/g'
Mejorar la velocidad del PluginLoader
Cómo ya he dicho antes, el PluginLoader es un componente que permite registrar prefijos de clases con múltiples paths. Esto penaliza en rendimiento pues por cada solicitud de carga de plugins se debe examinar cada prefijo con cada path, para encontrar la clase. Aún así el componente Zend_Loader_PluginLoader puede crear una cache de plugins, es decir crea en tiempo de ejecución un archivo PHP dónde va añadiendo includes de todos los plugins de los que ya ha resuelto el path. Así que yo aconsejaría encarecidamente usar esta característica para entornos de producción (no tiene sentido ponerlo en entorno de desarrollo a no ser que se quiera medir el rendimiento de la aplicación).
<?php
$classFileIncCache = APPLICATION_PATH . '/../data/pluginLoaderCache.php';
if (file_exists($classFileIncCache)) {
include_once $classFileIncCache;
}
if ($config->enablePluginLoaderCache) {
Zend_Loader_PluginLoader::setIncludeFileCache($classFileIncCache);
}
Aún sigo necesitando mejor rendimiento en carga de clases, qué puedo hacer?
Si aún así, después de estos tips necesitas mejorar la carga de clases aún existe algún improvment más que se puede usar. En la versión 2.0 de Zend Framework, han mejorado el componente relativo a la carga de clases para sacar las partes deprecadas y para mejorar sustancialmente el rendimiento. Lo bueno es que este componente ya es estable y puede ser usado.
Uno de los puntos del nuevo desarrollo del componente de autoloading en Zend Framework ha sido el crear el autoloader más rápido posible, es decir aquél que no tenga que calcular nada para conocer la ubicación de una clase pero que aún así sepa dónde están ubicadas todas las clases que se van a usar. Esto se ha traducido en un mapa de clases.
En este caso, lo que propone Zend es que antes de hacer un deploy a producción se generen estos mapas de clases (un archivo con un array de clases cómo claves y paths cómo valores) para que la nueva clase ClassMapAutoloader (al fin y al cabo es un “autoloader tonto”) pueda disponer de ellos y mejorar el rendimiento. Si esto lo combinamos con APC que ya de por sí sin hacer nada acelera las aplicaciones web en PHP, tendríamos que poder ver una mejora sustancial del rendimiento.
Y cómo lo puedo usar en mi Zend Framework 1.x?
Pues bien los chicos de Zend Framework se han encargado de hacer un mini port para Zend Framework 1.x y se puede localizar en el github personal de Matthew Weier O’phinney, Techlead de Zend Framework, dónde se explica detenidamente cómo usarlo.
Y ya estoy Certificado en PHP 5.3!
by Christian Soronellas on julio 30, 2011
A día de ayer, cosa de las 09.30h de la mañana, realicé el examen de certificación de PHP 5.3. Mis impresiones, a decir verdad, fueron que me esperava el examen un poco más difícil de lo qué realmente fue. Aunque sí qué es verdad qué había preguntas oscuras qué entrañaban cierta dificultad, creo que muchas de las preguntas podían sacarse usando la táctica de eliminación.
Pocas preguntas específicas de PHP 5.3
Una de las cosas que más me llamó la atención fue que preguntas específicas a PHP 5.3 no hubo más que tres: Una de namespaces y de late static bindings bastante sencillas y una de closures un poco más rebuscada. Pero realmente, eché en falta más preguntas relativas en PHP 5.3
Otros temas que se tocaron en el examen
Son los que ya se mencionan en las guías de Zend. Específicamente, de Databases me cayeron un par y las dos sobre transacciones bastante sencillas todo hay que decirlo. De fechas solo me cayó una, también sencillita. De XML, nada más que un par o tres de preguntas. Sobre todo hicieron foco en los PHP Basics, en el tema de Arrays, Web Features, algunas de Streams y de Security.
Mi recomendación
Para todos aquellos qué estén leyendo esta entrada y quieran sacarse la certificación de PHP 5.3, sobretodo hacer foco en los puntos en los qué he hecho referencia en el párrafo anterior, obviamente sin olvidar los otros. Por otra parte, no creo qué sea indispensable pero si muy útil, y es que una semana antes de hacer el exámen tuve acceso a unos videos sobre formación en certificación PHP 5.3 que proporciona Zend. Si podéis acceder a ellos de alguna manera los recomiendo por qué tratan preguntas parecidas a las del examen y sirven para ver en lo que se está fallando.

PHP 5.4 – El espectáculo continúa
by Christian Soronellas on julio 2, 2011
Muchos todavía estamos descubriendo las bondades de PHP5.3 y sus novedades y aún hay muchos sitios importantes que corren sobre PHP5.2. Sin embargo, el equipo que hace PHP no descansa y recientemente han publicado la primera versión alpha de lo que será PHP5.4 que promete muchas mejoras tanto en rendimiento como en funcionalidades.
Mi compañero Ricard Clau y yo hemos estado haciendo algunas pruebas con esta versión y aquí os presentamos algunas de las novedades que consideramos más destacadas.
Traits
Probablemente el plato estrella de la versión. Un trait viene a ser algo así como un trozo de clase que podemos importar en varios sitios. Además, en una clase hija, podemos heredar de varios traits. Y con esto, en parte, conseguimos la tan deseada por algunos herencia múltiple en cierta manera.
<?php
trait Singleton
{
private static $_instance;
private function __construct(){}
public function __clone()
{
throw new Exception('This object cannot be cloned!');
}
public static function getInstance()
{
if (null === static::$_instance) {
static::$_instance = new static();
}
return static::$_instance;
}
}
class Product
{
use Singleton;
}
$product = Product::getInstance();
Más fácil aún hacer Singletons y sobretodo, centralizado el código de instanciación.
Podéis ver algunos ejemplos más con, por ejemplo, tratamiento de colisiones al hacer el use de varios traits con funciones idénticas y algunos otros ejemplos en esta web.
Array Deferencing
Esto es algo bastante molón que hasta ahora no se podía hacer. Cuántas veces habéis tenido que llamar a una función que devuelve un array y al tener que tratar su resultado habéis pensado: Qué bien me iría poder poner corchetes al resultado de la función directamente. Pues eso por fin se puede hacer:
<?php
echo call_user_func(function(){return array('test', 'deferencing');})[1];
Aquí veríamos un ‘deferencing’ en consola. Muy a lo JavaScript.
DTrace
El DTrace es un sistema de debug de aplicaciones en tiempo real en entornos de producción. Usa scripts en “D” para debuggar partes de la aplicación al alcanzar ciertas condiciones (qué X archivo se ha abierto, qué Y linea se ha alcanzado, etc. ) y así aportar visibilidad en entornos de producción. Lejos ha quedado aquel tiempo en que las webs eran solamente una sección de noticias y un triste catálogo. Y a veces, tenemos aplicaciones monstruosas donde es complicado ver la secuencia de llamadas. Seguro que esto ayuda a encontrar errores en nuestra aplicación.
Mejoras en memoria y rendimiento de Zend Engine
Este punto es súper importante que no se descuide nunca. Bien es cierto que con los cacheos (HTTP_Cache, CDNs varios, Memcached, etc.) con APC para Opcode Cache y el resto de técnicas de high-performance se optimiza mucho. Pero al final, optimizando el core de PHP se ganará muchísimo ya que por la naturaleza de PHP, cada Request vuelve a empezar todo y el proceso de bootstraping debe ir lo más rápido posible.
Paso a PECL de la extensión de SQLite
Cada vez se usa más SQLite, para testear la BBDD, para entornos de desarrollo locales y es importante tener claro cómo trabajar con ella y cómo instalar la extensión para PHP.
Rotura con algunas miserias del pasado
Desaparecen por fin settings de tan infausto recuerdo como safe_mode, register_globals (cuánto daño ha hecho), register_long_arrays, allow_call_time_pass_reference, etc.
También desaparecen las siguientes funciones relacionadas con el tratamiento de sesiones: session_is_registered(), session_register() and session_unregister()
Además, el tema del break y el continue, queda de la siguiente manera: podemos hacer break y continue para salir de un bucle, break 2 o continue 3 para saltarnos varias iteraciones pero no podemos hacer un break $i. Nos dará un error de algo así como Expected Constant Value.
<?php
public function testBreakI()
{
$z = 2;
for($i = 0; $i < 10; $i++)
for($j = 0; $j < 10; $j++)
if ($i == 2 && $j == 2)
break $z;
var_dump($i, $j);
}
testBreakI();
Y en php.ini, el default encoding es UTF-8, el short tag de template = estará habilitado siempre y algunas cositas más que podéis ver en PHP.net
Conclusión
Es para mí, notícia de gran alegría, ver cómo un lenguaje al qué le profeso auténtica devoción va evolucionando día a día. Desde aquí dar las gracias a toda la comunidad de PHP por hacerlo más grande versión a versión. Y qué siga así por mucho más.
Si bien es cierto qué siempre hay cosas qué mejorar, pues soy firme creyente en qué siempre hay margen de mejora, cómo por ejemplo el tema de la superpoblación de frameworks o la perversión de CMSs.
Pero … que grande qué es PHP!
Acerca de los ciclos de desarrollo, respetuosamente.
by Christian Soronellas on junio 20, 2011
Recientemente me he visto envuelto en una discusión de alto nivel en LinkedIn, pues un compañero y amigo de allí dónde trabajo me recomendó qué le echara un vistazo por ciertos comentarios vertidos en ese hilo. El título del hilo, JQuery or YUI? Pro’s and Cons. Dentro del grupo Front end Developer Group. Un interesantísimo debate. Lo recomiendo a quién me esté leyendo, tenga facilidad con el inglés y sobretodo le interesen temas de front-end.
Está mi ciclo de desarrollo en … peligro?
A lo largo de este debate cierto individuo, tengo qué decir con toda sinceridad que equivocadamente, pone por delante de cualquier otra decisión técnica el valor qué tiene la velocidad de ejecución cuándo se trata de decidir usar un framework de JS. Si ya te has puesto las manos a la cabeza, por favor, espera a acabar de leerme.
Es evidente qué la velocidad es un factor a tener en cuenta cuándo hemos de decidir usar una librería u otra. Pero nunca puede ser el único motivo por el cuál decantarse. Estaríamos incurriendo en una mala decisión, qué nos puede llegar a afectar muy negativamente en un futuro, ya qué las decisiones técnicas deben venir siempre motivadas por unas necesidades de negocio. Estás necesidades responden a la medición de unos costes sobre las mismas oportunidades de negocio qué nunca se pueden menospreciar. Es decir, me explico. Cuál puede ser el coste, para un negocio, de perder una oportunidad? Un gran oportunidad? Puede suponer perder el hecho posicionarse delante de la competencia en una posición realmente privilegiada. Luego la propia marca empieza a perder valor (o le cuesta horrores recuperarlo), delante de la competencia. Y esto, sólo sería el principio de la cadena de consecuencias. Y eso lo puede solucionar un factor cómo la velocidad de ejecución de una librería JS? Esto qué parece tan mezquinamente lógico, para ciertos individuos no lo es. Se debe ir con calma y humildad.
Qué impacto puede tener esto en el ciclo de desarrollo?
El caso más simple y más fácil de ver, es el del típico tío de negocios, cliente, Product Owner qué quiere ver lo qué ha solicitado a desarrollo, online lo antes posible. Obviamente, su misión será presionar para conseguirlo cuánto antes. Hasta aquí, nada raro. Nada qué no se viva en una empresa cotidianamente. Pero qué pasa cuándo por ejemplo, el diseño de esa librería tan rápida no permite desarrollar la nueva funcionalidad tan rápidamente? O cuándo la curva de aprendizaje es realmente pronunciada? O cuándo resulta qué cómo consecuencia del pésimo diseño de ese framework, tenemos algo muy costoso de mantener? Pues qué ese ciclo de desarrollo se puede ver incrementado sensiblemente, debido al coste de implementar/mantener la nueva funcionalidad. O de aprender cómo funciona la librería. O un largo etcétera.
Y ¡Carámba! Ahora a negocio, ya no le sirve de nada qué usemos algo realmente ultrarápido, pues todo lo qué ganamos con un aspecto, lo perdemos irremediablemente al perder esa oportunidad de negocio. Después, de su pertinente tirón de orejas. E incluso, dependiendo de la oportunidad perdida, nuestro puesto de trabajo.
Conclusiones finales
Elegir una herramienta de trabajo, no es un juego. Es algo de suma importancia y no debe nunca ser tomado cómo tal. De ello puede depender toda una empresa, por lo qué se deben considerar todos los requerimientos impuestos por negocio. Sin excepción.
Patrón Service Layer en Zend Framework
by Christian Soronellas on febrero 8, 2011
Introducción
Muchas veces las aplicaciones web se encuentran con la necesidad de implementar ciertas funcionalidades o características qué deben responder al navegador en diferentes formas. Un ejemplo de ello son las aplicaciones web funcionando con AJAX y solicitando datos al servidor de forma asíncrona sin bloquear la UI del navegador, para así poder ofrecer al usuario una mejor y más rica experiencia de usuario. Es por ello también, qué se produjo el nacimiento de las restful web applications (de las qué voy a hablar en venideras entradas :). Pero el objeto de esta entrada se centra en ofrecer una via de respuesta con formato JSON implementando el protocolo JSON-RPC 2.0 a través del componente, Zend_Json_Server de Zend Framework. Un método un poco más simple qué REST pero algo menos complicado que, bajo mi humilde punto de vista, puede servir de enfoque para proyectos web no demasiado grandes.
Patrón Service Layer
Tal cómo define Randy Stafford en el libro de Martin Fowler el patrón Service Layer define un límite de la aplicación con una capa de servicios que establece un conjunto de operaciones disponibles y coordina la respuesta de la aplicación en cada operación. En otras palabras, estaríamos hablando de una capa qué actuaría por encima del modelo de dominio y que proporcionaría los datos qué se le pidieran a través de los métodos qué definiera. Así, por ejemplo, podríamos tener un service de pedidos (para sólo obtener datos de pedidos), o un service de productos, o de usuarios, etc.
En este caso no voy a entrar en detalles acerca de cómo implementar otros formatos de respuesta, pues es bien sencillo a partir de revisar el ejemplo qué todo seguido voy a exponer.
Sí, sí, muy guapo … pero y ahora qué?
Pues todo seguido voy a poner un ejemplo de una manera cómo se podría implementar sin llegar a ser demasiado complicado. Cómo siempre va a estar basado en una instalación limpia de Zend Framework. Así qué lo primero es iniciar nuevo proyecto y lo podemos hacer mediante
> zf create project . > zf enable layout > zf configure db-adapter 'adapter=PDO_SQLITE&dbname=APPLICATION_PATH "/../data/db/database-development.db"' development > zf create db-table section Section > zf create model Section > touch data/db/database-development.db > sqlite3 data/db/database-development.db
Luego procederemos a crear la base de datos con la qué vamos a trabajar en el entorno de desarrollo.
CREATE TABLE section ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title VARCHAR(50) NOT NULL, BODY TEXT NOT NULL );
Una vez creado el nuevo proyecto y la base de datos, procedemos a crear toda la capa qué interactuará con el Domain Model.
/**
* File located at library/Application/Services/Sections.php
*/
class Application_Services_Sections
{
/**
* The sections table instance
* @var Application_Model_DbTable_Section
*/
protected $_sectionsTable;
/**
* A setter to inject the sections table
*/
public function setTablesSection(Zend_Db_Table_Abstract $sectionsTable)
{
$this->_sectionsTable = $sectionsTable;
}
/**
* Returns a list of avaliable sections
* @return array
*/
public function getSections()
{
if (null === $this->_sectionsTable) {
$this->_sectionsTable = new Application_Model_DbTable_Sections();
}
return $this->_sectionsTable->fetchAll()->toArray();
}
/**
* Returns a single section by its ID
* @param integer $id The section ID
* @return array
*/
public function getSection($id)
{
if (null === $this->_sectionsTable) {
$this->_sectionsTable = new Application_Model_DbTable_Sections();
}
return $this->_sectionsTable->find($id)->current()->toArray();
}
}
De este modo queda definido nuestro punto de entrada a la aplicación por JSON-RPC, bajo el qué hay disponibles dos operaciones: getSections, qué devuelve una lista de secciones y getSection, qué dado un ID de sección devuelve su correspondiente registro. Ahora procederemos a crear el controlador encargado de proporcionar la respuesta de las llamadas por medio de JSON-RPC y sus correspondientes rutas para que la aplicación pueda responder por los diferentes servicios qué se le soliciten. Veamos.
/**
* File located at application/controllers/ServiceController
*/
class ServiceController extends Zend_Controller_Action
{
/**
* The JSON-RPC server instance
* @var Zend_Json_Server
*/
protected $_jsonServer = null;
/**
* The inflector used to map all the services to the classes correctly
* @var Zend_Filter_Inflector
*/
protected $_inflector = null;
/**
* (non-PHPdoc)
* @see library/Zend/Controller/Zend_Controller_Action::init()
*/
public function init()
{
/**
* Initializes the controller, and disable the viewRenderer and the layout
* only for this controller. Initializes the inflector and then starts the
* Zend_Json_Server.
*/
$this->_helper->getHelper('viewRenderer')->setNoRender(true);
$this->_helper->getHelper('layout')->disableLayout();
$this->_inflector = new Zend_Filter_Inflector(':classname');
$this->_inflector->setFilterRule(
'classname', array('Word_UnderscoreToDash', 'Word_DashToCamelCase')
);
$this->_jsonServer = new Zend_Json_Server();
}
/**
* Prepares the JSON-RPC server with the service (class :) requested. The reques
* ted service should exist on the "library/Application/Service/ServiceName.php",
* being "ServiceName" the service name.
* For example, to call an operation form the service "order-detail", the file
* "library/Application/Service/OrderDetail.php" must exist.
* @param string $service The requested service
*/
protected function _prepareJsonRpcServer($service)
{
$this->_jsonServer->setClass(
'Application_Service_' . $this->_inflector->filter(
array('classname' => $service)
)
);
}
/**
* Handle a JSON-RPC Request
*/
public function indexAction()
{
$this->_prepareJsonRpcServer($this->getRequest()->getParam('service'));
$this->_jsonServer->handle();
}
/**
* Return the Service descriptor
*/
public function smdAction()
{
$service = $this->getRequest()->getParam('service');
$this->_prepareJsonRpcServer($service);
$this->_jsonServer->setTarget('/services/' . strtolower($service))
->setEnvelope(Zend_Json_Server_Smd::ENV_JSONRPC_2);
$this->getResponse()->setHeader('Content-type', 'application/json');
$this->getResponse()->setBody($this->_jsonServer->getServiceMap());
}
}
Este controlador, tiene 2 propiedades. Una para albergar una instancia del servidor de JSON-RPC y otra para albergar una instancia del mapper que actuará de puente entre los servicios disponibles y sus classes correspondientes. Además definimos dos posibles acciones: la acción por defecto, desde la qué se van a realizar todas las operaciones (indexAction) y otra acción (smdAction) para enviar un descriptor del servicio a quién lo solicite. Para la ejecución de todas las acciones, vamos a decirle a este controlador qué no haga render de la vista y que no la vista con su layout correspondiente. Con el controlador definido, ahora sólo queda definir las rutas correspondientes.
; File located at application/configs/routes.ini ; Application routes ; The main services endpoint routes.service.route = "services/:service" routes.service.defaults.controller = service routes.service.defaults.action = index ; The service descriptor route routes.smd.route = "services/service-descriptor/:service" routes.smd.defaults.controller = service routes.smd.defaults.action = smd
Añadimos una entrada en el archivo de bootstrap para qué se haga uso de las rutas en la aplicación
/**
* File located at application/Bootstrap.php
*/
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
public function _initApplicationRoutes()
{
$this->bootstrap('frontcontroller');
$this->getResource('frontcontroller')
->getRouter()
->addConfig(
new Zend_Config_Ini(
APPLICATION_PATH . DIRECTORY_SEPARATOR . 'configs' . DIRECTORY_SEPARATOR . 'routes.ini'
),
'routes'
);
}
}
Ya con casi todo el tinglado montado, sólo nos queda montar la parte del cliente qué va a contactar y consumir los servicios disponibles. Para ello, yo he implementado un cliente muy muy sencillo a modo de ejemplo a través del plugin de jQuery, jQuery JSON RPC 2.0. Así qué bajamos una copia del proyecto y copiamos el archivo jquery.jsonrpc.js a la carpeta js (sino existe la creamos) de nuestro directorio public. Además allí también crearemos el archivo site.js con el siguiente código.
$(document).ready(function() {
$('a').click(function(event) {
$.jsonRPC.setup({
endPoint: '/services/sections'
});
$.jsonRPC.request('getSections', {}, {
success: function(result) {
var html = '<ul id="sections" class="list">';
$.each(result['result'], function(index, section) {
html += '<li class="section">' + section.title + '</li>';
});
html += '</ul>';
$(document.body).append(html);
}
});
event.preventDefault();
});
});
Y ya para finalizar, sólo hace falta añadir una interacción en la presentación del controlador IndexController para poder demostrar todo este comportamiento. Además también añadiremos las llamadas necesarias a los correspondientes archivos de javascript para qué sea efectiva la demostración.
<!-- File located at application/layouts/scripts/layout.phtml -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
<html>
<head>
<title>JSON RPC Test</title>
</head>
<body>
<?php echo $this -> layout() -> content; ?>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script type="text/javascript" src="/js/jquery.jsonrpc.js"></script>
<script type="text/javascript" src="/js/site.js"></script>
</body>
</html>
<!-- File located at application/views/scripts/index/index.phtml --> <p><a href="#">Get a list of sections</a></p>
Concluyendo
Con todo lo dicho hasta ahora deberíamos ser capaces de poder invocar métodos remotos mediante JSON-RPC. Y esto puede ser extrapolado a lo que se quiera hacer. Es decir, si tenemos un e-commerce del qué queremos conocer el detalle de un pedido asíncronamente, esta puede ser una manera (no la única) de hacerlo ordenadamente, qué a mi particularmente me gusta. Y no soy el único puesto qué sitios más grandes con altos volumen de tráfico (Facebook, Tuenti, etc.) ya lo estan llevando a la práctica (esto y algunas cosas más).
Cómo siempre, voy a dejar una copia del código colgada en mi cuenta de github para quién le interese poder trastear un poco con todo esto.
Zend Framework, Zend_View y Twig
by Christian Soronellas on enero 14, 2011
Introducción a los motores de plantillas en PHP
Para contextualizar esta entrada voy a hablar un poco sobre los motores de plantillas. Los motores de plantillas son librerías que permiten abstraer todas las vistas de una aplicación buscando la máxima eficiencia y la mejor reusabilidad de código. Permiten por ejemplo, si tenemos listados de productos en diferentes categorizaciones, definir una UI común para las categorías qué lo precisen. Esto dota a la aplicación de mucha flexibilidad, y le da sentido a la V de MVC. Todo ello gracias a el control sobre la salida que proporciona PHP (cosa qué, por ejemplo, era imposible con el antiguo ASP).
En PHP existen multitud de motores de plantillas, si realizamos una búsqueda en google lo podremos comprobar. Pero básicamente, el standard de facto hasta ahora era Smarty. Sí, sí. He dicho era. Puesto que a fecha de hoy han salido otros motores de plantillas que, bajo mi punto de vista, han puesto en entredicho que Smarty sea el motor de plantillas de PHP por excelencia. Uno de ellos, y voy a mencionar el objeto de esta entrada, es Twig.
Smarty 3 vs Twig
Porqué no usar Smarty 3
Todo sea dicho. Smarty és una gran librería para la separación de la vista con la lógica de la aplicación. Cumple su función y la cumple con creces. Sin embargo, su segunda versión quedó desfasada con la salida de PHP 5 y su nuevo modelo de gestión de objetos. Es por ello, que hace poco que ha salido Smarty 3. Entre las nuevas características de Smarty 3, sumadas a las que se mantienen de versión Smarty 2, podemos destacar:
- Reescrito para PHP5
- Un analizador real para su sintaxis (su anterior versión no tenia)
- Herencia de plantillas
- Autoescaping de variables
- Definición de funciones en contexto de plantilla
Y estas son solo por mencionar algunas, pues hay más. Quizá ahora la pregunta sea, y qué tiene de malo? Al parecer todo lo dicho hasta ahora tiene buena pinta. Pero y la performance? Cómo va afectar al rendimiento de nuestra aplicación la inclusión de uno u otro motor? Pues la verdad que para responder a esta pregunta recomiendo la lectura de un artículo de Fabien Potencier, dónde establece una comparativa de motores de plantillas en PHP, y qué cada uno seque sus propias conclusiones. Al fin y al cabo, los proyectos los acaba conociendo uno mismo y uno mismo sabe qué conviene según el caso. Yo me quedo con Twig.
Por qué sí usar Twig
En primer lugar, y cómo ya he mencionado antes, por motivos de rendimiento. Tal y cómo demuestra Fabien Potencier en su artículo hay diferencia de rendimiento entre los dos motores. Pero más allá de eso, la decisión a decantarse por este componente puede implicar otros aspectos del mismo. Con lo cuál, lo que viene a continuación es más a nivel de mi propia opinión (no lo declaro cómo estándar industrial).
En segundo lugar, Este motor tiene ciertas carácterísticas ya desarrolladas qué no vienen de serie en Smarty. Una de ellas y tengo que decir que personalmente me gusta mucho, es el modo Sandbox. El modo Sandbox, permite restringir qué características se le permite usar al editor de las plantillas.. Me refiero a, si yo no quiero que el web designer pueda usar la estructura if, mediante el modo Sandbox de Twig no la va a poder usar. Y eso con cualquier carácteristica del motor, incluso se pueden restringir nuestras propias clases de modelo. Impresionante.
Por tercer y último lugar, me gusta más el modelo de extensibilidad de Twig qué el de Smarty. Considero qué es más fácil de añadir funcionalidades a Twig. Smarty, en su versión 3, aún trae consigo el modelo de plugins de Smarty 2 y sinceramente, con PHP 5.3 actualmente en alza, hecho en falta un modelo de extensibilidad más orientado a objetos.
Zend_View y su agnosticismo
Digo agnosticismo puesto qué es una de las virtudes de este componente. Zend_View es agnóstico en cuánto a template engine se refiere, a través de la interface Zend_View_Interface, siendo PHP propiamente el template engine por defecto. Y así será cómo en este artículo voy a ilustrar cómo implementar Twig cómo extensión de Zend_View y sobreescribir la instancia por defecto de Zend_View por nuestra implementación a través de la (ya conocida :) clase abstracta Zend_Application_Resource_ResourceAbstract.
// File located at "library/Twig/Zend/View/Twig/TwigImplementation.php
require_once 'Zend/View/Interface.php';
require_once 'Twig/Autoloader.php';
class Twig_Zend_View_Twig_TwigImplementation
implements Zend_View_Interface
{
/**
* The twig loader instance
* @var Twig_Environment
*/
protected $_twigEnv;
/**
* The twig loader instance
* @var Twig_Loader_Interface
*/
protected $_twigLoader;
/**
* A map of all registered variables
* @var array
*/
protected $_variablesMap = array();
/**
* Class constructor
* @param string $templateBasePath The views base path
* @param array $envOptions
*/
public function __construct($templateBasePath, array $envOptions = array())
{
$zendAutoloader = Zend_Loader_Autoloader::getInstance();
$zendAutoloader -> pushAutoloader(array('Twig_Autoloader', 'autoload'));
$this -> _twigLoader = new Twig_Loader_Filesystem($templateBasePath);
$this -> _twigEnv = new Twig_Environment($this -> _twigLoader, $envOptions);
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::getEngine()
* @return Twig_Environment
*/
public function getEngine()
{
return $this -> _twigEnv;
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::setScriptPath()
*/
public function setScriptPath($path)
{
$this -> _twigEnv -> getLoader() -> setPaths($path);
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::getScriptPaths()
*/
public function getScriptPaths()
{
return $this -> _twigEnv -> getLoader() -> getPaths();
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::setBasePath()
*/
public function setBasePath($path, $classPrefix = 'Zend_View')
{
$this -> setScriptPath($path);
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::addBasePath()
*/
public function addBasePath($path, $classPrefix = 'Zend_View')
{
$this -> setScriptPath($path);
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::__set()
*/
public function __set($key, $val)
{
$this -> _variablesMap[$key] = $value;
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::__isset()
*/
public function __isset($key)
{
return isset($this -> _variablesMap[$key]);
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::__unset()
*/
public function __unset($key)
{
unset($this -> _variablesMap[$key]);
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::assign()
*/
public function assign($key, $value = NULL)
{
if (!is_null($value)) {
$this -> _variablesMap[$key] = $value;
} elseif (is_array($key)) {
$this -> _variablesMap = array_merge($this -> _variablesMap, $key);
}
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::clearVars()
*/
public function clearVars()
{
$this -> _variablesMap = array();
}
/**
* (non-PHPdoc)
* @see library/Zend/View/Zend_View_Interface::render()
*/
public function render($name)
{
$template = $this -> _twigEnv -> loadTemplate($name);
return $template -> render($this -> _variablesMap);
}
}
Está clase nos va a servir para introducir Twig en el contexto de Zend Application, cómo implementación de Zend_View_Interface. Ahora el siguiente paso es sobreescribir la instancia de Zend_View en tiempo de ejecución. Y eso lo podemos llevar a cabo creando una clase que herede de Zend_Application_Resource_ResourceAbstract.
// File located at "library/Twig/Zend/View/Twig/Twig.php"
require_once 'Zend/Application/Resource/ResourceAbstract.php';
require_once 'Twig/Zend/View/Twig/TwigImplementation.php';
defined('DS')
|| define('DS', DIRECTORY_SEPARATOR);
class Twig_Zend_View_Twig_Twig
extends Zend_Application_Resource_ResourceAbstract
{
/**
* The Zend_View_TwigImplementation instance
* @var Zend_View_TwigImplementation
*/
protected $_view;
/**
* (non-PHPdoc)
* @see library/Zend/Application/Resource/Zend_Application_Resource_Resource::init()
*/
public function init()
{
return $this -> _getView();
}
/**
* Returns the Zend_View_TwigImplementationInstance. If not initialized,
* initializes the instance
*/
protected function _getView()
{
if (null === $this -> _view)
{
$options = $this -> getOptions();
$view = new Twig_Zend_View_Twig_TwigImplementation(APPLICATION_PATH . DS . 'views' . DS . 'scripts', $options);
Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')
-> setView($view)
-> setViewSuffix('twig');
$this -> _view = $view;
}
return $this -> _view;
}
}
A través de la llamada a setView del helper viewRenderer se va a sobreescribir la instancia de Zend_View por nuestra implementación, previamente instanciada y además también le notificamos qué nuestras vistas tendrán la extensión .twig. Luego sólo falta modificar nuestro application.ini para poder empezar a usar el motor.
; ... ; Twig config pluginPaths.Twig_Zend_View_Twig_ = "Twig/Zend/View/Twig/" resources.twig.cache = APPLICATION_PATH "/../cache/twig"
Ya ahora renombramos nuestras vistas, de index.phtml a index.twig
<!-- views/scripts/index/index.twig -->
<h1>List of available sections</h1>
<ul>
{% for section in sections %}
<li>{{ section }}</li>
{% endfor %}
</ul>
Y ya para finalizar este artículo, dejo disponible todo el código de este artículo, en mi cuenta de Github
Por y para la comunidad … e aquí mi granito de arena
by Christian Soronellas on diciembre 23, 2010
He decidido liberar el código que expongo aquí en mis entradas. Para ello, voy a hacer uso de mi cuenta en Github para ir colgando en forma de repositorios el código qué aquí vaya exponiendo. De momento hay disponible el código de la primera entrada de Zend Framework y Doctrine 2. Para quién le interese es libre de bajarse el código y usarlo. Espero así aportar mi granito de arena a la comunidad.
Integración de Zend Framework con Doctrine 2 CLI
by Christian Soronellas on diciembre 5, 2010
Introducción a los entronos de CLI
Los entornos CLI son entornos ejecutados directamente en el shell del sistema operativo. Su nombre CLI es acrónimo de Command Line Interface (Interfaz de Linea de Comandos) y son usados para la ejecución de tareas muy especificas. Para el caso que nos ocupa, ambas herramientas disponen de sus respectivas herramientas CLI: la Zend Tool por parte de Zend Framework y la Doctrine CLI por parte de Doctrine 2. Ambas herramientas CLI, están pensadas para la generación de código y para aligerar tareas de mantenimiento de aplicaciones web. Por ejemplo: la creación de una aplicación web Zend Framework con su layout especifico, la creación de las distintas clases del modelo de dominio de Zend_Db por parte de Zend Tool o la creación de Entities, Proxies o Repositories en Doctrine 2.
Primeros pasos. Entendiendo las entrañas de Zend Tool
Zend Tool, consta de varios componentes que facilitan la integración de tareas en su contexto. Por una parte está la familia de componentes Zend_Tool_Framework que, a grandes rasgos, facilita la integración de tareas independientes de aplicaciones web. Por ejemplo si tuviéramos que diseñar una tarea que sirviera cómo interface al cron del sistema (por poner un ejemplo) sin que este debiera usar recursos de la aplicación web, muy probablemente la mejor manera sea usando Zend_Tool_Framework. En cambio si la tarea que debemos diseñar depende en algún momento de algún recurso de la aplicación web, seguro necesitará conocer determinados aspectos de la misma. Y esto se puede conseguir a través de la familia de componentes de Zend_Tool_Project (qué es el componente que vamos a usar aquí y ahora).
Una vez ya con las tareas diseñadas e implementadas, se le debe notificar a la Zend Tool de la existencia de las mismas a través del archivo .zf.ini situado dentro del espacio del usuario que ejecuta el shell.
Música, maestro!
En este artículo voy a detallar, la creación de dos tareas que se van a vincular a una aplicación web Zend Framework con Doctrine 2 integrado cómo O/RM. Estas dos tareas, se van a encargar principalmente de configurar Zend Application para que pueda ejecutar Doctrine 2 sin problemas (el cómo lo expliqué en mi anterior artículo, puedes verlo aquí) y de generar diferentes archivos útiles para su ejecución (proxies y repositories).
Analizando un poco qué arquitectura de tanto Zend Tool Project cómo de Doctrine 2 CLI vamos a plantear una clase genérica que sirva para preparar todo el entorno de Doctrine 2 y se pueda extender para crear diferentes providers de tareas.
<?php
/**
* Archivo ubicado en /library/Doctrine/Zend/Tool/Project/Provider/Abstract.php
*/
require_once 'Zend/Tool/Project/Provider/Abstract.php';
require_once 'Doctrine/Common/ClassLoader.php';
abstract class Doctrine_Zend_Tool_Project_Provider_Abstract
extends Zend_Tool_Project_Provider_Abstract
{
/**
* A class property to hold the Zend_Tool_Project_Profile instance.
* @var Zend_Tool_Project_Profile
*/
protected $_profile;
/**
* The application.ini config file
* @var Zend_Config_Ini
*/
protected $_config;
/**
* The path to the application.ini
* @var string
*/
protected $_appConfigFilePath;
/**
* The path to the application directory
* @var string
*/
protected $_appPath;
/**
* The target section of the application.ini we want to work on
* @var string
*/
protected $_sectionName;
/**
* The Doctrine CLI instance, ready to execute tasks
* @var Symfony\Component\Console\Application
*/
protected $_doctrineCli = NULL;
/**
* Prepares Doctrine 2 for task performing, with the application.ini stuff
* @return \Symfony\Component\Console\Application $cli
*/
protected function _prepare($sectionName = 'production')
{
if (is_null($this -> _doctrineCli)) {
// Load the project profile (.zfproject.xml)
$this -> _profile = $this -> _loadProfile(self::NO_PROFILE_THROW_EXCEPTION);
$appConfigFileResource = $this -> _profile -> search('applicationConfigFile');
if ($appConfigFileResource == FALSE) {
throw new Zend_Tool_Project_Exception('A project with an application config file is required to use this provider.');
}
$this -> _appConfigFilePath = $appConfigFileResource -> getPath();
$appDirResource = $this -> _profile -> search('applicationDirectory');
$this -> _appPath = $appDirResource -> getPath();
// This define it's important in order to work with the application.ini, since it's paths are configured
// with the APPLICATION_PATH constants. So define it just before instance the Zend_Config_Ini.
define('APPLICATION_PATH', $this -> _appPath);
$this -> _config = new Zend_Config_Ini($this -> _appConfigFilePath);
$this -> _sectionName = $sectionName;
if (!isset($this -> _config -> {$this -> _sectionName})) {
throw new Zend_Tool_Project_Exception('The config does not have a ' . $this -> _sectionName . ' section.');
}
/**
* Maybe at this point doctrine isn't configured on the application.ini, do all this stuff only if
* configured
*/
if (isset($this -> _config -> {$this -> _sectionName} -> resources -> doctrine)) {
$doctrineConsoleHelperSet = $this -> _getDoctrineConsoleHelperSet();
$this -> _doctrineCli = new \Symfony\Component\Console\Application('Doctrine Command Line Interface', Doctrine\ORM\Version::VERSION);
$this -> _doctrineCli -> setCatchExceptions(TRUE);
$this -> _doctrineCli -> setHelperSet($doctrineConsoleHelperSet);
$this -> _doctrineCli -> addCommands(array(
new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(),
new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(),
new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand(),
new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(),
new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(),
new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(),
new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand()
));
return $this -> _doctrineCli;
}
}
}
/**
* Builds the Doctrine 2 Entity Manager object, and the helper set, needed by the
* Doctrine 2 Console.
* @return \Symfony\Component\Console\Helper\HelperSet $helperSet
*/
private function _getDoctrineConsoleHelperSet()
{
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Symfony', 'Doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Application\Models', dirname(APPLICATION_PATH));
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Application\Proxies', dirname(APPLICATION_PATH));
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Application\Repositories', dirname(APPLICATION_PATH));
$classLoader->register();
$entityManager = $this -> _buildDoctrineEntityManager();
return new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($entityManager->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager)
));
}
/**
* Builds the EntityManager for Doctrine 2
* @return \Doctrine\ORM\EntityManager
*/
private function _buildDoctrineEntityManager()
{
$config = new \Doctrine\ORM\Configuration();
$cache = new \Doctrine\Common\Cache\ArrayCache();
$config -> setMetadataCacheImpl($cache);
$config -> setMetadataDriverImpl(\Doctrine\ORM\Mapping\Driver\AnnotationDriver::create(array(
$this -> _config -> {$this -> _sectionName} -> resources -> doctrine -> entitiesPath
)));
$config -> setProxyDir($this -> _config -> {$this -> _sectionName} -> resources -> doctrine -> proxiesPath);
$config -> setProxyNamespace('Application\Proxies');
$config -> setAutoGenerateProxyClasses(TRUE);
return \Doctrine\ORM\EntityManager::create($this -> _config -> {$this -> _sectionName} -> resources -> doctrine -> connection -> toArray(), $config);
}
/**
* This method will be responsible for running any task on the Doctrine 2 CLI
* Usage:
*
* $this -> _run(array(
* 'command' => 'orm:generate-entities',
* 'dest-path' => '/application/models'
* ), 'production');
*
* @param array $arguments The task arguments
* @param string $sectionName
* @throws Zend_Tool_Project_Exception
*/
protected function _run(array $arguments, $sectionName = 'production')
{
if (is_null($this -> _doctrineCli)) {
$doctrineCli = $this -> _prepare($sectionName);
} else {
$doctrineCli = $this -> _doctrineCli;
}
if (!is_null($doctrineCli)) {
$consoleOutput = new \Symfony\Component\Console\Output\ConsoleOutput();
$consoleOutput -> setDecorated(TRUE);
$doctrineCli -> run(new \Symfony\Component\Console\Input\ArrayInput($arguments), $consoleOutput);
} else {
throw new Zend_Tool_Project_Exception('Doctrine has not been configured on this project. Run first, the task configure on the doctrine-config provider.');
}
}
}
Esta clase abstracta nos va a permitir ejecutar tareas del CLI de Doctrine a través de las opciones definidas en al archivo application.ini. Así de este modo definimos las siguientes tareas
Provider para configurar Doctrine 2
<?php
/**
* Archivo 'Doctrine/Zend/Tool/Project/Provider/DoctrineConfigProvider.php
*/
require_once 'Doctrine/Zend/Tool/Project/Provider/Abstract.php';
class Doctrine_Zend_Tool_Project_Provider_DoctrineConfigProvider
extends Doctrine_Zend_Tool_Project_Provider_Abstract
{
/**
* This task configures Doctrine environtment on the application.ini.
* Usage:
*
* > zf config doctrine-provider \
* 'connection[driver]=pdo_sqlite&connection[path]=APPLICATION_PATH "/../data/db/database-dev"' \
* development
*
* The available key configurations are as follows:
* · connection: an array with all the key configs needed to configure a doctrine dbal connection
* see http://www.doctrine-project.org/projects/dbal/2.0/docs/reference/configuration/en#configuration for more info
* · entitiesPath: The path where entities will reside
* · proxiesPath: The path where proxies will reside
* · repositoriesPath: The path where repositories will reside
* · cacheClass: The class used to act as a object cache (One from \Doctrine\Common\Cache)
* @param string $connectionQueryString
* @param string $sectionName
* @throws Zend_Tool_Project_Exception
*/
public function config($connectionQueryString, $sectionName = 'production')
{
$this -> _prepare($sectionName);
parse_str($connectionQueryString, $options);
if (isset($options['connection']) && isset($options['connection']['driver']) && method_exists($this, "_{$options['connection']['driver']}")) {
$method = "_{$options['connection']['driver']}";
$this -> $method($options, $sectionName);
} else {
throw new Zend_Tool_Project_Exception('Unsupported driver (' . $options['connection']['driver'] . ')');
}
}
/**
* A method to configure the connection to the database. To implement more connections,
* define for exemple "_pdo_mysql" or "_pdo_pgsql" with the same signature as this method.
* This method only validates the options of the connection. The real implementation is on
* the "_setConfig" method.
* @param array $options The options defined
* @param unknown_type $sectionName
* @throws Zend_Tool_Project_Exception
*/
protected function _pdo_sqlite(array $options, $sectionName = 'production')
{
// Build the options array
if (!isset($options['connection']['path']) && !isset($options['connection']['memory'])) {
throw new Zend_Tool_Project_Exception('Malformed connection string');
}
$this -> _setConfig($options, $sectionName);
}
/**
* This method serializes all the config options from Doctrine 2 CLI to the
* application.ini
* @param array $options The defined options
* @param string $sectionName
*/
protected function _setConfig(array $options, $sectionName = 'production')
{
$doctrineItem = array(
'resources' => array(
'doctrine' => array()
)
);
$doctrineItem['resources']['doctrine']['entitiesPath'] = isset($options['entitiesPath']) ? $options['entitiesPath'] : 'APPLICATION_PATH "/models"';
$doctrineItem['resources']['doctrine']['proxiesPath'] = isset($options['proxiesPath']) ? $options['proxiesPath'] : 'APPLICATION_PATH "/proxies"';
$doctrineItem['resources']['doctrine']['repositoriesPath'] = isset($options['repositoriesPath']) ? $options['repositoriesPath'] : 'APPLICATION_PATH "/repositories"';
$doctrineItem['resources']['doctrine']['cacheClass'] = isset($options['cacheClass']) ? $options['cacheClass'] : 'Doctrine\Common\Cache\ApcCache';
$doctrineItem['resources']['doctrine']['connection'] = $options['connection'];
$appConfigFileResource = $this -> _profile -> search('applicationConfigFile');
$appConfigFileResource -> addItem($doctrineItem, $sectionName, NULL);
$appConfigFileResource -> create();
$this -> _registry -> getResponse() -> appendContent("A new Doctrine 2 configuration for the section $sectionName, has been stablished.");
}
}
Provider para la generación de Entities, Proxies y Repositories
<?php
/**
* Archivo /Doctrine/Zend/Tool/Project/Provider/DoctrineOrmProvider.php
*/
require_once 'Doctrine/Zend/Tool/Project/Provider/Abstract.php';
class Doctrine_Zend_Tool_Project_Provider_DoctrineOrmProvider
extends Doctrine_Zend_Tool_Project_Provider_Abstract
{
/**
* A task to generate the entity repositories on a defined path
* @param string $destPath
* @param string $sectionName
*/
public function generateRepositories($destPath, $sectionName = 'production')
{
$this -> _run(array(
'command' => 'orm:generate-repositories',
'dest-path' => $destPath
));
}
/**
* A task to generate proxies on a defined path
* @param string $destPath
* @param string $sectionName
*/
public function generateProxies($destPath, $sectionName = 'production')
{
$this -> _run(array(
'command' => 'orm:generate-proxies',
'dest-path' => $destPath
));
}
/**
* A task to generate entities on a defined path
* @param string $destPath
* @param string $sectionName
*/
public function generateEntities($destPath, $sectionName = 'production')
{
$this -> _run(array(
'command' => 'orm:generate-entities',
'dest-path' => $destPath
));
}
}
Concluyendo
Llegados hasta aquí sólo falta notificar a Zend Tool de la existencia de estas tareas en nuestro entorno CLI. Para ello, cómo he dicho antes vamos a usar el archivo “.zf.ini” situado en nuestra carpeta de usuario del sistema. Editamos dicho archivo y le modificamos el include_path para que apunte también al directorio library de nuestro proyecto y además le añadimos las clases que acabamos de crear.
php.include_path = ".:/path/to/my/zend/framework/project/library:/usr/local/zend/share/pear" basicloader.classes.0 = "Doctrine_Zend_Tool_Project_Provider_DoctrineConfigProvider" basicloader.classes.1 = "Doctrine_Zend_Tool_Project_Provider_DoctrineOrmProvider"
Con esto deberíamos ser capaces de ejecutar CLI tasks de Doctrine 2, a través de la Zend Tool
Prestahop, virtudes y “desvirtudes”
by Christian Soronellas on noviembre 30, 2010
Mientras acabo de preparar la siguiente entrada de ZF y Doctrine 2, he tenido el gusto de leer un interesante análisis sobre Prestashop destacando sus pros y sus contras: http://juanmacias.net/archives/805.
Zend Framework y Doctrine 2
by Christian Soronellas on noviembre 20, 2010
El matrimonio perfecto?
De los qué para mí está mejor posicionado dentro de la escena de frameworks disponibles para PHP, es sin dudar Zend Framework. Puede usarse tanto cómo stack de componentes, cómo stack MVC completo aprovechando las bondades del resto de componentes.
La tendencia, clara. Sigue la linea marcada por Ruby on Rails y por ende por el resto de frameworks. De modo qué la mayor parte de las convenciones ya definidas por otros, prevalecen en este. Y esto implica un mejor y más fácil aprendizaje por parte de todos aquellos qué quieran iniciarse. De este ORM se puede decir que implementa principalmente los patrones Table Data Gateway y en Row Data Gateway.
Para la interacción con el modelo de dominio en aplicaciones web que requieran una capa de dominio muy grande, intento usar el proyecto Doctrine 2 o algún ORM que implemente DataMapper. Doctrine 2 és un O/RM, basado en Data Mapper qué realmente promete mucho. Características cómo la introspección de bases de datos para generar schemas enteros, modelos basados en anotaciones sobre el código (@Anotations == POPOs — Plain Old PHP Objects), o el propio diseño basado en Data Mapper (y en el mítico proyecto Hibernate de Java) lo hacen una opción de lo más apetecible. Pero, y ya yendo al grano, ahora viene la gran pregunta …
Qué posibilidades de integración existen entre los dos proyectos?
Pues en realidad, todas. Los dos son proyectos muy potentes totalmente integrables entre sí. Y en esta entrada voy a describir el proceso minuciosamente. Este pretende ser el primero de varios artículos relacionados con el desarrollo bajo Zend Framework. Así qué, habrá más.
Entendiendo el funcionamiento de Zend Framework
Para empezar, Zend Framework posee un layout de directorios predeterminado. Esto es un directorio dónde se almacenan los recursos que necesita la aplicación, un directorio dónde almacenar librerías externas, otro cómo directorio público y otro destinado al almacén de los tests de nuestra aplicación.
Siguiendo el manual de Zend Framework, es realmente fácil crear un nuevo proyecto. Así qué recomiendo seguir el manual si alguien no sabe cómo iniciarse en el desarrollo de ZF.
Una de las características interesantes de Zend Framework es que permite al desarrollador intervenir en la fase de Bootstrap, para “inyectar” los componentes que se necesite. Y lo permite hacer de una forma qué considero muy elegante: a través del componente Zend_Application_Resource_ResourceAbstract. Esta classe nos va a permitir definir una interface entre ZF y Doctrine2, de tal modo que nos permita integrar todo el modelo de dominio generado por Doctrine 2 en el contexto de Zend.
El compromiso
/**
* Archivo ubicado en library/Doctrine/Zend/Doctrine.php
*/
require_once 'Zend/Application/Resource/ResourceAbstract.php';
require_once 'Doctrine/Common/ClassLoader.php';
class Doctrine_Zend_Doctrine
extends Zend_Application_Resource_ResourceAbstract
{
public function init()
{
$options = $this -> getOptions();
// Doctrine (use include_path)
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
$classLoader -> register();
// Entities
$classLoader = new \Doctrine\Common\ClassLoader(
'Application\Models',
dirname(APPLICATION_PATH) . DIRECTORY_SEPARATOR . 'library'
);
$classLoader -> register();
// Proxies
$classLoader = new \Doctrine\Common\ClassLoader(
'Application\Models\Proxies',
dirname(APPLICATION_PATH) . DIRECTORY_SEPARATOR . 'library'
);
$classLoader -> register();
// Now configure doctrine
if ('development' == APPLICATION_ENV) {
$cache = new \Doctrine\Common\Cache\ArrayCache();
} else {
$cache = new \Doctrine\Common\Cache\ApcCache();
}
$config = new Configuration();
$config -> setMetadataCacheImpl($cache);
$driverImpl = $config -> newDefaultAnnotationDriver($options['entitiesPath']);
$config -> setMetadataDriverImpl($driverImpl);
$config -> setQueryCacheImpl($cache);
$config -> setProxyDir($options['proxiesPath']);
$config -> setProxyNamespace('Application\Models\Proxies');
$config -> setAutoGenerateProxyClasses(('development' == APPLICATION_PATH));
$em = EntityManager::create(
$this -> _buildConnectionOptions($options),
$config
);
// Once we have the EntityManager ready, add it to the registry
Zend_Registry::set('em', $em);
// end
return $em;
}
/**
* A method to build the connection options, for a Doctrine
* EntityManager/Connection. Sure, we can find a more elegant solution to build
* the connection options. A builder class could be applied. Sure you can with
* some refactor!
* TODO: refactor to build some other, more elegant, solution to build the conn
* ection object.
* @param Array $options The options array defined on the application.ini file
* @return Array
*/
protected function _buildConnectionOptions($options)
{
$connectionSpec = array(
'pdo_sqlite' => array('user', 'password', 'path', 'memory'),
'pdo_mysql' => array(
'user', 'password', 'host', 'port', 'dbname', 'unix_socket'
),
'pdo_pgsql' => array('user', 'password', 'host', 'port', 'dbname'),
'pdo_oci' => array(
'user', 'password', 'host', 'port', 'dbname', 'charset'
)
);
$connection = array(
'driver' => $options['driver']
);
foreach ($connectionSpec[$options['driver']] as $driverOption) {
if (isset($options[$driverOption]) && !is_null($driverOption)) {
$connection[$driverOption] = $options[$driverOption];
}
}
if (isset($options['driverOptions'])
&& !is_null($options['driverOptions'])) {
$connection['driverOptions'] = $options['driverOptions'];
}
return $connection;
}
}
Con esto en nuestro include_path, ya no debe caber duda … vamos a pedir la mano
Pidiendo la mano
Con esa clase en el entorno de ejecución de ZF, ya se puede configurar a través del application.ini.
; ... [production] ; Doctrine 2 Config pluginPaths.Doctrine_Zend_ = "Doctrine/Zend/" resources.doctrine.entitiesPath = APPLICATION_PATH "/../library/models" resources.doctrine.proxiesPath = APPLICATION_PATH "/../library/models/proxies" resources.doctrine.driver = pdo_sqlite resources.doctrine.path = APPLICATION_PATH "/../data/db/database-dev.db"
El siguiente paso seria generar las entidades y los proxies necesarios. Esto se puede hacer a través de la herramienta de consola de Doctrine 2 (tema del próximo artículo: integración de las herramientas de consola de Doctrine 2 con la ZF tool). Y ya por último …
La Boda
Sobran palabras, el código habla por si sólo.
<?php
class SectionController extends Zend_Controller_Action
{
public function init()
{
/* Initialize action controller here */
}
public function indexAction()
{
$sections = Zend_Registry::get('em')
-> createQuery('select s from \Application\Models\Section s')
-> getResult();
$this -> view -> sections = $sections;
}
}
Ya sólo faltaria ver volar por el aire los granos de arroz y la típica canción de boda. Cómo dije en algún post anterior: POESíA! PURA POESíA!
PD: Para quién le interese, el código-ejemplo de esta entrada se puede encontrar en mi cuenta de Github.
PD 2: Debo pedir disculpas a quién se haya molestado a descargar el código de ejemplo y a probarlo. Debo admitir qué había presentes algunos errores que impedían la correcta ejecución de la applicación de ejemplo. Ya he podido solucionarlos, con lo qué ahora debería poder funcionar sin problemas. Un saludo y discuplad las molestias.



Inglés
Español