Aquellos de ustedes locos por abrir el editor de texto y hacer algo de PHP estarán encantados de saber que el tutorial de hoy nos introduce algo en el desarrollo. Vamos a definir el modelo de datos Jobeet, utilizaremos un ORM para interactuar con la base de datos, y construiremos el primer módulo de la aplicación. Pero como Symfony hace una gran parte del trabajo por nosotros, vamos a tener un módulo web totalmente funcional sin tener que escribir mucho código PHP.
Activando sfDoctrinePlugin
Si estás leyendo esto, entonces has decidido completar el tutorial de Jobeet con el ORM Doctrine en lugar de Propel. Esto es simple ya que todo lo que necesitas hacer primero es habilitar sfDoctrinePlugin
y deshabilitar sfPropelPlugin
. Esto puede ser hecho con el siguiente código en tu config/ProjectConfiguration.class.php
.
public function setup() { $this->enablePlugins(array('sfDoctrinePlugin')); $this->disablePlugins(array('sfPropelPlugin')); }
Si prefieres tener todos los plugins habilitados por defecto, puedes hacer lo siguiente:
public function setup() { $this->enableAllPluginsExcept(array('sfPropelPlugin', 'sfCompat10Plugin')); }
note
Despues de este cambio tendrás un error hasta que configures el
archivo config/databases.yml
en un paso posterior a la utilización de sfDoctrineDatabase
.
Después de realizar estos cambios, debes asegurarte de borrar el cache.
$ php symfony cc
Como veremos más adelante en el tutorial, cada plugin puede tener recursos web (imágenes,
hojas de estilo, y javascripts). Cuando se instala o activa un nuevo plugin, debemos instalarlos vía la tarea plugin:publish-assets
:
$ php symfony plugin:publish-assets
También, vamos a necesitamos quitar el directorio web/sfPropelPlugin
:
$ rm web/sfPropelPlugin
El Modelo Relacional
Los casos de uso que escribimos ayer describen los principales objetos de nuestro proyecto: puestos de trabajo, afiliados, y las categorías. Aquí está el correspondiente diagrama de entidad relación:
Además de las columnas descritas en los casos de uso, también hemos añadido un campo created_at
a algunas tablas. Symfony reconoce estos campos y establece el valor de la hora actual de sistema cuando un registro es creado. Esto es lo mismo para el campo updated_at
: su valor se establece siempre a la hora del sistema en que el registro se actualiza.
El Esquema
Para almacenar los puestos de trabajo, los afiliados, y las categorías, es evidente que necesitamos una base de datos relacional.
Pero como Symfony es un Framework Orientado a Objetos, nos gustaría manipular los objetos cada vez que podamos. Por ejemplo, en lugar de escribir sentencias SQL para recuperar los registros de la base de datos, preferimos más usar los objetos.
La información de la base de datos relacional debe ser mapeada a un modelo de objetos. Esto se puede hacer con una herramienta ORM, o Mapeador, y afortunadamente, Symfony tiene incluido dos de ellas: Propel y Doctrine. En este tutorial, usaremos Doctrine.
El ORM necesita una descripción de las tablas y sus relaciones para crear las clases relacionadas. Hay dos maneras de crear este esquema de descripción: ingeniería reversa de una base de datos existente o creándolo a mano.
Como la base de datos no existe todavía, y como queremos mantener la base de datos Jobeet agnóstica, vamos a crear el archivo de esquema a mano editando el archivo vacío config/doctrine/schema.yml
:
tip
Necesitas crear manualmente el directorio config/doctrine/
en tu
proyecto ya que éste no existe aún:
$ mkdir config/doctrine
# config/doctrine/schema.yml --- JobeetCategory: actAs: { Timestampable: ~ } columns: name: { type: string(255), notnull: true, unique: true } JobeetJob: actAs: { Timestampable: ~ } columns: category_id: { type: integer, notnull: true } type: { type: string(255) } company: { type: string(255), notnull: true } logo: { type: string(255) } url: { type: string(255) } position: { type: string(255), notnull: true } location: { type: string(255), notnull: true } description: { type: string(4000), notnull: true } how_to_apply: { type: string(4000), notnull: true } token: { type: string(255), notnull: true, unique: true } is_public: { type: boolean, notnull: true, default: 1 } is_activated: { type: boolean, notnull: true, default: 0 } email: { type: string(255), notnull: true } expires_at: { type: timestamp, notnull: true } relations: JobeetCategory: { onDelete: CASCADE, local: category_id, foreign: id, foreignAlias: JobeetJobs } JobeetAffiliate: actAs: { Timestampable: ~ } columns: url: { type: string(255), notnull: true } email: { type: string(255), notnull: true, unique: true } token: { type: string(255), notnull: true } is_active: { type: boolean, notnull: true, default: 0 } relations: JobeetCategories: class: JobeetCategory refClass: JobeetCategoryAffiliate local: affiliate_id foreign: category_id foreignAlias: JobeetAffiliates JobeetCategoryAffiliate: columns: category_id: { type: integer, primary: true } affiliate_id: { type: integer, primary: true } relations: JobeetCategory: { onDelete: CASCADE, local: category_id, foreign: id } JobeetAffiliate: { onDelete: CASCADE, local: affiliate_id, foreign: id }
tip
Si decidiste crear las tablas escribiendo las sentencias SQL, puedes
generar el archivo de configuración del esquema correspondiente schema.yml
,
ejecutando la tarea doctrine:build-schema
:
$ php symfony doctrine:build-schema
La anterior tarea requiere que tengas la base de datos configurada en el databases.yml
.
Te mostraremos como configurar la base de datos en un paso posterior. Si tratas de ejecutar esta
tarea ahora no funcionará ya que no sabe que base de datos contruir para el esquema.
El esquema es la traducción directa de un diagrama de entidad relación en formato YAML.
El archivo schema.yml
contiene la descripción de todas las tablas y sus columnas.
Cada columna se describe con la siguiente información:
type
: El tipo de columna (boolean
,integer
,float
,decimal
,string
,array
,object
,blob
,clob
,timestamp
,time
,date
,enum
,gzip
)notnull
: Estrue
si deseas que la columna sea obligadoriaunique
: Estrue
si deseas crear un índice único para la columna.
note
El atributo onDelete
define el comportamiento ON DELETE
para claves foráneas,
y Doctrine da soporte para CASCADE
, SETNULL
, y RESTRICT
. Por ejemplo, cuando
un registro de job
es borrado, todos los registros jobeet_category_affiliate
relacionados serán automáticamente eliminados de la base de datos
tip
Nota del Traductor Si bien no es probable, es posible que se quiera eliminar una categoría completa del sistema.
El campo category_id de la entidad JobeetJob, tiene al final añadida la opción onDelete.
La misma está establecida como CASCADE
, para determinar que cuando una categoría sea
eliminada, tambien se eliminen los registros job
(los puestos de trabajo).
Esto se debe a que los registros job
tienen establecido como campo obligatorio a la categoría
asociada (a la que pertenece), por lo que no sería correcto dejar huerfano
a un registro,
ni establecer éste valor a null
.
La Base De Datos
El framework Symfony soporta todas las Base De Datos soportadas por PDO (MySQL, PostgreSQL, SQLite, Oracle, MSSQL, ...). PDO es la capa de abstracción de base de datos que viene con PHP.
Vamos a usar MySQL para este tutorial:
$ mysqladmin -uroot -p create jobeet Enter password: mYsEcret ## La clave se mostrará como ********
note
Eres libre de elegir otro motor de base de datos si lo deseas. No será difícil adaptar el código que vamos a escribir ya que vamos a utilizar el ORM quien será quien escriba el SQL por nosotros.
Tenemos que decirle a Symfony que base de datos usar para el proyecto Jobeet:
$ php symfony configure:database --name=doctrine --class=sfDoctrineDatabase "mysql:host=localhost;dbname=jobeet" root mYsEcret
note
Después de configurar la conexión de doctrine a la base de datos, necesitarás manualmente
editar config/databases.yml
y quitar cualquier conexión que haga referencia a propel.
La tarea configure:database
emplea tres argumentos: el
PDO DSN, el nombre de usuario, y
la clave de acceso a la base de datos. Si no tienes ninguna contraseña de la base
en el servidor de desarrollo, basta con omitir el tercer argumento.
note
La tarea configure:database
almacena la configuración de la base de datos en el archivo de configuración config/databases.yml
. En lugar de utilizar la tarea, puedes editar este archivo manualmente.
caution
Pasar la clave de la base de datos por linea de comandos es conveniente pero
inseguro.
Dependiendo de quienes tiene acceso a tu entorno, podría ser mejor
editar el config/databases.yml
para cambiar la clave. Desde luego, para
mantener la clave a salvo, el acceso al archivo de configuración debería
también ser restringido.
El ORM
Gracias al archivo de descripción de la base de datos schema.yml
, podemos utilizar algunas de las tareas de Doctrine para generar los comandos SQL necesarios para crear tablas de la base de datos:
Primero para generar el SQL debes contruir el modelo apartir de tus archivos esquema.
$ php symfony doctrine:build-model
Ahora que tus modelos existen puedes generar e insertar el SQL.
$ php symfony doctrine:build-sql
La tarea doctrine:build-sql
genera comandos SQL en el directorio data/sql/
,
optimizado para el motor de base de datos que hemos configurado:
# snippet from data/sql/schema.sql CREATE TABLE jobeet_category (id BIGINT AUTO_INCREMENT, name VARCHAR(255) NOT NULL COMMENT 'test', created_at DATETIME, updated_at DATETIME, slug VARCHAR(255), UNIQUE INDEX sluggable_idx (slug), PRIMARY KEY(id)) ENGINE = INNODB;
Para realmente crear las tablas en la base de datos, necesitas ejecutar la tarea doctrine:insert-sql
:
$ php symfony doctrine:insert-sql
Navegando por los archivos generados, probablemente habrás notado que Doctrine genera cuatro clases por tabla. Para la tabla jobeet_job
:
JobeetJob
: Un objeto de esta clase representa un único registro de la tablajobeet_job
. La clase está vacía por defecto.BaseJobeetJob
: La clase padre deJobeetJob
. Cada vez que ejecutasdoctrine:build-model
, esta clase es sobreescrita, por lo que todas las personalizaciones se deben hacer en la claseJobeetJob
.JobeetJobTable
: La clase define los métodos que mayormente devuelve colecciones de objetosJobeetJob
. La clase está vacía por defecto.
Los valores de las columnas de un registro se pueden manipular con el modelo de objetos mediante el uso de algunos métodos get*()
y métodos set*()
:
$job = new JobeetJob(); $job->setPosition('Web developer'); $job->save(); echo $job->getPosition(); $job->delete();
También puedes definir claves foráneas directamente por la vinculación de objetos:
$category = new JobeetCategory(); $category->setName('Programming'); $job = new JobeetJob(); $job->setCategory($category);
La tarea doctrine:build-all
es un acceso directo para las tareas que han ejecutado en esta sección y algunas más. Por lo tanto, ejecuta esta tarea ahora para generar formularios y validadores para el modelo de clases de Jobeet:
$ php symfony doctrine:build-all --no-confirmation
Verás los validadores en acción al final del día y los formularios se explicarán en gran detalle en el día 10.
Como verás más adelante, Symfony automáticamente carga las clases PHP por nosotros, lo que significa que nunca más necesitarás utilizar un require
en tu código. Esta es una de las numerosas cosas que automatiza Symfony para el desarrollador, pero hay un inconveniente:
siempre que agreges una nueva clase, necesitarás borrar la caché de Symfony. Como la tarea doctrine:build-model
ha creado un montón de nuevas clases, vamos a borrar la caché:
$ php symfony cache:clear
tip
Una tarea symfony está hecha de un nombre y un espacio de nombres. Cada una puede ser más breve
en tanto no haya ambigüedad con otras tareas. Por lo tanto, los siguientes comandos
son equivalentes a cache:clear
:
$ php symfony cache:cl $ php symfony ca:c
Como la tarea cache:clear
es muy usada, ésta tiene una abreviación aún más corta:
$ php symfony cc
Los Datos Iniciales
Los tablas se han creado en la base de datos, pero no hay datos en ellas. Para cualquier aplicación web, hay tres tipos de datos:
Datos Iniciales: Los datos iniciales son necesarios para que la aplicación funcione. Por ejemplo, Jobeet necesita algunas categorías iniciales. Si no, nadie será capaz de envíar un puesto de trabajo. También necesitamos un administrador de usuarios para poder acceder al backend.
Datos de Prueba: Los datos de prueba son necesarios para que la aplicación sea probada. Como desarrollador, escribes pruebas para asegurarte que Jobeet se comporta como se describe en los casos de uso, y la mejor manera es escribir pruebas automáticas. Por lo tanto, cada vez que ejecutes tus pruebas, necesitas una base de datos limpia con algunos datos nuevos de prueba en ella.
Los Datos del Usuario: Los datos del usuario son creados por los usuarios durante la vida normal de la aplicación.
Cada vez que Symfony crea las tablas en la base de datos, todos los datos se pierden.
Para rellenar la base de datos con algunos los datos iniciales, podriamos crear un script PHP,
o ejecutar sentencias SQL con algun programa mysql
. Pero como la necesidad es bastante común, hay una mejor manera con Symfony: crear archivos YAML en el directorio data/fixtures/
y usar la tarea doctrine:data-load
para cargarlos en la base de datos.
Primero, crea los siguientes archivos de datos:
# data/fixtures/categories.yml JobeetCategory: design: name: Design programming: name: Programming manager: name: Manager administrator: name: Administrator # data/fixtures/jobs.yml JobeetJob: job_sensio_labs: JobeetCategory: programming type: full-time company: Sensio Labs logo: sensio-labs.gif url: http://www.sensiolabs.com/ position: Web Developer location: Paris, France description: | You've already developed websites with symfony and you want to work with Open-Source technologies. You have a minimum of 3 years experience in web development with PHP or Java and you wish to participate to development of Web 2.0 sites using the best frameworks available. how_to_apply: | Send your resume to fabien.potencier [at] sensio.com is_public: true is_activated: true token: job_sensio_labs email: job@example.com expires_at: '2008-10-10' job_extreme_sensio: JobeetCategory: design type: part-time company: Extreme Sensio logo: extreme-sensio.gif url: http://www.extreme-sensio.com/ position: Web Designer location: Paris, France description: | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in. Voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. how_to_apply: | Send your resume to fabien.potencier [at] sensio.com is_public: true is_activated: true token: job_extreme_sensio email: job@example.com expires_at: '2008-10-10'
note
El archivo de datos de puestos de trabajo hace referencia a dos imágenes. Puedes descargarlas de
(/get/jobeet/sensio-labs.gif
,
/get/jobeet/extreme-sensio.gif
) y colocarlas
en el directorio uploads/jobs/
.
Esta escrito en YAML, y define el modelo de objetos, etiquetados con un nombre único
(por ejemplo, hemos definido dos puestos de trabajo etiquetados con job_sensio_labs
y job_extreme_sensio
). Estas etiquetas son de gran utilidad para vincular objetos relacionados sin tener que definir claves primarias (que a menudo son auto-incrementales y no se pueden establecer).
Por ejemplo, la categoría de job_sensio_labs
es programming
, que es la etiqueta dada a la categoría 'Programming'.
tip
En un archivo YAML, cuado una cadena tiene saltos de linea (como la
columna description
en el archivo de datos de puestos de trabajo), puedes usar la barra vertical (|
) para
indicar que la cadena aparecerá en varias lineas.
A través de un archivo de datos se puede tener objetos de uno o varios modelos, hemos decidido crear un archivo por cada modelo para los datos de Jobeet.
note
Propel require que los archivos de datos tengan un prefijo númerico que determine
el orden en el cual los archivos serán cargados. Con Doctrine esto no es necesario
ya que todos los archivos serán cargados y guardados en el correcto orden para asegurar
que las claves foraneas sean adecuadas.
En un archivo de datos, no es necesario definir todos los valores de las columnas. Si no que, Symfony utilizará el valor predeterminado definido en el esquema de base de datos. Y como
Symfony usa Doctrine para cargar los datos en la base de datos, todos los comportamientos incorporados (como el seteo automático a created_at
o updated_at
), o los comportamientos personalizados que puedes haber agregado al modelo de las clases son activados.
La carga de los datos iniciales en la base de datos es tan simple como ejecutar la tarea doctrine:data-load
:
$ php symfony doctrine:data-load
tip
La tarea doctrine:build-all-reload
es un atajo para la tarea doctrine:build-all
seguida de la tarea doctrine:data-load
.
Ejecuta la tarea doctrine:build-all-reload
para asegurarte que todo es generado desde tu esquema. Esto generará tus formularios, filtros, modelos, vaciará tu base de datos y la re-creará de nuevo con todas las tablas.
$ php symfony doctrine:build-all-reload
Miralo en acción en el Navegador
Hemos utilizado la interfaz de línea de comandos mucho, pero eso no es realmente emocionante, especialmente para un proyecto web. Ahora tenemos todo lo que necesitamos para crear páginas Web que interactúan con la base de datos.
Vamos a ver cómo mostrar la lista de puestos de trabajo, cómo editar un trabajo, y cómo eliminar un puesto de trabajo. Como se explicó durante el día 1, un proyecto symfony se hace de las aplicaciones. Cada aplicación está dividida en módulos. Un módulo es un conjunto de código PHP auto-contenido que representa una característica de la aplicación (el módulo API, por ejemplo), o un conjunto de manipulaciones que el usuario puede hacer sobre un objeto del modelo (un módulo job, por ejemplo).
Symfony es capaz de generar automáticamente un módulo para un determinado modelo que proporciona las características básicas de manipulación:
$ php symfony doctrine:generate-module --with-show --non-verbose-templates frontend job JobeetJob
La tarea doctrine:generate-module
genera módulo job
en la
aplicación frontend
para el modelo JobeetJob
. Como con la mayoría de las tareas symfony, algunos archivos y directorios se han creado para ti bajo el directorio apps/frontend/modules/job/
:
Directorio | Descripción |
---|---|
actions/ |
Acciones del módulo |
templates/ |
Plantillas del módulo |
El archivo actions/actions.class.php
define todas las acciones disponibles para el módulo job
:
Nombre de la acción | Descripción |
---|---|
index |
Muestra los registros de la tabla |
show |
Muestra los campos de un registro |
new |
Muestra un formulario para crear un nuevo registro |
create |
Crea un nuevo registro |
edit |
Muestra un formulario para crear editar un registro existente |
update |
Actualiza un registro con los valores que envió el usuario |
delete |
Borra un registro de la tabla |
Ahora puedes probar el módulo job en un navegador:
http://jobeet.localhost/frontend_dev.php/job
Si intentas editar un puesto de trabajo, te darás cuenta que el combo Category id tiene una lista
de todos los nombres de las categorías. El valor de cada opción esta dado por el método __toString()
.
Doctrine tratará de dar un método base __toString()
adivinandolo de una
columna descriptiva de nombre como ser, title
, name
, subject
, etc. Si quieres algo distinto entonces necesitas agregar tus propios métodos __toString()
como se ve más abajo. El modelo JobeetCategory
esta listo para adivinar el método __toString()
usando la columna name
de la tabla jobeet_category
.
// lib/model/doctrine/JobeetJob.class.php class JobeetJob extends BaseJobeetJob { public function __toString() { return sprintf('%s at %s (%s)', $this->getPosition(), $this->getCompany(), $this->getLocation()); } } // lib/model/doctrine/JobeetAffiliate.class.php class JobeetAffiliate extends BaseJobeetAffiliate { public function __toString() { return $this->getUrl(); } }
Ahora puedes editar y crear puestos de trabajo. Trata de dejar un campo obligatorio en blanco, o tratar de dar una fecha no válida. Así es, Symfony ha creado básicas reglas de validación analizando el esquema de base de datos.
Nos vemos mañana
Eso es todo por hoy. Te lo advertí en la introducción. Hoy, apenas hemos escrito el código PHP, pero tenemos un módulo web para el modelo job, listo para ser personalizado. ¡Recuerda, menos código PHP también significa menos errores!
Si todavía te queda algo de energía, no dudes en leer el código generado para el módulo y el modelo y tratar de entender cómo funciona. Si no, no te preocupes y duerme bien, mañana, vamos a hablar de uno de los paradigmas más utilizados en los frameworks web, el Patrón de Diseño MVC.
Como cualquier otro día, el código escrito hoy está disponible en el repositorio SVN de Jobeet
Revisa la etiqueta release_day_03
:
$ svn co http://svn.jobeet.org/doctrine/tags/release_day_03/ jobeet/
Feedback
tip
Este capítulo ha sido traducido por Roberto Germán Puentes Díaz. Si encuentras algún error que deseas corregir o realizar algún comentario, no dudes en enviarlo por correo a puentesdiaz [arroba] gmail.com
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.