You are on page 1of 42

Jornadas Symfony 5 y 6 de julio 2010

Universitat Jaume I, Castellón


http://decharlas.uji.es/symfony

organizan patrocinan

colaboran
Doctrine
Nacho Martín

5 y 6 de julio 2010
Jornadas Symfony Universitat Jaume I, Castellón
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.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)
Array
$q = Doctrine_Query::create() (
->from('Todo l') [0] => Array
(
->innerJoin('l.Items i');
[id] => 1
[name] => Cosas que hacer en Denver
$lists = $q->execute(); //Record [description] => Cuando hayamos muerto
echo $lists[0]['name']; [created_at] => 2010-06-22 23:55:02
[updated_at] => 2010-06-22 23:55:02
[Items] => Array
$lists = $q->fetchArray(); //Array (
echo $lists[0]['name']; [0] => Array
//O bien $q->execute(array(), (
Doctrine::HYDRATE_ARRAY); [id] => 1
[name] => Comer gambas
print_r($lists); [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();

//Usando link()

Las relaciones son $item = new Item();
intuitivas $item['name'] = "La broma infinita";
$item->link('Todo',array($list['id']));

Siempre podemos recurrir $item->save();
a DQL //Borrar
$list->Items[0]->delete();

Pero en DQL no se
//Siempre nos quedará DQL
ejecutarán preDelete(), $q = Doctrine_Query::create()
postDelete()... (!) ->delete('Item')
->addWhere('todo_id = ?', $list['id'])
->whereIn('name', array($item['name'], 'otro
nombre'));
$q->execute();

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


Many to many 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') (
->leftJoin('l.Tags t'); [0] => Array
print_r($q->fetchArray()); (
[tag_id] => 2
[todo_id] => 27

Podemos “olvidarnos” [Tag] => Array
(
de la tabla intermedia [id] => 2
[name] => Turismo
)
)
)
)
)

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

41.4s
$final = microtime(true);
echo $final-$inicio."\n";

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 Fichero de
migración
Jornadas Symfony http://decharlas.uji.es/symfony
Migraciones (I)
Hacen el esquema versionable

BD (antes) BD (después)
Comparar

Esquema Fichero de
migración
Jornadas Symfony http://decharlas.uji.es/symfony
Migraciones (I)
Hacen el esquema versionable

BD (antes) BD (después)
Comparar
Generar

Esquema Fichero de
migración
Jornadas Symfony http://decharlas.uji.es/symfony
Migraciones (I)
Hacen el esquema versionable

BD (antes) BD (después)
Comparar
Generar

Esquema Fichero de
migración
Jornadas Symfony http://decharlas.uji.es/symfony
Migraciones (I)
Hacen el esquema versionable

Migrar

BD (antes) BD (después)
Comparar
Generar

Esquema Fichero de
migración
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 ;) :


nitram.ohcan@gmail.com
twitter:@nacmartin
http://nacho-martin.com

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

You might also like