L'ORM Doctrine offre beaucoup plus de flexibilité qu'il n'y paraît. Dans cette présentation, nous allons nous intéresser à son fonctionnement interne et à ses fonctionnalités moins connues, pour découvrir comment mieux l'utiliser. Au programme, évènements et listeners, filtres, tracking policy, mais aussi des astuces sur des architectures possibles pour son code...
Thanks to the new capabilities of the web platform (web components, Progressive Web Apps…) and the rise of modern JS libraries (Vue, React, Angular) almost all modern Symfony applications must leverage the frontend ecosystem.
Symfony 4 embed many gems that make it easy to integrate modern JavaScript within the framework, including the first component entirely written in JS: Webpack Encore.
In Symfony 4.2, another component that is super convenient for apps containing JS code has been released: Panther, a PHP library compatible with BrowserKit, that drives real web browsers to create end-to-end (E2E) tests with ease.
During this talk, I'll show you how to cleanly integrate modern JavaScript code with Symfony and Twig and how to test such applications using Panther.
The examples will use VueJS, because it’s probably the easiest JS framework to get started with as a PHP developer, but all the tips and tricks will be applicable with other libraries such as React or Angular.
Finally, we'll add some real time capabilities to our app using Mercure.rocks
Thanks to the new capabilities of the web platform (web components, Progressive Web Apps…) and the rise of modern JS libraries (Vue, React, Angular) almost all modern Symfony applications must leverage the frontend ecosystem.
Symfony 4 embed many gems that make it easy to integrate modern JavaScript within the framework, including the first component entirely written in JS: Webpack Encore.
In Symfony 4.2, another component that is super convenient for apps containing JS code has been released: Panther, a PHP library compatible with BrowserKit, that drives real web browsers to create end-to-end (E2E) tests with ease.
During this talk, I'll show you how to cleanly integrate modern JavaScript code with Symfony and Twig and how to test such applications using Panther.
The examples will use VueJS, because it’s probably the easiest JS framework to get started with as a PHP developer, but all the tips and tricks will be applicable with other libraries such as React or Angular.
Finally, we'll add some real time capabilities to our app using Mercure.rocks
Running Kubernetes Across Multiple AWS Accounts (CON409) - AWS re:Invent 2018Amazon Web Services
This session covers how a Kubernetes cluster can be run over multiple AWS accounts to separate the control plane from the worker nodes and increase security, separate concerns, and isolate workloads. Amazon Elastic Container Service for Kubernetes (Amazon EKS) manages the Kubernetes control plane and recommends that customers launch worker nodes in their accounts. We cover in detail how we made this topology possible, the challenges we faced, and how we solved it.
Introduction to Spring Framework and Spring IoCFunnelll
An introduction to the building blocks of the Spring framework. The presentation focuses on Spring Inverse of Control Container (IoC) ,how it used in the LinkedIn stack, how it integrates with other frameworks and how it works with your JUnit testing.
Today, there are several trends that are forcing application architectures to evolve. Users expect a rich, interactive and dynamic user experience on a wide variety of clients including mobile devices. Applications must be highly scalable, highly available and run on cloud environments. Organizations often want to frequently roll out updates, even multiple times a day. Consequently, it’s no longer adequate to develop simple, monolithic web applications that serve up HTML to desktop browsers.
This site describes a new, alternative architecture: microservices. Applications with a microservice architecture consist of a set of narrowly focused, independently deployable services. Read on to find out more about this approach and its associated trade-offs. A good starting point is the Monolithic Architecture pattern.
Docker is an open-source project that automates the deployment of applications inside software containers, by providing an additional layer of abstraction and automation of operating-system-level virtualization on Linux.[5] Docker uses resource isolation features of the Linux kernel such as cgroups and kernel namespaces to allow independent "containers" to run within a single Linux instance, avoiding the overhead of starting and maintaining virtual machines.
Le tutoriel Selenium fournit des concepts de base et avancés de Selenium.
Selenium est l'une des suites de tests d'automatisation de l'interface utilisateur Web (interface utilisateur) open source les plus utilisées.
Le tutoriel Selenium comprend tous les sujets de Selenium tels que les fonctionnalités, Selenium IDE, Selenium WebDriver, les fonctionnalités WebDriver, WebDriver vs RC, l'installation de WebDriver, etc.
Presentation explain about
Spring Boot vs Spring vs Spring MVC,
Advantages,
Where to start and how does Spring boot work ?,
Dependency Management,
Logging,
Exception Handling,
Database Handling.
in Spring boot.
APIs are transforming the world at an increasing pace. Every day several APIs are developed and adapted based on the needs of the organizations. With the usage of these APIs, developers can create quality applications.
As a Tester, you might be covering the exhaustive functional Testing of your APIs.
Your APIs might be working as designed on a normal day. How about there’s a sale or any worldwide changes which cause an unexpected number of API calls? Probably your APIs should handle it. But will it work? That’s why we need performance testing.
An inefficient API could lead to a slow-running application and will lose potential users. Most performance problems revolve around speed, response time, load time, and poor scalability. Speed is often one of the most important attributes of an API.
Some might be wondering what are tools available for performance testing of our APIs. There are plenty of tools available. I will be demonstrating a few important types of performance testing to be done for your APIs before moving into Production.
Talk Takeaways
Importance of Performance testing for APIs
Types of Performance testing to be focused on your APIs
Demo with a few tools open-source tools
Analysis of the Performance Test reports
Developing event-driven microservices with event sourcing and CQRS (svcc, sv...Chris Richardson
Modern, cloud-native applications typically use a microservices architecture in conjunction with NoSQL and/or sharded relational databases. However, in order to successfully use this approach you need to solve some distributed data management problems including how to maintain consistency between multiple databases without using 2PC.
In this talk you will learn more about these issues and how to solve them by using an event-driven architecture. We will describe how event sourcing and Command Query Responsibility Segregation (CQRS) are a great way to realize an event-driven architecture. You will learn about a simple yet powerful approach for building, modern, scalable applications.
Running Kubernetes Across Multiple AWS Accounts (CON409) - AWS re:Invent 2018Amazon Web Services
This session covers how a Kubernetes cluster can be run over multiple AWS accounts to separate the control plane from the worker nodes and increase security, separate concerns, and isolate workloads. Amazon Elastic Container Service for Kubernetes (Amazon EKS) manages the Kubernetes control plane and recommends that customers launch worker nodes in their accounts. We cover in detail how we made this topology possible, the challenges we faced, and how we solved it.
Introduction to Spring Framework and Spring IoCFunnelll
An introduction to the building blocks of the Spring framework. The presentation focuses on Spring Inverse of Control Container (IoC) ,how it used in the LinkedIn stack, how it integrates with other frameworks and how it works with your JUnit testing.
Today, there are several trends that are forcing application architectures to evolve. Users expect a rich, interactive and dynamic user experience on a wide variety of clients including mobile devices. Applications must be highly scalable, highly available and run on cloud environments. Organizations often want to frequently roll out updates, even multiple times a day. Consequently, it’s no longer adequate to develop simple, monolithic web applications that serve up HTML to desktop browsers.
This site describes a new, alternative architecture: microservices. Applications with a microservice architecture consist of a set of narrowly focused, independently deployable services. Read on to find out more about this approach and its associated trade-offs. A good starting point is the Monolithic Architecture pattern.
Docker is an open-source project that automates the deployment of applications inside software containers, by providing an additional layer of abstraction and automation of operating-system-level virtualization on Linux.[5] Docker uses resource isolation features of the Linux kernel such as cgroups and kernel namespaces to allow independent "containers" to run within a single Linux instance, avoiding the overhead of starting and maintaining virtual machines.
Le tutoriel Selenium fournit des concepts de base et avancés de Selenium.
Selenium est l'une des suites de tests d'automatisation de l'interface utilisateur Web (interface utilisateur) open source les plus utilisées.
Le tutoriel Selenium comprend tous les sujets de Selenium tels que les fonctionnalités, Selenium IDE, Selenium WebDriver, les fonctionnalités WebDriver, WebDriver vs RC, l'installation de WebDriver, etc.
Presentation explain about
Spring Boot vs Spring vs Spring MVC,
Advantages,
Where to start and how does Spring boot work ?,
Dependency Management,
Logging,
Exception Handling,
Database Handling.
in Spring boot.
APIs are transforming the world at an increasing pace. Every day several APIs are developed and adapted based on the needs of the organizations. With the usage of these APIs, developers can create quality applications.
As a Tester, you might be covering the exhaustive functional Testing of your APIs.
Your APIs might be working as designed on a normal day. How about there’s a sale or any worldwide changes which cause an unexpected number of API calls? Probably your APIs should handle it. But will it work? That’s why we need performance testing.
An inefficient API could lead to a slow-running application and will lose potential users. Most performance problems revolve around speed, response time, load time, and poor scalability. Speed is often one of the most important attributes of an API.
Some might be wondering what are tools available for performance testing of our APIs. There are plenty of tools available. I will be demonstrating a few important types of performance testing to be done for your APIs before moving into Production.
Talk Takeaways
Importance of Performance testing for APIs
Types of Performance testing to be focused on your APIs
Demo with a few tools open-source tools
Analysis of the Performance Test reports
Developing event-driven microservices with event sourcing and CQRS (svcc, sv...Chris Richardson
Modern, cloud-native applications typically use a microservices architecture in conjunction with NoSQL and/or sharded relational databases. However, in order to successfully use this approach you need to solve some distributed data management problems including how to maintain consistency between multiple databases without using 2PC.
In this talk you will learn more about these issues and how to solve them by using an event-driven architecture. We will describe how event sourcing and Command Query Responsibility Segregation (CQRS) are a great way to realize an event-driven architecture. You will learn about a simple yet powerful approach for building, modern, scalable applications.
jQuery est une bibliothèque JavaScript libre qui porte sur l'interaction entre JavaScript (comprenant Ajax) et HTML, et a pour but de simplifier des commandes communes de JavaScript.
Atelier présenté par SMAHI Zakaria.
UX Day, Semaine du Web.
Cette présentation a pour but de faire office de boite à outils du developpeur elgg. Pour le moment ce n'est qu'un brouillon n'hèsitez pas à laisser des commentaires si vous pensez qu'il manque quelque chose.
Vous avez déjà travaillé avec de vieux projet PHP (3,4), du “include-ception” ou tout simplement un framework non PSR-0? Voici un retour sur les étapes employé dans différent cas de migration de “legacy” vers Symfony 2.
Open close principle, on a dit étendre, pas extends !Engineor
Conférence en français sur le principe d'ouverture fermeture proposé à l'AFUP de Paris en février 2019, comportant des exemples d'implémentation du principe à l'aide de design patterns, avec ou sans utilisation d'un framework.
Quelle place pour le framework Rails dans le développement d'application web5pidou
Cette présentation a été réalisée dans le cadre des Rencontres Mondiales décentralisées du Logiciel Libre (RMLLd) qui se sont déroulées du 1er au 3 Juillet 2011 à Saint-Joseph (Ile de la Réunion).
6. Une requête basique
$blog = $entityManager->getRepository(Blog::class)->find(1);
— Doctrine ORM regarde dans le mapping (ClassMetadata) quel est
l'identifiant (ID) de notre entité
7. Une requête basique
$blog = $entityManager->getRepository(Blog::class)->find(1);
— Doctrine ORM regarde dans le mapping (ClassMetadata) quel est
l'identifiant (ID) de notre entité
— dans le cas d'une requête par ID, il regarde dans l'identityMap si
l'entité n'a pas déjà été chargée
8. Une requête basique
$blog = $entityManager->getRepository(Blog::class)->find(1);
— Doctrine ORM regarde dans le mapping (ClassMetadata) quel est
l'identifiant (ID) de notre entité
— dans le cas d'une requête par ID, il regarde dans l'identityMap si
l'entité n'a pas déjà été chargée
— si non, Doctrine va générer une requête SQL
9. Une requête basique
$blog = $entityManager->getRepository(Blog::class)->find(1);
— Doctrine ORM regarde dans le mapping (ClassMetadata) quel est
l'identifiant (ID) de notre entité
— dans le cas d'une requête par ID, il regarde dans l'identityMap si
l'entité n'a pas déjà été chargée
— si non, Doctrine va générer une requête SQL
— Doctrine DBAL va l'exécuter
10. Une requête basique
$blog = $entityManager->getRepository(Blog::class)->find(1);
— Doctrine ORM regarde dans le mapping (ClassMetadata) quel est
l'identifiant (ID) de notre entité
— dans le cas d'une requête par ID, il regarde dans l'identityMap si
l'entité n'a pas déjà été chargée
— si non, Doctrine va générer une requête SQL
— Doctrine DBAL va l'exécuter
— Doctrine ORM va construire un objet à partir du résultat
(hydratation)
11. Une requête basique
$blog = $entityManager->getRepository(Blog::class)->find(1);
— Doctrine ORM regarde dans le mapping (ClassMetadata) quel est
l'identifiant (ID) de notre entité
— dans le cas d'une requête par ID, il regarde dans l'identityMap si
l'entité n'a pas déjà été chargée
— si non, Doctrine va générer une requête SQL
— Doctrine DBAL va l'exécuter
— Doctrine ORM va construire un objet à partir du résultat
(hydratation)
— elle sera ajoutée à l'identityMap, puis retournée
12. Changer l'hydratation
Plusieurs modes sont disponibles :
$result = query->getResult(Query::HYDRATE_OBJECT); // Blog, objet construit par Reflection
$result = query->getResult(Query::HYDRATE_ARRAY); // tableau associatif avec id, name...
$result = query->getResult(Query::HYDRATE_SCALAR); // tableaux avec b_id, b_name... (non-dédupliqué !)
$result = query->getResult(Query::HYDRATE_SINGLE_SCALAR); // non supporté ici
$result = query->getResult(Query::HYDRATE_SIMPLEOBJECT); // Blog mais sans objets joints (1-to-1) (!)
Pourquoi?
Principalement pour des raisons de performance, l'hydratation en
objet, surtout avec des objets associés, est très lourde.
❗
pour HYDRATE_SIMPLEOBJECT, surtout si vos getters sont typés
13. Une autre optimisation : partial object
// Dans BlogRepository
$query = $this->createQueryBuilder('b')
->select('PARTIAL b.{id, name}')
->where('b.name = :name')
->setParameter('name', 'Romaric')
->getQuery();
$blogs = query->getResult();
dump($blogs[0] instanceof Blog); // true
dump($blogs[0]->getId()); // 1
dump($blogs[0]->getName()); // Romaric
dump($blogs[0]->getDescription()); // null
❗
Tous les autres champs seront null, les associations et collections
auront des proxys non initialisés / vides.
14. Ou encore : les Partial References
Lorsqu'on veut une entité que pour son ID, il est possible d'utiliser un
autre type d'objet partiel, une référence :
$blog1 = $entityManager->getPartialReference(Blog::class, 1);
dump($blog1 instanceof Blog); // true
dump($blog1->getId()); // 1
$articles = $entityManager->getRepository(Article::class)
->findBy(['blog' => $blog1]);
15. Relations / associations : base
Dans une relation, il y a l'owning side (requis), et l'inverse side.
/** @ORMEntity */
class Article
{
/**
* @ORMManyToOne(targetEntity="Blog", inversedBy="articles")
*/
private $blog;
}
/** @ORMEntity */
class Blog
{
/**
* @ORMOneToMany(targetEntity="Article", mappedBy="blog",
* cascade={"remove"},
* onDelete="CASCADE",
* orphanRemoval=true)
*/
private $articles;
}
16. Relations / associations : base
Dans une relation, il y a l'owning side (requis), et l'inverse side.
/** @ORMEntity */
class Article
{
/**
* @ORMManyToOne(targetEntity="Blog", inversedBy="articles")
*/
private $blog;
}
/** @ORMEntity */
class Blog
{
/**
* @ORMOneToMany(targetEntity="Article", mappedBy="blog",
* cascade={"remove"},
* onDelete="CASCADE",
* orphanRemoval=true)
*/
private $articles;
}
17. Relations / associations : base
Dans une relation, il y a l'owning side (requis), et l'inverse side.
/** @ORMEntity */
class Article
{
/**
* @ORMManyToOne(targetEntity="Blog", inversedBy="articles")
*/
private $blog;
}
/** @ORMEntity */
class Blog
{
/**
* @ORMOneToMany(targetEntity="Article", mappedBy="blog",
* cascade={"remove"},
* onDelete="CASCADE",
* orphanRemoval=true)
*/
private $articles;
}
18. Relations / associations : base
Dans une relation, il y a l'owning side (requis), et l'inverse side.
/** @ORMEntity */
class Article
{
/**
* @ORMManyToOne(targetEntity="Blog", inversedBy="articles")
*/
private $blog;
}
/** @ORMEntity */
class Blog
{
/**
* @ORMOneToMany(targetEntity="Article", mappedBy="blog",
* cascade={"remove"},
* onDelete="CASCADE",
* orphanRemoval=true)
*/
private $articles;
}
19. Relations et proxy
Dans la mesure du possible, Doctrine propose du lazy
loading, c'est-à-dire de mettre soit un proxy soit une
PersistentCollection à la place de(s) entité(s) jointe(s).
Type Côté Peut être proxy ?
One-to-One Owning Oui (ou null)
One-to-One Inverse Jamais ❗
Many-to-One Owning Oui (ou null)
Many-to-One (One-to-Many) Inverse Oui, Collection
Many-to-Many Owning Oui, Collection
Many-to-Many Inverse Oui, Collection
20. Lazy-loading...
$blogs = $entityManager->getRepository(Blog::class)->findAll();
// Chaque Blog a des articles (One-to-Many),
// et des contributeurs/authors (One-to-Many)
foreach ($blogs as $blog) {
foreach ($blog->getArticles() as $article) {
$title = $article->getTitle();
// ...
}
foreach ($blog->getAuthors() as $author) {
$name = $author->getName();
// ...
}
}
22. N+1 : une possible solution
On peut demander à Doctrine de récupérer en même temps les
articles et les contributeurs :
$blogs = $this->createQueryBuilder('blog')
->addSelect('article, author')
->join('blog.articles', 'article')
->join('blog.authors', 'author')
->getQuery()
->getResult();
❗
on garde le problème du coût de l'hydratation (coût en O(n*m*q) ici,
avec n blogs, m articles et q contributeurs).
23. Solution2
: multi-step hydratation
$blogs = $entityManager->createQuery('
SELECT blog, article
FROM Blog blog
LEFT JOIN blog.articles article
')
->getResult();
$entityManager->createQuery('
SELECT PARTIAL blog.{id}, author
FROM Blog blog
LEFT JOIN blog.authors author
')
->getResult(); // Résultat inutile
$blogs[0]->getArticles()->first()->getTitle(); // Ne déclenche pas de requête
2
Plus de détails sur le blog de Marco Pivetta (Ocramius), exemples sur Github ici
24. ❗
Note sur les proxies
// Un Blog a un Logo (One-To-One, owning side)
$logo = $blog->getLogo();
// Les proxies supportent mal la sérialisation
$str = serialize($logo);
$logo2 = unserialize($str); //
!
Error at offset 0...
// À la place, soit charger le proxy
if ($logo instanceof DoctrineORMProxyProxy) {
$logo->__load();
}
$logo2 = unserialize(serialize($logo)); //
// Soit utiliser l'identité
$logoId = unserialize(serialize($logo->getId()));
25. Requêtage, astuce : les Criteria
class Blog
{
public function getDraftArticles(): Collection
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq('status', 'draft'))
->orderBy(['position' => Criteria::ASC]);
return $this->articles->matching($criteria);
}
}
Si la collection n'est pas chargée, une requête SQL avec un WHERE sera
générée, sinon le filtrage aura lieu sur les éléments en mémoire.
26. Astuce 2 : appliquer un filtre à toutes les requêtes
class NoDraftFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $entityMetadata, $targetTableAlias)
{
if (Article::class !== $entityMetadata->reflClass->getName()) {
return '';
}
return $targetTableAlias.'.status != 'draft''; // DQL qui sera injecté dans le WHERE
}
}
orm:
entity_managers:
default:
filters:
draft_filter:
class: AppFilterNoDraftFilter
enabled: true
$publishedArticles = $entityManager->getRepository(Article::class)->findAll();
$entityManager->getFilters()->disable('draft_filter');
$allArticles = $entityManager->getRepository(Article::class)->findAll();
27. Astuce 3 : organiser ses repositories...
class ArticleRepository
{
public function findOnlineArticlesIWroteOnBlog(User $user, Blog $blog)
{
$queryBuilder = $this->createQueryBuilder('a');
self::withIsOnline($queryBuilder, 'a');
self::withIWrote($queryBuilder, 'a', $user);
self::withFromBlog($queryBuilder, 'a', $blog);
return $queryBuilder->getQuery()->getResult();
}
private static function withIsOnline(QueryBuilder $queryBuilder, string $alias)
{
$queryBuilder
->andWhere($alias.'.status != 'draft'')
->andWhere($alias.'.publishOn >= CURRENT_TIMESTAMP()')
;
}
// withIWrote(), withFromBlog(), etc
}
28. ...ou construire des requêtes complexes
class ArticleQueryBuilderBuilder
{
private $queryBuilder;
private $tableAlias;
public function __construct(QueryBuilder $queryBuilder, string $tableAlias)
{
$this->queryBuilder = $queryBuilder;
$this->tableAlias = $tableAlias;
}
public function withIsOnline()
{
$this->queryBuilder
->andWhere($this->tableAlias.'.status != 'draft'')
->andWhere($this->tableAlias.'.publishOn >= CURRENT_TIMESTAMP()');
}
// withIWrote(), withFromBlog(), etc
}
$articles = $entityManager->getRepository(Article::class)
->getArticleQueryBuilderBuilder() // À ajouter dans ArticleRepository
->withIsOnline()
->withIWrote($user)
->withFromBlog($blog)
->getQueryBuilder()->getQuery()->getResult();
31. Cycle de Vie d'un persist()
$article = new Article();
// Doctrine détecte qu'il ne connaît pas l'entité,
// il va l'ajouter dans UnitOfWork::entityInsertions
$entityManager->persist($article);
// Maintenant, Doctrine va synchroniser l'UnitOfWork avec la BDD :
// il regarde s'il y a de nouvelles entités,
// ouvre une transaction,
// génère et exécute un INSERT SQL,
// puis commit et l'UnitOfWork se "nettoie"
$entityManager->flush();
32. Cycle de Vie d'un persist()
$article = new Article();
// Doctrine détecte qu'il ne connaît pas l'entité,
// il va l'ajouter dans UnitOfWork::entityInsertions
$entityManager->persist($article);
// Maintenant, Doctrine va synchroniser l'UnitOfWork avec la BDD :
// il regarde s'il y a de nouvelles entités,
// ouvre une transaction,
// génère et exécute un INSERT SQL,
// puis commit et l'UnitOfWork se "nettoie"
$entityManager->flush();
33. Cycle de Vie d'un persist()
$article = new Article();
// Doctrine détecte qu'il ne connaît pas l'entité,
// il va l'ajouter dans UnitOfWork::entityInsertions
$entityManager->persist($article);
// Maintenant, Doctrine va synchroniser l'UnitOfWork avec la BDD :
// il regarde s'il y a de nouvelles entités,
// ouvre une transaction,
// génère et exécute un INSERT SQL,
// puis commit et l'UnitOfWork se "nettoie"
$entityManager->flush();
34. Évènements
Action Quand ? Évènements
Nouvelle entité EM:flush() prePersist et postPersist
Mise à jour EM:flush() preUpdate et postUpdate
Suppression EM:flush() preRemove et postRemove
Toujours EM:flush() preFlush, onFlush et postFlush
Lecture de la BDD find()... postLoad
Première opération find(), EM:persist()... loadClassMetadata
Nettoyage EM:clear() onClear
35. Lifecycle callbacks (1/3)
use DoctrineCommonPersistenceEventPreUpdateEventArgs;
/**
* @ORMEntity
* @ORMHasLifecycleCallbacks
*/
class Blog
{
/** @ORMPreUpdate */
public function onPreUpdate(PreUpdateEventArgs $event)
{
$this->updatedAt = new DateTimeImmutable();
}
}
36. Events listeners/subscriber (2/3)
use DoctrineORMEvents;
use DoctrineCommonEventSubscriber;
use DoctrineCommonPersistenceEventLifecycleEventArgs;
class LocalizationPersister implements EventSubscriber
{
public function getSubscribedEvents()
{
return [Events::prePersist];
}
public function onPrePersist(LifecycleEventArgs $args)
{
if (!$args->getObject() instanceof Article) {
return;
}
$args->getObject()->setLocale('fr'); // ou injectée...
}
}
37. Entity listeners3
⭐
(3/3)
use DoctrineCommonPersistenceEventLifecycleEventArgs;
class BlogLogoListener
{
public function preRemove(Logo $logo, LifecycleEventArgs $args)
{
unlink($logo->getPath());
}
}
services:
blog_logo_listener:
class: AppListenerBlogLogoListener
tags:
- { name: doctrine.orm.entity_listener, event: preRemove, entity: AppEntityLogo }
3
Syntaxe avec Doctrine 2.5+.
❗
La documentation n'est pas très claire pour l'instant.
39. Lors du flush()
$article->setTitle('Retex SymfonyLive 2019');
$entityManager->flush($article); // Ou flush() tout court
Par défaut, pas besoin de persist() !
Doctrine va regarder si chaque champ de chaque entité a été modifié.
Il est possible de changer cela, c'est-à-dire la tracking policy.
40. Les tracking policies
Deferred Implicit : stratégie par défaut. Doctrine garde en mémoire les
valeurs récupérées de la BDD, et lors du flush() va comparer chaque
champ des entités qu'il connaît pour voir s'il a été modifié.
❗
Peut utiliser beaucoup de ressource.
Deferred Explicit : seules les entités explicitement persistées
($entityManager->persist($article)) ont leurs champs comparés.
❗
Utilise moins de ressources, mais attention aux cascades.
Notify : chaque entité doit signaler ses modifications à un listener.
Le plus optimisé, mais lourd à mettre en place.
41. Exemple avec Deferred explicit
/**
* @ORMEntity
* @ORMChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class Article
{
// ...
}
$article->setTitle('Hello World');
$entityManager->persist($article);
$entityManager->flush();
❗
Il faut appeler exactement $entityManager->persist() sur chaque
entité, cascade: {"persist"} dans les annotations ne suffit pas. Donc il
faut pouvoir/vouloir accéder à chaque entité depuis son contrôleur...
42. Listeners : particularité de l'update
use DoctrineCommonPersistenceEventPreUpdateEventArgs;
class BlogListener
{
public function preUpdate(Blog $blog, PreUpdateEventArgs $args)
{
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name')) {
// Attention de cette manière seuls les champs déjà modifiés peuvent être remodifiés
$eventArgs->setNewValue('name', 'Nouveau nom: '.$eventArgs->getNewValue());
$blog->setSlug(canonicalize($blog->getName()));
// Si on souhaite modifier une autre propriété, il faut lancer une recomparaison
$classMetadata = $args->getEntityManager()->getClassMetadata(Blog::class);
$args->getEntityManager()->getUnitOfWork()->recomputeSingleEntityChangeSet($classMetadata, $blog);
}
}
}
❗
On ne peut pas créer de nouvelles entités ici.
43. Listeners : créer de nouvelles entités
class BlogSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return [Events::onFlush];
}
public function onFlush(OnFlushEventArgs $args)
{
$entityManager = $args->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
foreach ($unitOfWork->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof Blog) {
$changeset = $unitOfWork->getEntityChangeSet($entity);
if (isset($changeset['name'])) {
$notification = new Notification(); // etc
$entityManager->persist($notification);
// On ne peut pas déclencher de flush() - on doit recompute "à la main"
$classMetadata = $entityManager->getClassMetadata(Notification::class);
$unitOfWork->computeChangeSet($classMetadata, $notification);
}
}
}
}
}
44. Un piège : suivi des objets
Par défaut, Doctrine compare les valeurs des propriétés pour détecter
si elles ont été modifiées.
❗
Cela ne marche pas avec les objets: UploadedFile, DateTime...
Il faut alors soit modifier la valeur d'une autre propriété, d'un type
primitif, soit utiliser des objets immutables.
class Article
{
/**
* @var DateTimeImmutable
* @ORMColumn(type="datetime_immutable")
*/
private $updatedAt;
}
47. Embeddables
/** @ORMEmbeddable */
class Address
{
/** @ORMColumn() */
private $street;
/** @ORMColumn() */
private $city;
}
/** @ORMEntity */
class Blog
{
/** @ORMEmbedded(class="Address") */
private $address;
public function __construct()
{
$this->address = new Address();
}
}
// Pour requêter en DQL, on pourra écrire :
// SELECT b FROM Blog b WHERE b.address.city = ...
48. Ajouter un type Doctrine (1/2)
use DoctrineDBALPlatformsAbstractPlatform;
use DoctrineDBALTypesConversionException;
use DoctrineDBALTypesType;
class DatetimeUtcType extends Type
{
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return $platform->getDateTimeTypeDeclarationSQL($fieldDeclaration);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (null === $value) {
return $value;
}
if (!$value instanceof DateTime) {
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']);
}
$value->setTimezone(new DateTimeZone('UTC'));
return $value->format($platform->getDateTimeFormatString());
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === null || $value instanceof DateTime) {
return $value;
}
$val = DateTime::createFromFormat($platform->getDateTimeFormatString(), $value, new DateTimeZone('UTC'));
if (!$val) {
throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateTimeFormatString());
}
return $val;
}
public function getName()
{
return 'datetime_utc';
}
}
49. Ajouter un type Doctrine (2/2)
doctrine:
dbal:
types:
datetime_utc: AppTypeDatetimeUtcType
Exemple, pour stocker une date avec un timezone (en MySQL...) :
/** ORMEntity */
class Article
{
/** ORMColumn(type="datetime_utc") */
private $utcDate;
/** ORMColumn(type="string") */
private $timezone;
public function getDate()
{
return (clone $this->utcDate)->setTimezone(new DateTimeZone($this->timezone));
}
public function setDate(DateTime $date)
{
$this->timezone = $date->getTimezone()->getName();
$this->utcDate = clone $date;
}
}
50. Héritage
Cas typique : définir dans un bundle une entité qui sera étendue.
/** @ORMMappedSuperclass */ /** @ORMEntity */
abstract class AbstractUser class User extends AbstractUser
{ {
/** @ORMColumn() */ /** @ORMId @ORMColumn(type="integer") */
protected $email; private $id;
/** @ORMColumn() */ /** @ORMColumn() */
protected $username; private $myfield;
} }
❗
aux limites (associations...)
Doctrine supporte les Traits, cela est généralement plus simple.
❗ ❗ ❗
pour Single Table Inheritance et Multiple Table Inheritance
51. Caches
doctrine:
orm:
# Ces 2 caches sont INDISPENSABLES en "prod" :
# (et activés par Symfony Flex par défaut)
metadata_cache_driver: apcu # cache les annotations
query_cache_driver: apcu # cache le DQL généré
# Optionnel et manuel :
result_cache_driver: apcu
Le Result cache devra être rempli manuellement :
$query = $em->createQuery('SELECT b FROM AppEntityBlog b');
$query->useResultCache(true, 3600); // 1 heure
53. Étendre Doctrine
Le plus connu, les Doctrine extensions :
Sortable, SoftDeleteable, Sluggable...
⭐ ⭐
steevanb/DoctrineStatsBundle
⭐ ⭐
Des fonctions DQL supplémentaires :
- spécifiques à chaque plate-forme
- pour requêter/manipuler du JSON
- pour les types spatiaux
Des utilitaires pour les batchs