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.
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 Propel.
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.
note
Algunas herramientas te permiten crear una base de datos gráficamente
(por ejemplo Fabforce's Dbdesigner)
y generar directamente un schema.xml
(con DB Designer 4 TO Propel Schema
Converter).
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/schema.yml
:
# config/schema.yml propel: jobeet_category: id: ~ name: { type: varchar(255), required: true, index: unique } jobeet_job: id: ~ category_id: { type: integer, foreignTable: jobeet_category, foreignReference: id, required: true, onDelete: CASCADE } type: { type: varchar(255) } company: { type: varchar(255), required: true } logo: { type: varchar(255) } url: { type: varchar(255) } position: { type: varchar(255), required: true } location: { type: varchar(255), required: true } description: { type: longvarchar, required: true } how_to_apply: { type: longvarchar, required: true } token: { type: varchar(255), required: true, index: unique } is_public: { type: boolean, required: true, default: 1 } is_activated: { type: boolean, required: true, default: 0 } email: { type: varchar(255), required: true } expires_at: { type: timestamp, required: true } created_at: ~ updated_at: ~ jobeet_affiliate: id: ~ url: { type: varchar(255), required: true } email: { type: varchar(255), required: true, index: unique } token: { type: varchar(255), required: true } is_active: { type: boolean, required: true, default: 0 } created_at: ~ jobeet_category_affiliate: category_id: { type: integer, foreignTable: jobeet_category, foreignReference: id, required: true, primaryKey: true, onDelete: cascade } affiliate_id: { type: integer, foreignTable: jobeet_affiliate, foreignReference: id, required: true, primaryKey: true, onDelete: cascade }
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 propel:build-schema
:
$ php symfony propel: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
,tinyint
,smallint
,integer
,bigint
,double
,float
,real
,decimal
,char
,varchar(size)
,longvarchar
,date
,time
,timestamp
,blob
, yclob
)required
: Estrue
si deseas que la columna sea obligadoriaindex
: Estrue
si deseas crear un índice para la columna ounique
si quieres que un índice único se cree en la columna.primaryKey
: Define una columna como la clave primaria para la tabla.foreignTable
,foreignReference
: Define una columna para ser la clave foranea a otra tabla.
Para las columnas establecidas a ~
, el cual significa null
en YAML (id
, created_at
, y updated_at
), Symfony adivinará la mejor configuración (primary key para id
y timestamp para
created_at
y updated_at
).
note
El atributo onDelete
define el comportamiento ON DELETE
para claves foráneas,
y Propel da soporte para CASCADE
, SETNULL
, y RESTRICT
. Por ejemplo, cuando
un registro job
es borrado, todos los registros jobeet_category_affiliate
relacionados serán automáticamente eliminados de la base de datos o por Propel si el motor de
la base de datos no soporta esta funcionalidad.
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 "mysql:host=localhost;dbname=jobeet" root mYsEcret
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 Propel para generar los comandos SQL necesarios para crear tablas de la base de datos:
$ php symfony propel:build-sql
La tarea propel: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/lib.model.schema.sql CREATE TABLE `jobeet_category` ( `id` INTEGER NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `jobeet_category_U_1` (`name`) )Type=InnoDB;
Para realmente crear las tablas en la base de datos, necesitas ejecutar la tarea propel:insert-sql
:
$ php symfony propel:insert-sql
Como la tarea elimina las tablas existentes antes de re-crearlas, necesitas confirmar la operación. También puedes añadir la opción --no-confirmation
para saltearte la pregunta, lo cual es útil si deseas ejecutar la tarea desde un proceso no interactivo:
$ php symfony propel:insert-sql --no-confirmation
tip
Como con cualquier herramienta de línea de comandos, las tareas symfony pueden tener argumentos y opciones.
Cada tarea viene con un mensaje de ayuda que se puede mostrar ejecutando
la tarea help
:
$ php symfony help propel:insert-sql
El mensaje de ayuda lista de todos los posibles argumentos y opciones, da los valores por defecto para cada uno de ellos, y proporciona algunos ejemplos útiles.
El ORM también genera las clases PHP que mapea los registros de la tabla con los objetos:
$ php symfony propel:build-model
La tarea propel:build-model
genera archivos PHP en el directorio lib/model/
que se pueden utilizar para interactuar con la base de datos.
Navegando por los archivos generados, probablemente habrás notado que Propel 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 ejecutaspropel:build-model
, esta clase es sobreescrita, por lo que todas las personalizaciones se deben hacer en la claseJobeetJob
.JobeetJobPeer
: La clase define los métodos estáticos que mayormente devuelve colecciones de objetosJobeetJob
. La clase está vacía por defecto.BaseJobeetJobPeer
: La clase padre deJobeetJobPeer
. Cada vez que ejecutaspropel:build-model
, esta clase es sobreescrita, por lo que todas las personalizaciones se deben hacer en la claseJobeetJobPeer
.
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 propel: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 propel: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 propel: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 propel:data-load
para cargarlos en la base de datos.
Primero, crea los siguientes archivos de datos:
# data/fixtures/010_categories.yml JobeetCategory: design: { name: Design } programming: { name: Programming } manager: { name: Manager } administrator: { name: Administrator } # data/fixtures/020_jobs.yml JobeetJob: job_sensio_labs: category_id: 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: 2010-10-10 job_extreme_sensio: category_id: 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: 2010-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.
tip
Nota los números de prefijo del nombre de cada archivo. Es una manera simple de controlar el orden de carga de datos. Más adelante en el proyecto, si es necesario insertar algunos nuevos archivos, será fácil como tengamos algunos números libres entre los existentes.
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 Propel 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 propel:data-load
:
$ php symfony propel:data-load
tip
La tarea propel:build-all-load
es un atajo para la tarea propel:build-all
seguida de la tarea propel:data-load
.
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 propel:generate-module --with-show --non-verbose-templates frontend job JobeetJob
La tarea propel: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, tendrás una excepción porque Symfony necesita de un texto que represente una categoría. Un objeto de PHP se representa definiendo el método mágico PHP __toString()
. El texto de representación de una categoría
debería definirse en la clase del modelo JobeetCategory
:
// lib/model/JobeetCategory.php class JobeetCategory extends BaseJobeetCategory { public function __toString() { return $this->getName(); } }
Ahora cada vez que necesites un texto que represente a una categoría, se llamará al
método __toString()
el cual devuelve el nombre de la categoría ya que necesitaremos un texto para todas las clases del modelo en un punto u otro, vamos a definie un método __toString()
para cada clase del modelo:
// lib/model/JobeetJob.php class JobeetJob extends BaseJobeetJob { public function __toString() { return sprintf('%s at %s (%s)', $this->getPosition(), $this->getCompany(), $this->getLocation()); } } // lib/model/JobeetAffiliate.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/propel/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.