Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
N
ne-soyez-plus-l-esclave-de-doctrine
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Greg0ire
ne-soyez-plus-l-esclave-de-doctrine
Commits
2b4a85ec
Commit
2b4a85ec
authored
7 years ago
by
Maxime Veber
Browse files
Options
Downloads
Plain Diff
Merge branch 'custom_types_embeddables' into 'master'
Custom types embeddables See merge request
!1
parents
51e1d11c
5b27a961
Branches
Branches containing commit
No related tags found
1 merge request
!1
Custom types embeddables
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
index.md
+174
-0
174 additions, 0 deletions
index.md
with
174 additions
and
0 deletions
index.md
+
174
−
0
View file @
2b4a85ec
...
...
@@ -30,6 +30,11 @@ Maxime Veber
└── Twig
</pre>
Notes:
-
Aucune idée de ce que fait l'application
-
Tout ce qu'on reconnait, c'est des répertoires qui sont présents sur d'autres
applications.
---
### Vous avez dit entité ?
...
...
@@ -53,6 +58,7 @@ Notes:
-
Pas de validation
-
Pas de règles métier
-
Aucune logique (pas de tests nécessaires)
-
Pas de setter pour id, il est setté par Doctrine après la persistence
---
### Nous aimons le DDD
...
...
@@ -61,6 +67,9 @@ Notes:
-
Représenter les
**règles métier**
dans les entités
-
Avoir une API expressive
Notes:
-
Il existe d'autres architectures, cf le talk sur le clean code de Romain Kuzniak
---
### Doctrine n'a pas besoin de setters
...
...
@@ -107,6 +116,9 @@ class Article
Note:
-
Impossible de persister une entité invalide
-
Validation compliquée quand on a trop de propriétés
-
Pour qu'on puisse utiliser des constructeurs sans que ça crée de conflit avec
Doctrine, Doctrine utilise de la réflection ou de la désérialisation pour
hydrater les entités.
---
### Les value objects
...
...
@@ -134,6 +146,58 @@ Note:
-
Doctrine Embeddables
-
Custom types
---
### Les embeddables
```
php
use
Doctrine\ORM\Annotation
as
ORM
;
class
NiceArticle
{
private
$uuid
;
/** @ORM\Embedded(class = "ArticleContent") */
private
$articleContent
;
}
```
Note:
-
À utiliser en cas de Value Object composite
-
Des soucis avec la nullabilité, contournables avec un package
-
Ne peuvent contenir des colonnes complexes
---
### Les custom types
```
php
use
Doctrine\DBAL\Platforms\AbstractPlatform
as
P
;
final
class
ArticleContentType
extends
Type
{
public
function
convertToPHPValue
(
$value
,
P
$p
):
ArticleId
{
return
new
ArticleId
(
$value
);
}
public
function
convertToDatabaseValue
(
$value
,
P
$p
):
string
{
return
(
string
)
$value
;
}
}
```
Note:
-
Permet de contrôler qu'on respecte toujours les règles métier à
l'hydratation. Si ça crashe, c'est qu'il manque des migrations.
-
La méthode
`getName()`
fait doublon avec le nom utilisé lors de
l'enregistrement du type dans le registre de type, et disparaître dès Doctrine 3
-
`ArticleId`
devrait être une clé naturelle ou un UUID, le principal c'est de
pas avoir besoin de demander à la DB de le calculer, ça évite des attaques
pour cause d'ID devinables, et ça évite d'exposer le nombre d'entités présentes
dans une table. Ça évite aussi des collisions lorsque vous migrez des données
d'une base vers une autre, et que la nouvelle base peut elle aussi être
alimentée directement.
---
### Les constructeurs nommés
...
...
@@ -145,9 +209,119 @@ class BetterArticle
return
new
self
(
new
ArticleContent
(
$content
));
}
}
```
Note:
-
On peut faire plusieurs constructeurs nommés
-
On peut passer le constructeur en privé pour encourager l'utilisation des
constructeurs nommés.
---
### Les repositories
```
php
final
class
DoctrineArticleRepository
implements
ArticleRepository
{
public
function
__construct
(
EntityManagerInterface
$entityManager
)
{
$this
->
entityManager
=
$entityManager
;
}
public
function
latestArticles
(
int
$size
):
iterable
{
$this
->
entityManager
->
createQueryBuilder
()
->
select
(
'a.*'
)
->
from
(
Article
::
class
)
->
orderBy
(
'a.createdAt'
,
'DESC'
)
->
setMaxResults
(
$size
)
->
getQuery
()
->
getResults
();
}
}
```
Note:
-
`Repository`
, c'est un pattern, et c'est vous qui devriez en définir
l'interface.
-
`EntityRepository`
et ses méthodes magiques à éviter si vous voulez des type
hints de retour et donc de l'autocompletion. Il est maintenant possible d'en
faire des services: https://github.com/doctrine/DoctrineBundle/pull/727, mais
ça ne résout pas le problème. Si vous tenez à les utiliser, injectez les dans
vos repositories plutôt que de les étendre.
-
Plus l'interface est grosse, plus elle devient difficile à implémenter, et le
code qui consomme l'API a rarement besoin de faire beaucoup d'appels, du
coup… slide suivant
---
# Les Query functions
```
php
final
class
DoctrineGetLatestArticles
implements
GetLatestArticles
{
public
function
__construct
(
EntityManagerInterface
$entityManager
)
{
$this
->
entityManager
=
$entityManager
;
}
public
function
__invoke
(
int
$size
):
iterable
{
$this
->
entityManager
->
createQueryBuilder
()
->
select
(
'a.*'
)
->
from
(
Article
::
class
)
->
orderBy
(
'a.createdAt'
,
'DESC'
)
->
setMaxResults
(
$size
)
->
getQuery
()
->
getResults
();
}
}
```
Note:
-
Tout de suite beaucoup plus simple à réimplémenter en elasticsearch
---
# Les collections
-
elles permettent à Doctrine de repérer les changements
-
elles permettent à Doctrine de faire du lazy-loading
-
vous pouvez les éviter en type hintant
`iterable`
et en utilisant un tableau
pour les initialiser.
-
si une collection comporte énormément d'éléments, c'est probablement une
relation à faire sauter
---
# Faire une jointure avec une relation uni-directionnelle
```
php
$queryBuilder
->
select
(
'a.*'
)
->
from
(
Article
::
class
)
->
innerJoin
(
Comment
::
class
,
'c'
,
Expr\Join
::
WITH
,
'c.article_id = a.id'
)
->
where
(
"c.content LIKE '%Doctrine%'"
)
->
getQuery
()
->
getResults
();
```
Note:
-
utile si on a besoin de rajouter des conditions sans nécessiter d'hydrater
des objets de la classe jointe.
---
# Choisir la bonne API pour interroger la base de données
-
DQL Query Builder
-
DQL
-
SQL Query Builder
-
SQL
Note:
-
Si vous n'avez pas besoin d'objets, le SQL peut s'avérer plus simple, plus
puissant, et plus performant (pas d'hydratation).
-
Si vous voulez des objets mais ne pouvez pas utiliser du DQL, tournez vous
vers le ResultSetMapping
-
Si vous n'avez pas besoin de faire des requêtes dynamiques, vous pouvez vous
passer du Query Builder.
---
# Emoji test
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment