34063724 Doctrine Nacho Martin Jornadas Symfony en Castellon Julio 2010

42
organizan patrocinan colaboran Jornadas Symfony 5 y 6 de julio 2010 Universitat Jaume I, Castellón http://decharlas.uji.es/symfony

Transcript of 34063724 Doctrine Nacho Martin Jornadas Symfony en Castellon Julio 2010

organizan patrocinan

colaboran

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

http://decharlas.uji.es/symfony

DoctrineNacho Martín

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

http://decharlas.uji.es/symfony

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

¿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.phppublic function setup(){ $this->enablePlugins(array('sfDoctrinePlugin')); $this->disablePlugins(array('sfPropelPlugin'));}

#config/databases.ymlall: 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 YAMLTodo: 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) }

4

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

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

$q = Doctrine_Query::create() ->select('l.*, i.name, t.name') ->from('Todo l') ->innerJoin('l.Items i') ->leftJoin('l.Tags t');echo $q->getSqlQuery();

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

Objetos

$list = new Todo();

//Manipulación$list->name = "Cosas que hacer en Denver";$list['name'] = "Cosas que hacer en Denver";$list->set('name', "Cosas que hacer en Denver");

//Accesoecho $list->name;echo $list['name']; //Recomendado (hidratación)echo $list->get('name');

Métodos de acceso y manipulación

123

123

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)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 ( … ) ) ))

$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);

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/gettersclass 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();

//Usando link() $item = new Item(); $item['name'] = "La broma infinita"; $item->link('Todo',array($list['id'])); $item->save(); //Borrar $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();

●Las relaciones son intuitivas

●Siempre podemos recurrir a DQL

●Pero en DQL no se ejecutarán preDelete(), postDelete()... (!)

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

Many to many$q = Doctrine_Query::create() ->from('Todo l') ->leftJoin('l.TodoTag tt') ->leftJoin('tt.Tag t');print_r($q->fetchArray());

//Equivalente$q = Doctrine_Query::create() ->from('Todo l') ->leftJoin('l.Tags t');print_r($q->fetchArray());

Array( [0] => Array ( [id] => 27 [name] => Cosas que hacer en Denver [description] => Cuando hayamos muerto [created_at] => 2010-06-23 20:35:41 [updated_at] => 2010-06-23 20:35:41 [TodoTag] => Array ( [0] => Array ( [tag_id] => 2 [todo_id] => 27 [Tag] => Array ( [id] => 2 [name] => Turismo ) ) ) ))

● Podemos olvidarnos “ ”de la tabla intermedia

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● …

Mucha tela que cortar

¿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";

//////////////////////////$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";

0.377s

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

http://github.com/guilhermeblanco/Doctrine2-Sluggable-Functional-Behavior

http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior http://www.doctrine-project.org/blog/doctrine2-versionable

Ejemplos:

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

Migraciones (I)Hacen el esquema versionable

BD (antes) BD (después)

Esquema Fichero demigración

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

Migraciones (I)Hacen el esquema versionable

BD (antes) BD (después)

Esquema

Comparar

Fichero demigración

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

Migraciones (I)Hacen el esquema versionable

BD (antes) BD (después)

Esquema

Comparar

Fichero demigración

Generar

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

Migraciones (I)Hacen el esquema versionable

BD (antes) BD (después)

Esquema

Comparar

Fichero demigración

Generar

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

Migraciones (I)Hacen el esquema versionable

BD (antes) BD (después)

Esquema

Comparar

Fichero demigración

Generar

Migrar

Jornadas Symfony 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:

● Diff: tras cambiar una entidad, genera la 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

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 ;) :[email protected]

twitter:@nacmartinhttp://nacho-martin.com