Entradas etiquetadas con symfony
Symfony: Enrutamiento (II)
0Por defecto Symfony no muestra el archivo que hace de controlador (index.php, frontend_dev.php, etc) para cambiarlo vamos al archivo settings.yml aquí podemos indicar que no se muestre el nombre del controlador de la siguiente manera:
- prod:
- .settings:
- no_script_name: off
Hiperenlaces, botones y formularios
- / Opciones adicionales como array asociativo
- 'class' => 'miclasecss',
- 'target' => '_blank'
- )) ?>
-
- // Opciones adicionales como cadena de texto (producen el mismo resultado)
- <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia','class=miclasecss target=_blank') ?>
- => <a href="/url/con/enrutamiento/a/Economia_en_Francia" class="miclasecss" target="_blank">Mi artículo</a>
- // URI interna
- <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
- => <a href="/url/con/enrutamiento/a/Economia_en_Francia">Mi artículo</a>
-
- // URI interna con parámetros dinámicos
- <?php echo link_to('Mi artículo', 'articulo/ver?titulo='.$articulo->getTitulo()) ?>
-
- // URI interna con anclas (enlaces a secciones internas de la página)
- <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia#seccion1') ?>
- => <a href="/url/con/enrutamiento/a/Economia_en_Francia#seccion1">Mi artículo</a>
-
- // URL absolutas
- <?php echo link_to('Mi artículo', 'http://www.ejemplo.com/cualquierpagina.html') ?>
- => <a href="http://www.ejemplo.com/cualquierpagina.html">Mi artículo</a>
- // Enlace simple de texto
- <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
- => <a href="/url/con/enrutamiento/a/Economia_en_Francia">Mi artículo</a>
-
- // Enlace en una imagen
- <?php echo link_to(image_tag('ver.gif'), 'articulo/ver?titulo=Economia_en_Francia') ?>
- => <a href="/url/con/enrutamiento/a/Economia_en_Francia"><img src="/images/ver.gif" /></a>
-
- // Boton
- <?php echo button_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
- => <input value="Mi artículo" type="button" onclick="document.location.href='/url/con/enrutamiento/a/Economia_en_Francia';" />
-
- // Formulario
- <?php echo form_tag('articulo/ver?titulo=Economia_en_Francia') ?>
- => <form method="post" action="/url/con/enrutamiento/a/Economia_en_Francia" />
- <?php echo link_to('Borrar elemento', 'item/borrar?id=123', 'confirm=¿Estás seguro?') ?>
- => <a onclick="return confirm('¿Estás seguro?');"
- href="/url/con/enrutamiento/a/borrar/123.html">Borrar elemento</a>
-
- <?php echo link_to('Añadir al carrito', 'carritoCompra/anadir?id=100', 'popup=true') ?>
- => <a onclick="window.open(this.href);return false;"
- href="/url/con/enrutamiento/a/carritoCompra/anadir/id/100.html">Añadir al carrito</a>
-
- )) ?>
- => <a onclick="window.open(this.href,'popupWindow','width=310,height=400,left=320,top=0');return false;"
- href="/url/con/enrutamiento/a/carritoCompra/anadir/id/100.html">Añadir al carrito</a>
Symfony: El enrutamiento (I)
0Continuo con el libro de Symfony 1.4 en esta ocasión toca el enrutamiento.
Normalmente las urls que escribimos son así:
http://www.ejemplo.com/web/galeria/album.php?nombre=mis%20vacaciones
http://www.ejemplo.com/web/weblog/publico/post/listado.php
http://www.ejemplo.com/web/general/contenido/pagina.php?nombre=sobre%20nosotros
Y deberían ser así:
http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html
Principalmente porque son mucho más legibles para los usuarios y buscadores, y además evitamos mostrar información a un posible atacante.
El sistema de enrutamiento utiliza un archivo de configuración especial, llamado routing.yml, en el que se pueden definir las reglas de enrutamiento.
- articulo_segun_titulo:
- url: articulos/:tema/:ano/:titulo.html
- param: { module: articulo, action: permalink }
Todas las peticiones realizadas a una aplicación Symfony son analizadas en primer lugar por el sistema de enrutamiento (que es muy sencillo porque todas las peticiones se gestionan mediante un único controlador frontal). El sistema de enrutamiento busca coincidencias entre la URL de la petición y los patrones definidos en las reglas de enrutamiento. Si se produce una coincidencia, las partes del patrón que tienen nombre se transforman en parámetros de la petición y se juntan a los parámetros definidos en la clave param:.
Después, la petición se pasa a la acción permalink del módulo articulo, que dispone de toda la información necesaria en los parámetros de la petición para obtener el artículo solicitado.
El mecanismo de enrutamiento también funciona en la otra dirección. Para mostrar las URL en los enlaces de una aplicación, se debe proporcionar al sistema de enrutamiento la información necesaria para determinar la regla que se debe aplicar a cada enlace. Además, no se deben escribir los enlaces directamente con etiquetas <a> (ya que de esta forma no se estaría utilizando el sistema de enrutamiento) sino con un helper especial:
- // El helper url_for() transforma una URI interna en una URL externa
- <a href="<?php echo url_for('articulo/permalink?tema=economia&ano=2006&titulo=sectores-actividad') ?>">pincha aquí</a>
-
- // El helper reconoce que la URI cumple con la regla articulo_segun_titulo
- // El sistema de enrutamiento crea una URL externa a partir de el
- => <a href="http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html">pincha aquí</a>
-
- // El helper link_to() muestra directamente un enlace
- // y evita tener que mezclar PHP y HTML
- <?php echo link_to(
- 'pincha aqui',
- 'articulo/permalink?tema=economia&ano=2006&titulo=sectores-actividad'
- ) ?>
-
- // Internamente link_to() llama a url_for(), por lo que el resultado es el mismo
- => <a href="http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html">pincha aquí</a>
Symfony: El modelo (III)
0CONEXIONES A LA BASE DE DATOS
- > php symfony configure:database "mysql://login:password@localhost/blog"
- > php symfony --env=prod configure:database "mysql://login:password@localhost/blog"
- > php symfony --app=frontend configure:database "mysql://login:password@localhost/blog"
- > php symfony --name=otraconexion configure:database "mysql://login:password@localhost/blog"
Las opciones de conexión con la base de datos también se pueden establecer manualmente en el archivo databases.yml que se encuentra en el directorio config/.
- all:
- propel:
- class: sfPropelDatabase
- param:
- dsn: mysql://login:password@localhost/blog
- prod:
- propel:
- param:
- hostspec: mi_servidor_datos
- usuarioname: mi_nombre_usuario
- password: xxxxxxxxxx
-
- all:
- propel:
- class: sfPropelDatabase
- param:
- phptype: mysql # fabricante de la base de datos
- hostspec: localhost
- database: blog
- usuarioname: login
- password: passwd
- port: 80
- encoding: utf8 # Codificación utilizada para crear la tabla
- persistent: true # Utilizar conexiones persistentes
Los valores permitidos para el parámetro phptype corresponden a los tipos de bases de datos soportados por PDO:
- mysql
- mssql
- pgsql
- sqlite
- oracle
Si se utiliza una base de datos de tipo SQLite, el parámetro hostspec debe indicar la ruta al archivo de base de datos.
- all:
- propel:
- class: sfPropelDatabase
- param:
- phptype: sqlite
- database: %SF_DATA_DIR%/blog.db
EXTENDER EL MODELO
Los métodos del modelo que se generan automáticamente están muy bien, pero no siempre son suficientes. Si se incluye lógica de negocio propia, es necesario extender el modelo añadiendo nuevos métodos o redefiniendo algunos de los existentes.
AÑADIR NUEVOS MÉTODOS
Los nuevos métodos se pueden añadir en las clases vacías del modelo que se generan en el directorio lib/model/. Se emplea $this para invocar a los métodos del objeto actual y self:: para invocar a los métodos estáticos de la clase actual. No se debe olvidar que las clases personalizadas heredan los métodos de las clases Base del directorio lib/model/om/.
- <?
-
- class Articulo extends BaseArticulo
- {
- public function __toString()
- {
- return $this->getTitulo(); // getTitulo() se hereda de BaseArticulo
- }
- }
También se pueden extender las clases peer, como por ejemplo para obtener todos los artículos ordenados por fecha de creación:
- <?
-
- class ArticuloPeer extends BaseArticuloPeer
- {
- public static function getTodosOrdenadosPorFecha()
- {
- $c = new Criteria();
- $c->addAscendingOrderByColumn(self::CREATED_AT);
- return self::doSelect($c);
- }
- }
- <?
-
- foreach (ArticuloPeer::getTodosOrdenadosPorFecha() as $articulo)
- {
- echo $articulo; // Se llama al método mágico __toString()
- }
REDEFINIR MÉTODOS EXISTENTES
Si alguno de los métodos generados automáticamente en las clases Base no satisfacen las necesidades de la aplicación, se pueden redefinir en las clases personalizadas. Solamente es necesario mantener el mismo número de argumentos para cada método.
Por ejemplo, el método $articulo->getComentarios() devuelve un array de objetos Comentario, sin ningún tipo de ordenamiento. Si se necesitan los resultados ordenados por fecha de creación siendo el primero el comentario más reciente, se puede redefinir el método getComentarios(). Como el método getComentarios() original (guardado en lib/model/om/BaseArticulo.php) requiere como argumentos un objeto de tipo Criteria y una conexión, la nueva función debe contener esos mismos parámetros.
- <?
-
- public function getComentarios($criteria = null, $con = null)
- {
- {
- $criteria = new Criteria();
- }
- else
- {
- // Los objetos se pasan por referencia en PHP5, por lo que se debe clonar
- // el objeto original para no modificarlo
- $criteria = clone $criteria;
- }
- $criteria->addDescendingOrderByColumn(ComentarioPeer::CREATED_AT);
-
- return parent::getComentarios($criteria, $con);
- }
El método personalizado acaba llamando a su método padre en la clase Base, lo que se considera una buena práctica. No obstante, es posible saltarse completamente la clase Base y devolver el resultado directamente.
USO DE COMPORTAMIENTOS EN EL MODELO
Algunas de las modificaciones que se realizan en el modelo son genéricas y por tanto se pueden reutilizar. Por ejemplo, los métodos que hacen que un objeto del modelo sea reordenable o un bloqueo de tipo optimistic en la base de datos para evitar conflictos cuando se guardan de forma concurrente los objetos se pueden considerar extensiones genéricas que se pueden añadir a muchas clases.
Symfony encapsula estas extensiones en «comportamientos» (del inglés behaviors). Los comportamientos son clases externas que proporcionan métodos extras a las clases del modelo. Las clases del modelo están definidas de forma que se puedan enganchar estas clases externas y Symfony extiende las clases del modelo mediante sfMixer.
Para habilitar los comportamientos en las clases del modelo, se debe modificar una opción del archivo config/propel.ini:
propel.builder.AddBehaviors = true // El valor por defecto es false
Symfony no incluye por defecto ningún comportamiento, pero se pueden instalar mediante plugins. Una vez que el plugin se ha instalado, se puede asignar un comportamiento a una clase mediante una sola línea de código. Si por ejemplo se ha instalado el plugin sfPropelParanoidBehaviorPlugin en la aplicación, se puede extender la clase Articulo con este comportamiento añadiendo la siguiente línea de código al final del archivo Articulo.class.php:
Después de volver a generar el modelo, los objetos de tipo Articulo que se borren permanecerán en la base de datos, aunque será invisibles a las consultas que hacen uso de los métodos del ORM, a no ser que se deshabilite temporalmente el comportamiento mediante sfPropelParanoidBehavior::disable().
Desde la versión 1.1 de Symfony también es posible declarar los comportamientos directamente en el archivo schema.yml, incluyéndolos bajo la clave _behaviors.
La lista de plugins de Symfony disponible en el wiki incluye numerosos comportamientos http://trac.symfony-project.org/wiki/SymfonyPlugins#Behaviors. Cada comportamiento tiene su propia documentación y su propia guía de instalación.
SINTAXIS EXTENDIDA DEL ESQUEMA
Atributos
Se pueden definir atributos específicos para las conexiones y las tablas. Estas opciones se establecen bajo la clave _attributes.
- propel:
- _attributes: { noXsd: false, defaultIdMethod: none, package: lib.model }
- blog_articulo:
- _attributes: { phpName: Articulo }
Si se quiere validar el esquema antes de que se genere el código asociado, se debe desactivar en la conexión el atributo noXSD. La conexión también permite que se le indique el atributo defaultIdMethod. Si no se indica, se utilizará el método nativo de generación de IDs –por ejemplo, autoincrement en MySQL o sequences en PostgreSQL. El otro valor permitido es none.
El atributo package es como un namespace; indica la ruta donde se guardan las clases generadas automáticamente. Su valor por defecto es lib/model/, pero se puede modificar para organizar el modelo en una estructura de subpaquetes. Si por ejemplo no se quieren mezclar en el mismo directorio las clases del núcleo de la aplicación con las clases de un sistema de estadísticas, se pueden definir dos esquemas diferentes con los paquetes lib.model.business y lib.model.stats.
Ya se ha visto el atributo de tabla phpName, que se utiliza para establecer el nombre de la clase generada automáticamente para manejar cada tabla de la base de datos.
Las tablas que guardan contenidos adaptados para diferentes idiomas (es decir, varias versiones del mismo contenido en una tabla relacionada, para conseguir la internacionalización) también pueden definir dos atributos adicionales.
- propel:
- blog_articulo:
- _attributes: { isI18N: true, i18nTable: db_group_i18n }
DETALLE DE LAS COLUMNAS
La sintaxis básica ofrece dos posibilidades: dejar que Symfony deduzca las características de una columna a partir de su nombre (indicando un valor vacío para esa columna) o definir el tipo de columna con uno de los tipos predefinidos.
- propel:
- blog_articulo:
- id: # Symfony se encarga de esta columna
- titulo: varchar(50) # Definir el tipo explícitamente
Se pueden definir muchos más aspectos de cada columna. Si se definen, se utiliza un array asociativo para indicar las opciones de la columna.
- propel:
- blog_articulo:
- id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
- name: { type: varchar(50), default: foobar, index: true }
- group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade }
Los parámetros de las columnas son los siguientes:
- type: Tipo de columna. Se puede elegir entre boolean, tinyint, smallint, integer, bigint, double, float, real, decimal, char, varchar(tamano), longvarchar, date, time, timestamp, bu_date, bu_timestamp, blob y clob
- required: valor booleano. Si vale true la columna debe tener obligatoriamente un valor.
- default: el valor por defecto.
- primaryKey: valor booleano. Si vale true indica que es una clave primaria.
- autoIncrement: valor booleano. Si se indica true para las columnas de tipo integer, su valor se auto-incrementará.
- sequence: el nombre de la secuencia para las bases de datos que utilizan secuencias para las columnas autoIncrement (por ejemplo PostgreSQL y Oracle).
- index: valor booleano. Si vale true, se construye un índice simple; si vale unique se construye un índice único para la columna.
- foreignTable: el nombre de una tabla, se utiliza para crear una clave externa a otra tabla.
- foreignReference: el nombre de la columna relacionada si las claves externas se definen mediante foreignTable.
- onDelete: determina la acción que se ejecuta cuando se borra un registro en una tabla relacionada. Si su valor es setnull, la columna de la clave externa se establece a null. Si su valor es cascade, se borra el registro relacionado. Si el sistema gestor de bases de datos no soporta este comportamiento, el ORM lo emula. Esta opción solo tiene sentido para las columnas que definen una foreignTable y una foreignReference.
- isCulture: valor booleano. Su valor es true para las columnas de tipo culture en las tablas de contenidos adaptados a otros idiomas.
CLAVES EXTERNAS
Además de los atributos de columna foreignTable y foreignReference, es posible añadir claves externas bajo la clave _foreignKeys: de cada tabla.
- propel:
- blog_articulo:
- id:
- titulo: varchar(50)
- usuario_id: { type: integer }
- _foreignKeys:
- -
- foreignTable: blog_usuario
- onDelete: cascade
- references:
- - { local: usuario_id, foreign: id }
La sintaxis alternativa es muy útil para las claves externas múltiples y para indicar un nombre a cada clave externa.
- _foreignKeys:
- mi_clave_externa:
- foreignTable: db_usuario
- onDelete: cascade
- references:
- - { local: usuario_id, foreign: id }
- - { local: post_id, foreign: id }
INDICES
Además del atributo de columna index, es posible añadir claves índices bajo la clave _indexes: de cada tabla. Si se quieren crean índices únicos, se debe utilizar la clave _uniques:. En las columnas que requieren un tamaño, por ejemplo por ser columnas de texto, el tamaño del índice se indica entre paréntesis, de la misma forma que se indica el tamaño de cualquier columna.
- propel:
- blog_articulo:
- id:
- titulo: varchar(50)
- created_at:
- _indexes:
- mi_indice: [titulo(10), usuario_id]
- _uniques:
- mi_otro_indice: [created_at]
COLUMNAS VACÍAS
Cuando Symfony se encuentra con una columna sin ningún valor, utiliza algo de magia para determinar su valor.
- // Las columnas vacías llamadas "id" se consideran claves primarias
- id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
-
- // Las columnas vacías llamadas "XXX_id" se consideran claves externas
- loquesea_id: { type: integer, foreignTable: db_loquesea, foreignReference: id }
-
- // Las columnas vacías llamadas created_at, updated at, created_on y updated_on
- // se consideran fechas y automáticamente se les asigna el tipo "timestamp"
- created_at: { type: timestamp }
- updated_at: { type: timestamp }
Para las claves externas, Symfony busca una tabla cuyo phpName sea igual al principio del nombre de la columna; si se encuentra, se utiliza ese nombre de tabla como foreignTable.
TABLAS I18N
Symfony permite internacionalizar los contenidos mediante tablas relacionadas. De esta forma, cuando se dispone de contenido que debe ser internacionalizado, se guarda en 2 tablas distintas: una contiene las columnas invariantes y otra las columnas que permiten la internacionalización.
Todo lo anterior se considera de forma implícita cuando en el archivo schema.yml se dispone de una tabla con el nombre cualquiernombre_i18n.
- propel:
- db_group:
- id:
- created_at:
-
- db_group_i18n:
- name: varchar(50)
- propel:
- db_group:
- _attributes: { isI18N: true, i18nTable: db_group_i18n }
- id:
- created_at:
-
- db_group_i18n:
- id: { type: integer, required: true, primaryKey: true, foreignTable: db_group, foreignReference: id, onDelete: cascade }
- culture: { isCulture: true, type: varchar(7), required: true, primaryKey: true }
- name: varchar(50)
COMPORTAMIENTOS
Los comportamientos son plugins que modifican el modelo de datos para añadir nuevas funcionalidades a las clases de Propel.
- propel:
- blog_articulo:
- titulo: varchar(50)
- _behaviors:
- paranoid: { column: deleted_at }
«Chuletas» para Symfony
0Más de uno conocerá esas imágenes o PDFs que, de forma resumida, muestran todos los métodos y funciones de los lenguajes de programación o de frameworks. Con Symfony eso no es diferente, además de ser muy útil en las primeras etapas de aprendizaje. Os dejo el link:
http://trac.symfony-project.org/wiki/CheatSheets
¡Qué lo disfrutéis!
Symfony: El modelo (I)
0Las bases de datos son relacionales. PHP 5 y Symfony están orientados a objetos. Para acceder de forma efectiva a la base de datos desde un contexto orientado a objetos, es necesaria una interfaz que traduzca la lógica de los objetos a la lógica relacional. Esta interfaz se llama ORM (object-relational mapping) o «mapeo de objetos a bases de datos», y está formada por objetos que permiten acceder a los datos y que contienen en sí mismos el código necesario para hacerlo.
La utilización de objetos en vez de registros y de clases en vez de tablas, tiene otra ventaja: permite añadir métodos accesores en los objetos que no tienen relación directa con una tabla. Si se dispone por ejemplo de una tabla llamada cliente con dos campos llamados nombre y apellidos, puede que se necesite un dato llamado NombreCompleto que incluya y combine el nombre y los apellidos. En el mundo orientado a objetos, es tan fácil como añadir un método accesor a la clase Cliente. Desde el punto de vista de la aplicación, no existen diferencias entre los atributos Nombre, Apellidos, NombreCompleto de la clase Cliente. Solo la propia clase es capaz de determinar si un atributo determinado se corresponde con una columna de la base de datos.
- <?php
-
- public function getNombreCompleto()
- {
- return $this->getNombre().' '.$this->getApellidos();
- }
Todo el código repetitivo de acceso a los datos y toda la lógica de negocio de los propios datos se puede almacenar en esos objetos. Imagina que se ha definido la clase CarritoCompra en la que se almacenan Productos (que son objetos). Para obtener el precio total del carrito de la compra antes de realizar el pago, se puede crear un método que encapsula el proceso de cálculo:
- <?php
-
- public function getTotal()
- {
- $total = 0;
- foreach ($this->getProductos() as $producto)
- {
- $total += $producto->getPrecio() * $producto->getCantidad();
- }
-
- return $total;
- }
ESQUEMA DE BASE DE DATOS DE SYMFONY
Para crear el modelo de objetos de datos que utiliza Symfony, se debe traducir el modelo relacional de la base de datos a un modelo de objetos de datos. Para realizar ese mapeo o traducción, el ORM necesita una descripción del modelo relacional, que se llama «esquema» (schema). En el esquema se definen las tablas, sus relaciones y las características de sus columnas.
La sintaxis que utiliza Symfony para definir los esquemas hace uso del formato YAML. Los archivos schema.yml deben guardarse en el directorio miproyecto/config/.
- propel:
- blog_articulo:
- _attributes: { phpName: Articulo }
- id:
- titulo: varchar(255)
- contenido: longvarchar
- created_at:
- blog_comentario:
- _attributes: { phpName: Comentario }
- id:
- articulo_id:
- autor: varchar(255)
- contenido: longvarchar
- created_at:
SINTAXIS BÁSICA DE LOS ESQUEMAS
En el archivo schema.yml, la primera clave representa el nombre de la conexión. Puede contener varias tablas, cada una con varias columnas. Siguiendo la sintaxis de YAML, las claves terminan con dos puntos (:) y la estructura se define mediante la indentación con espacios, no con tabuladores.
Cada tabla puede definir varios atributos, incluyendo el atributo phpName (que es el nombre de la clase PHP que será generada para esa tabla). Si no se menciona el atributo phpName para una tabla, Symfony crea una clase con el mismo nombre que la tabla al que se aplica las normas del camelCase.
Las tablas contienen columnas y el valor de las columnas se puede definir de 3 formas diferentes:
- Si no se indica nada, Symfony intenta adivinar los atributos más adecuados para la columna en función de su nombre y de una serie de convenciones. Por ejemplo, en el listado anterior no es necesario definir la columna id. Symfony por defecto la trata como de tipo entero (integer), cuyo valor se auto-incrementa y además, clave principal de la tabla. En la tabla blog_comentario, la columna articulo_id se trata como una clave externa a la tabla blog_articulo (las columnas que acaban en _id se consideran claves externas, y su tabla relacionada se determina automáticamente en función de la primera parte del nombre de la columna). Las columnas que se llaman created_at automáticamente se consideran de tipo timestamp. Para este tipo de columnas, no es necesario definir su tipo. Esta es una de las razones por las que es tan fácil crear archivos schema.yml.
- Si sólo se define un atributo, se considera que es el tipo de columna. Symfony entiende los tipos de columna habituales: boolean, integer, float, date, varchar(tamaño), longvarchar (que se convierte, por ejemplo, en tipo text en MySQL), etc. Para contenidos de texto de más de 256 caracteres, se utiliza el tipo longvarchar, que no tiene tamaño definido (pero que no puede ser mayor que 65KB en MySQL). Los tipos date y timestamp tienen las limitaciones habituales de las fechas de Unix y no pueden almacenar valores anteriores al 1 de Enero de 1970. Como puede ser necesario almacenar fechas anteriores (por ejemplo para las fechas de nacimiento), existe un formato de fechas «anteriores a Unix» que son bu_date and bu_timestamp.
- Si se necesitan definir otros atributos a la columna (por ejemplo su valor por defecto, si es obligatorio o no, etc.), se indican los atributos como pares clave: valor.
Las columnas también pueden definir el atributo phpName, que es la versión modificada de su nombre según las convenciones habituales (Id, Titulo, Contenido, etc) y que normalmente no es necesario redefinir.
LAS CLASES DEL MODELO
Como ya has podido leer, Symfony a través de Propel genera una clase para cada tabla. Para generar esas clases simplemente, una vez hayas terminado de crear el esquema del modelo, en la consola escribe:
> php symfony propel:build-model
Al ejecutar ese comando, se analiza el esquema y se generan las clases base del modelo, que se almacenan en el directorio lib/model/om/ del proyecto:
- BaseArticulo.php
- BaseArticuloPeer.php
- BaseComentario.php
- BaseComentarioPeer.php
Además, se crean las verdaderas clases del modelo de datos en el directorio lib/model/:
- Articulo.php
- ArticuloPeer.php
- Comentario.php
- ComentarioPeer.php
Sólo se han definido dos tablas y se han generado ocho archivos. Aunque este hecho no es nada extraño, merece una explicación.
Puede ser necesario añadir métodos y propiedades personalizadas en los objetos del modelo (piensa por ejemplo en el método getNombreCompleto()). También es posible que a medida que el proyecto se esté desarrollando, se añadan tablas o columnas. Además, cada vez que se modifica el archivo schema.yml se deben regenerar las clases del modelo de objetos mediante el comando propel-build-model. Si se añaden los métodos personalizados en las clases que se generan, se borrarían cada vez que se vuelven a generar esas clases.
Las clases con nombre Base del directorio lib/model/om/ son las que se generan directamente a partir del esquema. Nunca se deberían modificar esas clases, porque cada vez que se genera el modelo, se borran todas las clases.
Por otra parte, las clases de objetos propias que están en el directorio lib/model heredan de las clases con nombre Base. Estas clases no se modifican cuando se ejecuta la tarea propel:build-model, por lo que son las clases en las que se añaden los métodos propios.
- <?php
-
- class Articulo extends BaseArticulo
- {
- }
Articulo y Comentario son clases objeto que representan un registro de la base de datos. Permiten acceder a las columnas de un registro y a los registros relacionados. Por tanto, es posible obtener el título de un artículo invocando un método del objeto Articulo:
- <?php
-
- $articulo = new Articulo();
-
- // ...
- $titulo = $articulo->getTitulo();
ArticuloPeer y ComentarioPeer son clases de tipo «peer«; es decir, clases que tienen métodos estáticos para trabajar con las tablas de la base de datos. Proporcionan los medios necesarios para obtener los registros de las tablas. Sus métodos devuelven normalmente un objeto o una colección de objetos de la clase objeto relacionada:
- <?php
-
- // $articulos es un array de objetos de la clase Articulo
#symfony, I18N, UTF-8 y Dreamweaver
0Supongo que ya sabrás de lo que voy a hablar, sí, codificación de caracteres y el jodío de Dreamweaver. Te cuento:
Estoy haciendo algunas pruebas con Symfony y su sistema de internacionalización ( I18N ), y para ello he hecho que el charset que muestre la plantilla sea utf-8 (además de las tablas de la BD y las conexiones desde y hacía la BD), lo curioso es que cuando he ido a verlo en el navegador el resultado me aparecía con chinos (unos cuadrados (aunque pueden ser otros símbolos) que sustituyen a las letras con tilde), total que me he tirado todo el día dándole vueltas al tema, y el problema lo tenía en Dreamweaver que, por defecto, guarda los archivos en «Europeo occidental» y no en utf-8.
Te explico todo lo que he modificado en el proyecto de Symfony, por si te pasa algo parecido puedas comparar. Con esta configuración que te voy a mostrar el sistema de internacionalización de Symfony funciona al 100%:
Antes de nada te informo que esto está pensado para Symfony 1.4, versiones superiores o inferiores pueden necesitar una configuración distinta. Cuando hablo de Dreamweaver me refiero a la versión CS3, aunque muy probablemente algo parecido haya en versiones superiores.
settings.yml
Doy por hecho que ya has creado el proyecto, al menos una aplicación y un módulo con el que hacer pruebas. Nos vamos al archivo settings.yml de la app y añadimos:
- all:
- .settings:
-
- # Indicamos la cultura por defecto
- # Aquí poned la que os interese
- default_culture: es_ES
-
- # Indicamos la codificación de caracteres
- charset: utf-8
-
- # Esto lo dejo a tu elección
- # Puedes escribir esta línea para que el helper I18N esté
- # en todas las plantillas de forma global
- # Lo bueno de usar esta opción es que puedes añadir
- # más helpers:
- # standard_helpers: [I18N,text, etc]
- standard_helpers: [I18N]
-
- # O puedes escribir esta otra línea, pero
- # en cada una de las plantillas tendrás que incluir
- # al prinicipio <?php use_helper('I18N') ?>
- # la decisión es tuya
- i18n: true
routing.yml
Modificamos este archivo para indicar el módulo que se mostrará como página de inicio (homepage):
- # Esto es lo que está por defecto
- homepage:
- url: /
- param: { module: default, action: index }
-
- # Lo único que cambio es el nombre del módulo, que en mi caso es 'login'
- homepage:
- url: /
- param: { module: login, action: index }
view.yml
Este archivo creo que no hacia falta modificarlo para la internacionalización, pero por si acaso:
- metas:
- language: es
indexSuccess.php del módulo
Añadimos el texto que vamos a probar:
- // No voy a poner todo el código que tengo en mi plantilla
- // así que pongo solo un ejemplo
-
- <?php echo __('Hola Mundo!') ?>
-
- // Esto te mostrará el texto en español
Bien, como le hemos dado el valor «es_ES» a «default_culture«, Symfony no mostrará ninguna traducción sino el valor que le hemos indicado en la plantilla. Para hacer una prueba en condiciones vamos a modificar la cultura de un usuario a «en_EN«, esto mantendrá la cultura de todo el proyecto como «es_ES«.
actions.class.php del módulo
En la acción Index escribimos lo siguiente:
- class loginActions extends sfActions
- {
- /**
- * Executes index action
- *
- * @param sfRequest $request A request object
- */
- public function executeIndex(sfWebRequest $request)
- {
-
- // Esta línea cambiará la cultura SÓLO para el usuario
- $this -> getUser() -> setCulture('en_EN');
-
- return sfView::SUCCESS;
- }
- }
Obviamente nos falta crear el archivo que le indicará a Symfony el texto que debe utilizar para su sustitución. A ello voy:
english.en.xml
Este archivo se guarda en la carpeta «i18n» de la app, aunque también puedes crear la carpeta dentro del módulo y guardar el archivo allí. Este archivo tiene un formato especial que se debe mantener:
- <?xml version="1.0" encoding="utf-8"?>
- <xliff version="1.0">
- <file orginal="global" source-language="en_EN" datatype="plaintext">
- <body>
- <trans-unit id="1">
- <source>Hola Mundo!</source>
- <target>Hello World!</target>
- </trans-unit>
- <trans-unit id="2">
- <source>soy un texto</source>
- <target>I'm a text</target>
- </trans-unit>
- <trans-unit id="3">
- <source>Adiós</source>
- <target>Bye</target>
- </trans-unit>
- </body>
- </file>
- </xliff>
Cosas importantes respecto de este archivo:
- Le puedes poner cualquier nombre pero debe acabar en *.culture.xml, por ejemplo: login.en.xml, registro.en.xml, administracion.fr.xml, comentario.it.xml.
- También se puede poner la cultura completa, es decir, en vez de solo *.en.xml puedes nombrarlo como *.en_EN.xml.
- El parámetro «source-language» siempre debe indicar la cultura que se va a traducir, en este caso es el ingles (en_EN).
- Cada texto a traducir está dentro de la etiqueta <trans-unit>, que tiene el atributo id, pues bien, cada frase a traducir debe aumentar el id ( 1,2,3,4,5….500 etc)
- No es necesario que todas las frases estén en un solo archivo, puede haber varios archivos con textos diferentes, por ejemplo, para el menú, la cabecera, el pie de página, etc.,. Importante: aunque sean archivos para idiomas diferentes el nombre del archivo siempre debe ser el mismo, variando, eso sí, la cultura.
Una vez guardado el archivo volvemos al indexSuccess.php y lo modificamos para que quede tal que así;
- // No voy a poner todo el código que tengo en mi plantilla
- // así que pongo solo un ejemplo
-
- <?php echo __('Hola Mundo!', null, 'login') ?>
-
- // Ahora mostrará el texto que corresponda con la cultura del usuario
- // Además lo buscará en un archivo concreto en este caso login.en.xml
Y así se internacionaliza un proyecto Symfony. Ahora el problemita de Dreamweaver:
El bicho, (por llamarlo de alguna manera) tiene una opción para abrir archivos que no indiquen su codificación. Normalmente está en «Europeo occidental» y debería estar en «Unicode (utf-8)«. Para cambiarlo accedemos al menú «Edición -> Preferencias…» y en «Nuevo documento» busca un desplegable que ponga «Codificación pred.«; ahí elige «Unicode (utf-8)«, activa la casilla que dice «Utilizar al abarir archivos existentes que no indiquen su codificación» y en el desplegable de abajo (Formulario de normas Unicode) selecciona «C (descomposición de compatibilidad seguida de composición canónica)«. Pulsa aceptar y prueba el en navegador.
Tal vez tengas que limpiar la cache de Symfony para ello solo tienes que escribir en la consola (que debe apuntar a la carpeta donde tienes el proyecto) symfony cc o php symfony cc.
Si sigue fallando, abre el archivo indexSuccess.php del módulo con el bloc de notas, y sin modificar nada, vete a Archivo -> Guardar como… , ahí podrás elegir la codificación, en nuestro caso, utf-8, y para evitar que se guarde como un archivo .txt elige «Todos los archivos» y así se guardará en php. Vuelve a limpiar la caché y vuelve a probar, ahora sí te debería funcionar bien.
Symfony: El controlador (III)
0SEGURIDAD DE LA ACCIÓN
Con Symfony podemos controlar quién puede acceder a una acción concreta, utilizando para ello las herramientas de identificación de usuarios que el framework nos provee.
Una acción segura sólo podrá ser ejecutada por un usuario registrado y que tenga la acreditación para ello.
- Las acciones seguras requieren que los usuarios estén autenticados.
- Las credenciales son privilegios de seguridad agrupados bajo un nombre y que permiten organizar la seguridad en grupos.
- ver:
- is_secure: off # Todos los usuarios pueden ejecutar la acción "ver"
-
- modificar:
- is_secure: on # La acción "modificar" es sólo para usuarios autenticados
-
- borrar:
- is_secure: on # Sólo para usuarios autenticados
- credentials: admin # Con credencial "admin"
-
- all:
- is_secure: off # off es el valor por defecto
Lo que sucede cuando un usuario trata de acceder una acción restringida depende de sus credenciales:
- Si el usuario está autenticado y tiene las credenciales apropiadas, entonces la acción se ejecuta.
- Si el usuario no está autenticado, es redireccionado a la acción de login.
- Si el usuario está autenticado, pero no posee las credenciales apropiadas, será redirigido a la acción segura por defecto.
Las páginas login y secure son bastante simples, por lo que seguramente será necesario personalizarlas. Se puede configurar que acciones se ejecutan en caso de no disponer de suficientes privilegios en el archivo settings.yml de la aplicación cambiando el valor de las propiedades:
- all:
- .actions:
- login_module: default
- login_action: login
-
- secure_module: default
- secure_action: secure
OTORGANDO ACCESO
Los métodos utilizados para permitir acceso a una acción segura se encuentran en el objeto sfUser. El estado identificado se establece con setAuthenticated() y se puede comprobar con isAuthenticated():
- <?php
-
- class miCuentaActions extends sfActions
- {
- public function executeLogin($peticion)
- {
- if ($peticion->getParameter('login') == 'valor')
- {
- $this->getUser()->setAuthenticated(true);
- }
- }
-
- public function executeLogout()
- {
- $this->getUser()->setAuthenticated(false);
- }
- }
Las credenciales son un poco más complejas de tratar, ya que se pueden verificar, agregar, quitar y borrar:
- <?php
-
- class miCuentaActions extends sfActions
- {
- public function executeEjemploDeCredenciales()
- {
- $usuario = $this->getUser();
-
- // Agrega una o más credenciales
- $usuario->addCredential('parametro');
- $usuario->addCredentials('parametro', 'valor');
-
- // Verifica si el usuario tiene una credencial
- echo $usuario->hasCredential('parametro'); => true
-
- // Verifica si un usuario tiene una de las credenciales
-
- // Verifica si el usuario tiene ambas credenciales
-
- // Quitar una credencial
- $usuario->removeCredential('parametro');
- echo $usuario->hasCredential('parametro'); => false
-
- // Elimina todas las credenciales (útil en el proceso de logout)
- $usuario->clearCredentials();
- echo $usuario->hasCredential('valor'); => false
- }
- }
Las credenciales se pueden utilizar también para mostrar contenido autenticado en una plantilla:
- <?php
-
- <ul>
- <li><?php echo link_to('seccion1', 'content/seccion1') ?></li>
- <li><?php echo link_to('seccion2', 'content/seccion2') ?></li>
- <?php if ($sf_user->hasCredential('seccion3')): ?>
- <li><?php echo link_to('seccion3', 'content/seccion3') ?></li>
- <?php endif; ?>
- </ul>
CREDENCIALES COMPLEJAS
a sintaxis YAML utilizada en el archivo security.yml permite restringir el acceso a usuarios que tienen una combinación de credenciales, usando asociaciones de tipo AND y OR. Con estas combinaciones, se pueden definir flujos de trabajo y sistemas de manejo de privilegios muy complejos — como por ejemplo, un sistema de gestión de contenidos (CMS) cuya parte de gestión sea accesible solo a usuarios con credencial admin, donde los artículos pueden ser editados solo por usuarios con credenciales de editor y publicados solo por aquellos que tienen credencial de publisher:
- editarArticulo:
- credentials: [ admin, editor ] # admin AND editor
-
- publicarArticulo:
- credentials: [ admin, publisher ] # admin AND publisher
-
- gestionUsuarios:
- credentials: [[ admin, superuser ]] # admin OR superuser
Cada vez que se añade un nuevo nivel de corchetes, la lógica cambia entre AND y OR. Así que se pueden crear combinaciones muy complejas de credenciales, como la siguiente:
- credentials: [[root, [supplier, [owner, quasiowner]], accounts]]
- # root OR (supplier AND (owner OR quasiowner)) OR accounts
CONFIGURACIÓN DEL MÓDULO
Algunas características de los módulos dependen de la configuración. Para modificarlas, se debe crear un archivo module.yml en el directorio config/ y se deben definir parámetros para cada entorno (o en la sección all: para todos los entornos).
- all: # Para todos los entornos
- enabled: true
- is_internal: false
- view_class: sfPHP
- partial_view_class: sf
El parámetro enabled permite desactivar todas las acciones en un módulo. En ese caso, todas las acciones se redireccionan a la acción module_disabled_module/module_disabled_action (tal y como se define en el archivo settings.yml).
El parámetro is_internal permite restringir la ejecución de todas las acciones de un módulo a llamadas internas. Esto es útil por ejemplo para acciones de envío de correos electrónicos que se deben llamar desde otras acciones para enviar mensajes de e-mail, pero que no se deben llamar desde el exterior.
El parámetro view_class define la clase de la vista. Debe heredar de sfView. Sobreescribir este valor permite utilizar otros sistemas de generación de vistas con otros motores de plantillas, como por ejemplo Smarty.
El parámetro partial_view_class define la clase de la vista que se emplea para los elementos parciales de este módulo. La clase indicada debe heredar de sfPartialView.
Symfony, creando el archivo schema.yml para una base de datos ya existente
0Sí, sigo dándole caña a Symfony… y cometiendo errores de novato xD.
Para ir probando Symfony con un proyecto real, he decidido utilizar una base de datos ya existente para generar una nueva versión del proyecto anterior, y como ya sabrás para que Symfony (bueno, en este caso Propel) genere las clases necesarias para trabajar con la BD es necesario que la estructura de ésta esté en el archivo schema.yml dentro de la carpeta /config del proyecto.
Para ello, primero hay que configurar la conexión a la base de datos en symfony de la siguiente manera:
symfony configure:database «mysql://login:password@localhost/basededatos»symfony configure:database «mysql:host=localhost;dbname=nombreBD» root pass
Ahora le decimos a Propel que genere el archivo schema.yml a partir de la base de datos:
symfony propel:build-schema
Propel coge los datos de configuración de la BD que le hemos dado anteriormente.
Y con esto se genera el archivo schema.yml y symfony está listo para generar las clases.
PD1: Propel utiliza PDO para conectar con la BD, si por alguna razón os muestra un error diciendo que no encuentra el driver o algo por el estilo, simplemente id al archivo php.ini y habilitad la extensión pdo_mysql y reiniciar apache, esto lo debería arreglar.
PD2: Cada vez que tengas que generar el modelo recuerda de limpiar la caché de symfony para evitarte problemas:
symfony cc
PD3: Si al generar el modelo, Propel te devuelve un error de «tabla duplicada» o parecido, solo tienes que ir a la carpeta /config del proyecto y eliminar un archivo llamado generated-schema.yml (si no recuerdo mal).
Symfony: Generando un proyecto utilizando el ORM Propel
1La versión 1.4 de Symfony utiliza por defecto como ORM a Doctrine, ya que tiene mejor rendimiento, pero las prácticas de Symfony como por ejemplo Jobeet utiliza Propel. Asi que, para poder utilizar este ORM hay que generar la aplicación con el parámetro –orm, tal como sigue:
symfony generate:project mi_proyecto –orm=Propel
De esta forma, Propel, será el ORM por defecto. Aunque siempre podrás cambiarlo a Doctrine.