symfony & doctrine

Post on 21-Feb-2017

103 Views

Category:

Engineering

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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

@obokaman

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

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

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', 'albert.garcia@uvinum.com'); $statement->bindValue(':country', 'Albert'); while ($row = $statement->fetch()){ echo '<p>' . $row['username'] . '</p>'; }

¿Y lo del ORM?Tiene buena pinta…

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('new@email.com'); $entity_manager->persist($entity); $entity_manager->flush();

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.)

Un momeeeEento…

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.

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.

Entidades y mapeo de datos

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

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

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

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;

Integración en Symfony

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

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.)

Entidades de dominio != Entidades Doctrine

Entidades de dominio != Entidades Doctrine

Entidades de dominio != Entidades 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; } }

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

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

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

Mola, pero…

Vamos a meterle mano al código

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.

top related