Symfony & Doctrine
-
Upload
uvinum -
Category
Engineering
-
view
116 -
download
0
Transcript of Symfony & Doctrine
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', '[email protected]'); $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('[email protected]'); $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.