Jornadas Symfony

http://decharlas.uji.es/symfony
organizan patrocinan

5 y 6 de julio 2010 Universitat Jaume I, Castellón

colaboran

Doctrine
Nacho Martín

Jornadas Symfony
http://decharlas.uji.es/symfony

5 y 6 de julio 2010 Universitat Jaume I, Castellón

¿Qué es Doctrine?
Object Relational Mapper hecho para PHP >=5.2.3 (Doctrine 2.0 PHP >5.3)
● ●

Basado en Hibernate (Java) ¿Y Propel?

Jornadas Symfony

http://decharlas.uji.es/symfony

Componentes

Jornadas Symfony

http://decharlas.uji.es/symfony

Activación
//config/ProjectConfiguration.class.php public function setup() { $this->enablePlugins(array('sfDoctrinePlugin')); $this->disablePlugins(array('sfPropelPlugin')); } #config/databases.yml all: doctrine: class: sfDoctrineDatabase param: dsn: 'mysql:host=localhost;dbname=midb' username: usuario password: secreto
Jornadas Symfony http://decharlas.uji.es/symfony

Una aplicación de ejemplo

Lista ToDo Con items (one-many) Y tags (many-many)

Jornadas Symfony

http://decharlas.uji.es/symfony

El esquema YAML
Todo: actAs: Timestampable: ~ columns: name: { type: string(255), notnull: true} description: { type: string(1024) } relations: Tags: { class: Tag, refClass: TodoTag, local: todo_id, foreign: tag_id, foreignAlias: Todos} Item: actAs: Timestampable: ~ columns: name: { type: string(255) } text: { type: string(4000) } todo_id: { type: integer, notnull: true } relations: Todo: { class: Todo, onDelete: CASCADE, local: todo_id, foreign: id, foreignAlias: items } TodoTag: columns: tag_id: { type: integer, primary: true } todo_id: { type: integer, primary: true } relations: Tag: { onDelete: CASCADE, local: tag_id, foreign: id } Todo: { onDelete: CASCADE, local: todo_id, foreign: id } Tag: columns: name: { type: string(255) }
Jornadas Symfony http://decharlas.uji.es/symfony

El modelo (columnas)
abstract class BaseTodo extends sfDoctrineRecord { public function setTableDefinition() { $this->setTableName('todo'); $this->hasColumn('name', 'string', 255, array( 'type' => 'string', 'notnull' => true, 'length' => '255', )); $this->hasColumn('description', 'string', 1024, array( 'type' => 'string', 'length' => '1024', )); }

Jornadas Symfony

http://decharlas.uji.es/symfony

El modelo (relaciones)
public function setUp() { parent::setUp(); $this->hasMany('Tag as Tags', array( 'refClass' => 'TodoTag', 'local' => 'todo_id', 'foreign' => 'tag_id')); $this->hasMany('Item as items', array( 'local' => 'id', 'foreign' => 'todo_id')); $this->hasMany('TodoTag', array( 'local' => 'id', 'foreign' => 'tag_id')); //Behaviour $timestampable0 = new Doctrine_Template_Timestampable(); $this->actAs($timestampable0);

}

}

Jornadas Symfony

http://decharlas.uji.es/symfony

Fixtures (Doctrine 1)
Todo: denver: name: Cosas que hacer en Denver description: Cuando hayamos muerto Item: gambas: name: Comer gambas Todo: denver gangsters: name: Cosas de gangsters Todo: denver Tag: turismo: name: Turismo gangsteril Todos: [denver]
Jornadas Symfony http://decharlas.uji.es/symfony

DQL

Simplifica SQL y es portable Incorpora POO a SQL

Jornadas Symfony

http://decharlas.uji.es/symfony

DQL
$q = Doctrine_Query::create() ->select('l.*, i.name, t.name') ->from('Todo l') ->innerJoin('l.Items i') ->leftJoin('l.Tags t'); echo $q->getSqlQuery(); SELECT t.id AS t__id, t.name AS t__name, t.description AS t__description, t.created_at AS t__created_at, t.updated_at AS t__updated_at, i.id AS i__id, i.name AS i__name, t2.id AS t2__id, t2.name AS t2__name FROM todo t INNER JOIN item i ON t.id = i.todo_id LEFT JOIN todo_tag t3 ON (t.id = t3.todo_id) LEFT JOIN tag t2 ON t2.id = t3.tag_id

Jornadas Symfony

http://decharlas.uji.es/symfony

Objetos
Métodos de acceso y manipulación
$list = new Todo(); //Manipulación 1 $list->name = "Cosas que hacer en Denver"; 2 $list['name'] = "Cosas que hacer en Denver"; 3 $list->set('name', "Cosas que hacer en Denver"); //Acceso 1 echo $list->name; 2 echo $list['name']; //Recomendado (hidratación) 3 echo $list->get('name');

Jornadas Symfony

http://decharlas.uji.es/symfony

Hidratación (I)
En objetos ● En arrays (más rápido) ● Scalar ● Single Scalar ● Bajo demanda ● …

¿No es suficiente? ¡Escribe el tuyo!

Jornadas Symfony

http://decharlas.uji.es/symfony

Hidratación (II)
$q = Doctrine_Query::create() ->from('Todo l') ->innerJoin('l.Items i'); $lists = $q->execute(); //Record echo $lists[0]['name'];

Jornadas Symfony

http://decharlas.uji.es/symfony

Hidratación (II)
$q = Doctrine_Query::create() ->from('Todo l') ->innerJoin('l.Items i'); $lists = $q->execute(); //Record echo $lists[0]['name']; $lists = $q->fetchArray(); //Array echo $lists[0]['name']; //O bien $q->execute(array(), Doctrine::HYDRATE_ARRAY); print_r($lists);
Array ( [0] => Array ( [id] => 1 [name] => Cosas que hacer en Denver [description] => Cuando hayamos muerto [created_at] => 2010-06-22 23:55:02 [updated_at] => 2010-06-22 23:55:02 [Items] => Array ( [0] => Array ( [id] => 1 [name] => Comer gambas [text] => [todo_id] => 1 [created_at] => 2010-06-22 23:55:02 [updated_at] => 2010-06-22 23:55:02 ) [1] => Array (…)

)

)

)

Jornadas Symfony

http://decharlas.uji.es/symfony

Hidratación (III)
El acceso por arrays funciona en los dos métodos de hidratación ● La hidratación por arrays es más eficiente si solo queremos consultar datos directos de la BD ● fetchArray() es un alias de execute() con la hidratación por array ● Uso de foreach, count(), isset(), unset()

Jornadas Symfony

http://decharlas.uji.es/symfony

Definiendo setters/getters
class Todo extends BaseTodo { //Nuevo getter public function getDescriptionHtml() { return Markdown::parse(htmlspecialchars( $this->description)); } //Sobrecarga del setter public function setDescription($description) { return $this->_set('description', Markdown::parse(htmlspecialchars($description))); } }
Jornadas Symfony http://decharlas.uji.es/symfony

Relaciones
$list = new Todo(); $list['name'] = "Libros para este verano"; $list->Items[]->name = "Hablemos de Langostas"; $list->save();

Las relaciones son intuitivas
● ●

Siempre podemos recurrir a DQL //Borrar Pero en DQL no se ejecutarán preDelete(), postDelete()... (!)

//Usando link() $item = new Item(); $item['name'] = "La broma infinita"; $item->link('Todo',array($list['id'])); $item->save(); $list->Items[0]->delete(); //Siempre nos quedará DQL $q = Doctrine_Query::create() ->delete('Item') ->addWhere('todo_id = ?', $list['id']) ->whereIn('name', array($item['name'], 'otro nombre')); $q->execute();

Jornadas Symfony

http://decharlas.uji.es/symfony

Array $q = Doctrine_Query::create() ( [0] => Array ->from('Todo l') ( ->leftJoin('l.TodoTag tt') [id] => 27 ->leftJoin('tt.Tag t'); [name] => Cosas que hacer en Denver print_r($q->fetchArray()); [description] => Cuando hayamos muerto [created_at] => 2010-06-23 20:35:41 //Equivalente [updated_at] => 2010-06-23 20:35:41 $q = Doctrine_Query::create() [TodoTag] => Array ( ->from('Todo l') [0] => Array ->leftJoin('l.Tags t'); ( print_r($q->fetchArray()); [tag_id] => 2 [todo_id] => 27 ● [Tag] => Array Podemos “olvidarnos” ( de la tabla intermedia [id] => 2 [name] => Turismo ) ) ) ) )

Many to many

Jornadas Symfony

http://decharlas.uji.es/symfony

Mucha tela que cortar
Behaviours ● Validadores ● Migraciones ● Herencia ● Caché ● Event listeners ● …

Jornadas Symfony

http://decharlas.uji.es/symfony

Mucha tela que cortar
Behaviours ● Validadores ● Migraciones ● Herencia ● Caché ● Event listeners ● …

¿Pero y Doctrine2?
Veamos Doctrine2
Jornadas Symfony http://decharlas.uji.es/symfony

Doctrine2
Reescritura completa del código para PHP 5.3 ● Mejoras importantes de rendimiento ● Menos magia ● Caché mejorada ● Entidades

Jornadas Symfony

http://decharlas.uji.es/symfony

Entidades (I)
<?php namespace Entities; /** @Entity @Table(name="usuarios") */ class Usuario { /** * @Id @Column(type="integer") * @GeneratedValue(strategy="AUTO") */ private $id; /** @Column(type="string", length=50) */ private $nombre; /** * @OneToOne(targetEntity="Direccion") * @JoinColumn(name="direccion_id", referencedColumnName="id") */ private $direccion;

DocBlock Annotations
Jornadas Symfony http://decharlas.uji.es/symfony

Entidades (II)
public function getId() { return $this->id; } public function getNombre() { return $this->nombre; } public function setNombre($nombre) { $this->nombre = $nombre; } public function getDireccion() { return $this->direccion; } public function setDireccion(Direccion $direccion) { if ($this->direccion !== $direccion) { $this->direccion = $direccion; $direccion->setUsuario($this); } }

}

Jornadas Symfony

http://decharlas.uji.es/symfony

Entidades (III)
No descienden de ninguna clase, están “separadas” del ORM, aunque mapeadas por él ● Menos magia. Es más fácil entender qué está pasando ● Más rápidas ● Herencia ● Gestionadas por el Entity Manager ● Sí, se pueden escribir en YAML y XML ;)

Jornadas Symfony

http://decharlas.uji.es/symfony

Fixtures
$em = $this->getEntityManager(); $user1 = new \Models\Usuario(); $user1->nombre = 'Nacho';

Adiós al YAML. Se escriben en PHP ● ¿Por qué?

Es más rápido cargarlas ● El código para tratar fixtures en YAML introdujo muchos bugs en el pasado

Jornadas Symfony

http://decharlas.uji.es/symfony

persist() y flush()
$user = new \Entities\Usuario; $user->setNombre('Nacho'); $entitymanager->persist($user); $entitymanager->flush();

Atención al uso de espacios de nombre ● Persist “marca” el objeto para guardar ● Flush ejecuta la unidad de trabajo

Jornadas Symfony

http://decharlas.uji.es/symfony

Rendimiento (I)
for ($i=0; $i<1000; $i++){ $user = new \Entities\Usuario; $user->setNombre('Nacho'); $em->persist($user); } $inicio = microtime(true); $em->flush(); $final = microtime(true); echo $final-$inicio."\n";

0.377s

////////////////////////// $inicio = microtime(true); for ($i=0; $i<1000; $i++){ mysql_query("INSERT INTO usuarios (nombre) VALUES ('Nacho')", $link); } $final = microtime(true); echo $final-$inicio."\n";

41.4s

Jornadas Symfony

http://decharlas.uji.es/symfony

Rendimiento (II)
Doctrine2 gestiona las transacciones por nosotros ● Así que es más rápido que código PHP+SQL mal optimizado ● (Por supuesto usar transacciones en PHP+SQL es más rápido que Doctrine2) ● También podemos controlar las transacciones nosotros

Jornadas Symfony

http://decharlas.uji.es/symfony

Eventos Lifecycle
pre/postRemove ● pre/postPersist ● pre/postUpdate ● postLoad : carga desde BD ● loadClassMetadata : carga desde metadatos (annotations, yaml, xml) ● onFlush

/** @Entity @HasLifecycleCallbacks */ class Usuario { //(...) /** @PostPersist */ public function doAlgoOnPostPersist() { $this->nombre = 'Me han cambiado en el postpersist'; }
Jornadas Symfony http://decharlas.uji.es/symfony

Behaviours (I)
En Doctrine2 son código normal de PHP que extiende la funcionalidad base de las entidades
/** @HasLifecycleCallbacks */ class BlogPost { //(...) public function __construct() { $this->created = $this->updated = new DateTime("now"); } /** * @PreUpdate */ public function updated() { $this->updated = new DateTime("now"); }

}

Jornadas Symfony

http://decharlas.uji.es/symfony

Behaviours (II)
¿Pero cómo hacer el código reutilizable entre entidades? Usando interfaces, eventos y código PHP orientado a objetos Ejemplos:
http://github.com/guilhermeblanco/Doctrine2-Sluggable-Functional-Behavior http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior http://www.doctrine-project.org/blog/doctrine2-versionable

Jornadas Symfony

http://decharlas.uji.es/symfony

Migraciones (I)
Hacen el esquema versionable

BD (antes)

BD (después)

Esquema
Jornadas Symfony

Fichero de migración
http://decharlas.uji.es/symfony

Migraciones (I)
Hacen el esquema versionable

BD (antes)
Comparar

BD (después)

Esquema
Jornadas Symfony

Fichero de migración
http://decharlas.uji.es/symfony

Migraciones (I)
Hacen el esquema versionable

BD (antes)
Comparar

BD (después)
Generar

Esquema
Jornadas Symfony

Fichero de migración
http://decharlas.uji.es/symfony

Migraciones (I)
Hacen el esquema versionable

BD (antes)
Comparar

BD (después)
Generar

Esquema
Jornadas Symfony

Fichero de migración
http://decharlas.uji.es/symfony

Migraciones (I)
Hacen el esquema versionable
Migrar

BD (antes)
Comparar

BD (después)
Generar

Esquema
Jornadas Symfony

Fichero de migración
http://decharlas.uji.es/symfony

Migraciones (II)
Aspecto de un fichero de migración
class Version20100416130401 extends AbstractMigration { public function up(Schema $schema) { $table = $schema->createTable('users'); $table->addColumn('username', 'string'); $table->addColumn('password', 'string'); } public function down(Schema $schema) { $schema->dropTable('users'); }

}

Jornadas Symfony

http://decharlas.uji.es/symfony

Migraciones (III)
Gestionadas desde la consola:

migración necesaria para cambiar la BD ● Dry-run: muestra el SQL para cerciorarnos de que es lo que esperamos ● Status: muestra en qué estado (versión, migraciones posibles, fecha...) estamos ● Migrate: ejecuta la migración (hacia adelante o hacia atrás → revertir) ● Write-sql: en lugar de migrar, escribe el SQL a un fichero
Jornadas Symfony http://decharlas.uji.es/symfony

Diff: tras cambiar una entidad, genera la

MongoDB (ODM)
El ODM tiene el mismo aspecto que el ORM (métodos parecidos, Entidad → Documento, EntityManager → DocumentManager,...)

Mañana hay una charla sobre MongoDB y Symfony ;)

Jornadas Symfony

http://decharlas.uji.es/symfony

¿Preguntas?
Si surgen más tarde ;) : nitram.ohcan@gmail.com twitter:@nacmartin http://nacho-martin.com

Jornadas Symfony

http://decharlas.uji.es/symfony