symfony & doctrine

27
Symfony & Databases: Doctrine FTW Configuración, creación y mapeo de Entidades, uso bajo DDD @obokaman

Upload: uvinum

Post on 21-Feb-2017

116 views

Category:

Engineering


0 download

TRANSCRIPT

Page 1: Symfony & Doctrine

Symfony & Databases: Doctrine FTWConfiguración, creación y mapeo de Entidades, uso bajo DDD

@obokaman

Page 2: Symfony & Doctrine

¿Qué es Doctrine?

Un conjunto de componentes que nos ofrecen un sistema de persistencia de datos en PHP

Aporta una capa de abstracción entre nuestra aplicación y la base de datos

Compatible tanto con BD relacionales (MySQL, PostgreSQL, SQLite…) como con BD NoSQL (MongoDB, CouchDB…)

Page 3: Symfony & Doctrine

Doctrine ORM

Doctrine DBAL

PDO

Enlaza el modelo relacional de la BD con modelado basado en objetos.

API de abstracción de acceso a basede datos propia de Doctrine

API básica de acceso a base de datos

Page 4: Symfony & Doctrine

DBAL: ¿Qué pinta tiene?

<?phpuse Doctrine\DBAL\Configuration; use Doctrine\DBAL\DriverManager; $config = new Configuration(); $connection_params = [ 'dbname' => 'uvinum', 'user' => 'uvinum', 'password' => 'alpanpanyalvinovino', 'host' => 'localhost', 'drive' => 'pdo_mysql']; $connection = DriverManager::getConnection($connection_params, $config); $statement = $connection->query("SELECT * FROM users WHERE email = :email AND name = :name"); $statement->bindValue(':email', '[email protected]'); $statement->bindValue(':country', 'Albert'); while ($row = $statement->fetch()){ echo '<p>' . $row['username'] . '</p>'; }

Page 5: Symfony & Doctrine

¿Y lo del ORM?Tiene buena pinta…

Page 6: Symfony & Doctrine

ORM: ¿Qué pinta tiene?

$connection_params = [ // ... ]; $config = Setup::createAnnotationMetadataConfiguration(['path/to/entities']); $entity_manager = EntityManager::create($connection_params, $config); $entity_repository = $entity_manager->getRepository(MyEntity::class); $entity = $entity_repository->find(12); $entity->changeEmail('[email protected]'); $entity_manager->persist($entity); $entity_manager->flush();

Page 7: Symfony & Doctrine

ORM

Modelado Code First VS DB First

Transforma datos de la BD a objetos PHP (entidades)… y viceversa.

Permite definir las relaciones entre múltiples entidades y transformarlas en relaciones entre campos de la BD.

Ahorra repetir mismas estructuras de SQL en todos los repositorios.

Aplica automáticamente buenas prácticas (protección SQL Injection, p.e.)

Page 8: Symfony & Doctrine

Un momeeeEento…

Page 9: Symfony & Doctrine

ORM: contras / riesgos

Agrega overhead

Puede ejecutar queries SQL no óptimas (si no se usa adecuadamente)

Alto riesgo de acoplarnos a Doctrine en nuestra aplicación al tratar de sacar el 100% de partido a temas como el Unit of Work.

Page 10: Symfony & Doctrine

ORM: El Entity Manager

Data MapperEl objeto de Doctrine que implementa este patrón es el Entity Manager. Realiza las operaciones en BD relacionadas con las Entities gestionadas. Hidrata los objetos con los datos obtenidos de BD

Unit of WorkEmpleado por el Entity Manager para acceder a la BD de forma transaccional. Mantiene el estado de las entidades gestionadas por el Entity Manager. Permite aplicar a la BD únicamente aquellas operaciones necesarias para reflejar los cambios sufridos por las entidades gestionadas.

Page 11: Symfony & Doctrine

Entidades y mapeo de datos

Page 12: Symfony & Doctrine

Entidades y mapeo de datos

/** * @ORM\Entity * @ORM\Table(name="product") */class Product { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=100) */ private $name; /** * @ORM\Column(type="decimal", scale=2) */ private $price; /** * @ORM\Column(type="text") */ private $description; }

AppBundle\Entity\Product: type: entity table: product id: id: type: integer generator: { strategy: AUTO } fields: name: type: string length: 100 price: type: decimal scale: 2 description: type: text

Annotations YAML

Page 13: Symfony & Doctrine

Custom Types

http://doctrine-orm.readthedocs.io/en/latest/cookbook/custom-mapping-types.html

<?phpuse Doctrine\DBAL\Types\TextType; use Doctrine\DBAL\Platforms\AbstractPlatform; class DeliveryTimeType extends TextType{ const DELIVERY_TIME = 'delivery_time'; public function convertToPHPValue($value, AbstractPlatform $platform) { $value = parent::convertToPHPValue($value, $platform); $value = explode('|', $value); return new DeliveryTime( $value[0], new Hours($value[1]) ); } public function convertToDatabaseValue($value, AbstractPlatform $platform) { return implode( '|', [ $value->shipping_method(), $value->toHours() ] ); } public function getName() { return self::DELIVERY_TIME; } }

Page 14: Symfony & Doctrine

Relaciones entre entidades

One-to-One Por ejemplo, una relación entre un usuario y un carrito.

One-to-ManyPor ejemplo una relación de una categoría con los productos que cuelgan de ella

Many-to-OnePor ejemplo un empleado a su departamento.

Many-to-ManyPor ejemplo una dirección de email a una lista de distribución

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html

Page 15: Symfony & Doctrine

Relaciones entre entidades

/** * @ORM\Entity() */class Company { //... /** * @ORM\OneToOne(targetEntity="Employee", inversedBy="owned_company", cascade={"remove"}) */ private $owner; /** * @ORM\OneToMany(targetEntity="Department", mappedBy="company", cascade={"remove"}) */ private $departments; /** * @ORM\OneToMany(targetEntity="Employee", mappedBy="company", cascade={"remove"}) */ private $employees; /** * @ORM\ManyToMany(targetEntity="Employee", inversedBy="managed_companies", cascade={"remove"}) * @ORM\JoinTable(name="company_manager") */ private $managers;

Page 16: Symfony & Doctrine

Integración en Symfony

Page 17: Symfony & Doctrine

Configuración

doctrine: dbal: driver: pdo_sqlite host: "%database_host%" port: "%database_port%" dbname: "%database_name%" user: "%database_user%" password: "%database_password%" charset: UTF8 path: "%database_path%" orm: auto_generate_proxy_classes: "%kernel.debug%" naming_strategy: doctrine.orm.naming_strategy.underscore auto_mapping: true

app/config/config.yml

parameters: database_host: 127.0.0.1 database_port: ~ database_name: symfony database_user: root database_password: ~ database_path: '%kernel.root_dir%/db.db3'

app/config/parameters.yml.dist

Page 18: Symfony & Doctrine

Entidades de dominio != Entidades Doctrine

Nuestro dominio no debería conocer detalles de implementación / infraestructura (Acoplamiento a ArrayCollections, p.e.)

Reutilizar las entidades de Doctrine puede obligarnos a aplicar cambios a nuestro Domain Model (collections de entidades relacionadas)

Métodos de nuestras entidades podrían ejecutar “mágicamente” lógica de Doctrine. (Lazy loading de entidades relacionadas, p.e.)

Page 19: Symfony & Doctrine

Entidades de dominio != Entidades Doctrine

Page 20: Symfony & Doctrine

Entidades de dominio != Entidades Doctrine

Page 21: Symfony & Doctrine

Entidades de dominio != Entidades Doctrine

Page 22: Symfony & Doctrine

Solución propuesta: Separación y mapeo mediante nuestros repositorios

class UserRepository implements UserRepositoryContract { /** @var EntityManager */ private $em; /** @var DoctrineUserRepository */ private $repo; public function __construct(EntityManager $an_entity_manager) { $this->em = $an_entity_manager; $this->repo = $this->em->getRepository(DoctrineUser::class); } public function find(UserId $a_user_id) { $result = $this->repo->find((string) $a_user_id); return $this->hydrateItem($result); } private function hydrateItem(DoctrineUser $result = null) { if (empty($result)) return null; $creation_date = \DateTimeImmutable::createFromMutable($result->getCreationDate()); $user = new User( new UserId($result->getId()), $result->getName(), new Email($result->getEmail()), $creation_date ); return $user; } }

Page 23: Symfony & Doctrine

Comandos útiles

$ bin/console doctrine:create:database (--force)crea la base de datos (vacía) en base a la configuración de la aplicación

$ bin/console doctrine:drop:database (--force)elimina la base de datos a la que se referencia en la configuración

$ bin/console doctrine:schema:update (--force)aplica las modificaciones necesarias al esquema de BD para que encaje con el modelado y el mapeo de campos y relaciones de los entities actuales

$ bin/console doctrine:mapping:info nos muestra todas las entidades mapeadas actualmente con Doctrine

Page 24: Symfony & Doctrine

Herramientas interesantes

FixturesLas Fixtures se usan para cargar en la BD una serie de datos iniciales o de prueba que nos permitan disponer de una versión inicial “usable” de nuestra aplicación, lista para entornos de desarrollo o testing.http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

MigrationsLas Migrations añaden la posibilidad de automatizar la aplicación de las queries necesarias para modificar la estructura de BD para que siga las modificaciones realizadas al mapeo de entidades y sus relaciones, así como para mover los datos necesarios a las nuevas estructuras de datos. http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html

Page 25: Symfony & Doctrine

Comandos útiles

$ bin/console doctrine:fixtures:load ejecuta las Fixtures que estén disponibles

$ bin/console doctrine:migrations:diff crea una clase Migration que contiene las queries necesarias para migrar los datos de la BD actual para que cumpla con el modelado y mapeado de datos de las Entities actuales.

$ bin/console doctrine:migrations:execute 201609…ejecuta una migración de datos contenida en determinada clase Migration

Page 26: Symfony & Doctrine

Mola, pero…

Vamos a meterle mano al código

Page 27: Symfony & Doctrine

Workshop

1.Aplicaremos los cambios necesarios al proyecto en symfony_playground para gestionar nuestra entity User con el ORM de Doctrine

2.Aparece una nueva Entity “Skills”. Cada usuario debería poder tener una lista de Skills (habilidades). No se podrán “compartir” skills entre usuarios. Las skills pertenecerán únicamente a un usuario. Si eliminamos un usuario, deberán eliminarse también sus skills.