4
4
How to Work with Doctrine Associations / Relations
5
5
==================================================
6
6
7
+ There are **two ** main relationship/association types:
8
+
9
+ ``ManyToOne `` / ``OneToMany ``
10
+ The most common relationship, mapped in the database with a simple foreign
11
+ key column (e.g. a ``category_id `` column on the ``product `` table). This is
12
+ actually just *one * association type, but seen from the two different *sides *
13
+ of the relation.
14
+
15
+ ``ManyToMany ``
16
+ Uses a join table and is needed when both sides of the relationship can have
17
+ many of the other side (e.g. "students" and "classes": each student is in many
18
+ classes, and each class has many students).
19
+
20
+ First, you need to determine which relationship to use. If both sides of the relation
21
+ will contain many of the oter side (e.g. "students" and "classes"), you need a
22
+ ``ManyToMany `` relation. Otherwise, you likely need a ``ManyToOne ``.
23
+
24
+ .. tip ::
25
+
26
+ There is also a OneToOne relationship (e.g. one User has one Profile and vice
27
+ versa). In practice, using this is similar to ``ManyToOne ``.
28
+
29
+ The ManyToOne / OneToMany Association
30
+ -------------------------------------
31
+
7
32
Suppose that each product in your application belongs to exactly one category.
8
33
In this case, you'll need a ``Category `` class, and a way to relate a
9
34
``Product `` object to a ``Category `` object.
10
35
11
- Start by creating the ``Category `` entity. Since you know that you'll eventually
12
- need to persist category objects through Doctrine, you can let Doctrine create
13
- the class for you.
36
+ Start by creating a ``Category `` entity:
14
37
15
38
.. code-block :: terminal
16
39
17
- $ php bin/console doctrine:generate:entity --no-interaction \
18
- --entity="App:Category" \
19
- --fields="name:string(255)"
40
+ $ php bin/console make:entity Category
41
+
42
+ Then, add a ``name `` field to that new ``Category `` class::
43
+
44
+ // src/Entity/Category
45
+ // ...
20
46
21
- This command generates the ``Category `` entity for you, with an ``id `` field,
22
- a ``name `` field and the associated getter and setter functions.
47
+ class Category
48
+ {
49
+ /**
50
+ * @ORM\Id
51
+ * @ORM\GeneratedValue
52
+ * @ORM\Column(type="integer")
53
+ */
54
+ private $id;
55
+
56
+ /**
57
+ * @ORM\Column(type="string")
58
+ */
59
+ private $name;
60
+
61
+ // ... getters and setters
62
+ }
23
63
24
- Relationship Mapping Metadata
25
- -----------------------------
64
+ Mapping the ManyToOne Relationship
65
+ ----------------------------------
26
66
27
- In this example, each category can be associated with *many * products, while
67
+ In this example, each category can be associated with *many * products. But,
28
68
each product can be associated with only *one * category. This relationship
29
69
can be summarized as: *many * products to *one * category (or equivalently,
30
70
*one * category to *many * products).
31
71
32
72
From the perspective of the ``Product `` entity, this is a many-to-one relationship.
33
73
From the perspective of the ``Category `` entity, this is a one-to-many relationship.
34
- This is important, because the relative nature of the relationship determines
35
- which mapping metadata to use. It also determines which class *must * hold
36
- a reference to the other class.
37
74
38
- To relate the `` Product `` and `` Category `` entities, simply create a `` category ``
39
- property on the ``Product `` class, annotated as follows :
75
+ To map this, first create a `` category `` property on the `` Product `` class with
76
+ the ``ManyToOne `` annotation :
40
77
41
78
.. configuration-block ::
42
79
@@ -50,10 +87,20 @@ property on the ``Product`` class, annotated as follows:
50
87
// ...
51
88
52
89
/**
53
- * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
54
- * @ORM\JoinColumn(name="category_id", referencedColumnName="id" )
90
+ * @ORM\ManyToOne(targetEntity="App\Entity\ Category", inversedBy="products")
91
+ * @ORM\JoinColumn(nullable=true )
55
92
*/
56
93
private $category;
94
+
95
+ public function getCategory(): Category
96
+ {
97
+ return $this->category;
98
+ }
99
+
100
+ public function setCategory(Category $category)
101
+ {
102
+ $this->category = $category;
103
+ }
57
104
}
58
105
59
106
.. code-block :: yaml
@@ -64,11 +111,10 @@ property on the ``Product`` class, annotated as follows:
64
111
# ...
65
112
manyToOne :
66
113
category :
67
- targetEntity : Category
114
+ targetEntity : App\Entity\ Category
68
115
inversedBy : products
69
116
joinColumn :
70
- name : category_id
71
- referencedColumnName : id
117
+ nullable : true
72
118
73
119
.. code-block :: xml
74
120
@@ -83,22 +129,19 @@ property on the ``Product`` class, annotated as follows:
83
129
<!-- ... -->
84
130
<many-to-one
85
131
field =" category"
86
- target-entity =" Category"
87
- inversed-by =" products"
88
- join-column =" category" >
89
-
90
- <join-column name =" category_id" referenced-column-name =" id" />
132
+ target-entity =" App\Entity\Category"
133
+ inversed-by =" products" >
134
+ <join-column nullable =" true" />
91
135
</many-to-one >
92
136
</entity >
93
137
</doctrine-mapping >
94
138
95
- This many-to-one mapping is critical . It tells Doctrine to use the ``category_id ``
139
+ This many-to-one mapping is required . It tells Doctrine to use the ``category_id ``
96
140
column on the ``product `` table to relate each record in that table with
97
141
a record in the ``category `` table.
98
142
99
- Next, since a single ``Category `` object will relate to many ``Product ``
100
- objects, a ``products `` property can be added to the ``Category `` class
101
- to hold those associated objects.
143
+ Next, since a *one * ``Category `` object will relate to *many * ``Product ``
144
+ objects, add a ``products `` property to ``Category `` that will hold those objects:
102
145
103
146
.. configuration-block ::
104
147
@@ -108,20 +151,29 @@ to hold those associated objects.
108
151
109
152
// ...
110
153
use Doctrine\Common\Collections\ArrayCollection;
154
+ use Doctrine\Common\Collections\Collection;
111
155
112
156
class Category
113
157
{
114
158
// ...
115
159
116
160
/**
117
- * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
161
+ * @ORM\OneToMany(targetEntity="App\Entity\ Product", mappedBy="category")
118
162
*/
119
163
private $products;
120
164
121
165
public function __construct()
122
166
{
123
167
$this->products = new ArrayCollection();
124
168
}
169
+
170
+ /**
171
+ * @return Collection|Product[]
172
+ */
173
+ public function getProducts()
174
+ {
175
+ return $this->products;
176
+ }
125
177
}
126
178
127
179
.. code-block :: yaml
@@ -132,7 +184,7 @@ to hold those associated objects.
132
184
# ...
133
185
oneToMany :
134
186
products :
135
- targetEntity : Product
187
+ targetEntity : App\Entity\ Product
136
188
mappedBy : category
137
189
# Don't forget to initialize the collection in
138
190
# the __construct() method of the entity
@@ -150,7 +202,7 @@ to hold those associated objects.
150
202
<!-- ... -->
151
203
<one-to-many
152
204
field =" products"
153
- target-entity =" Product"
205
+ target-entity =" App\Entity\ Product"
154
206
mapped-by =" category" />
155
207
156
208
<!--
@@ -160,68 +212,29 @@ to hold those associated objects.
160
212
</entity >
161
213
</doctrine-mapping >
162
214
163
- While the many-to-one mapping shown earlier was mandatory, this one-to-many
164
- mapping is optional. It is included here to help demonstrate Doctrine's range
165
- of relationship management capabilities. Plus, in the context of this application,
166
- it will likely be convenient for each `` Category `` object to automatically
167
- own a collection of its related `` Product `` objects .
215
+ The `` ManyToOne `` mapping shown earlier is * required *, But, this `` OneToMany ``
216
+ is optional: only add it * if * you want to be able to access the products that are
217
+ related to a category. In this example, it * will * be useful to be able to call
218
+ `` $category->getProducts() ``. If you don't want it, then you also don't need the
219
+ `` inversedBy `` or `` mappedBy `` config .
168
220
169
- .. note ::
170
-
171
- The code in the constructor is important. Rather than being instantiated
172
- as a traditional ``array ``, the ``$products `` property must be of a type
173
- that implements Doctrine's ``Collection `` interface. In this case, an
174
- ``ArrayCollection `` object is used. This object looks and acts almost
175
- *exactly * like an array, but has some added flexibility. If this makes
176
- you uncomfortable, don't worry. Just imagine that it's an ``array ``
177
- and you'll be in good shape.
178
-
179
- .. seealso ::
180
-
181
- To understand ``inversedBy `` and ``mappedBy `` usage, see Doctrine's
182
- `Association Updates `_ documentation.
183
-
184
- .. tip ::
185
-
186
- The targetEntity value in the metadata used above can reference any entity
187
- with a valid namespace, not just entities defined in the same namespace. To
188
- relate to an entity defined in a different class or bundle, enter a full
189
- namespace as the targetEntity.
190
-
191
- Now that you've added new properties to both the ``Product `` and ``Category ``
192
- classes, you must generate the missing getter and setter methods manually or
193
- using your own IDE.
194
-
195
- Ignore the Doctrine metadata for a moment. You now have two classes - ``Product ``
196
- and ``Category ``, with a natural many-to-one relationship. The ``Product ``
197
- class holds a *single * ``Category `` object, and the ``Category `` class holds
198
- a *collection * of ``Product `` objects. In other words, you've built your classes
199
- in a way that makes sense for your application. The fact that the data needs
200
- to be persisted to a database is always secondary.
201
-
202
- Now, review the metadata above the ``Product `` entity's ``$category `` property.
203
- It tells Doctrine that the related class is ``Category ``, and that the ``id ``
204
- of the related category record should be stored in a ``category_id `` field
205
- on the ``product `` table.
206
-
207
- In other words, the related ``Category `` object will be stored in the
208
- ``$category `` property, but behind the scenes, Doctrine will persist this
209
- relationship by storing the category's id in the ``category_id `` column
210
- of the ``product `` table.
211
-
212
- .. image :: /_images/doctrine/mapping_relations.png
213
- :align: center
221
+ .. sidebar :: What is the ArrayCollection Stuff?
214
222
215
- The metadata above the ``Category `` entity's ``$products `` property is less
216
- complicated. It simply tells Doctrine to look at the ``Product.category ``
217
- property to figure out how the relationship is mapped.
223
+ The code inside ``__construct() `` is important: The ``$products `` property must
224
+ be a collection object that implements Doctrine's ``Collection `` interface.
225
+ In this case, an ``ArrayCollection `` object is used. This looks and acts almost
226
+ *exactly * like an array, but has some added flexibility. Just imagine that it's
227
+ an ``array `` and you'll be in good shape.
218
228
219
- Before you continue, be sure to tell Doctrine to add the new ``category ``
220
- table, the new ``product.category_id `` column, and the new foreign key:
229
+ Your database is setup! Now, execute the migrations like normal:
221
230
222
231
.. code-block :: terminal
223
232
224
- $ php bin/console doctrine:schema:update --force
233
+ $ php bin/console doctrine:migrations:diff
234
+ $ php bin/console doctrine:migrations:migrate
235
+
236
+ Thanks to the relationship, this creates a ``category_id `` foreign key column on
237
+ the ``product `` table. Doctrine is ready to persist our relationship!
225
238
226
239
Saving Related Entities
227
240
-----------------------
@@ -234,9 +247,12 @@ Now you can see this new code in action! Imagine you're inside a controller::
234
247
use App\Entity\Product;
235
248
use Symfony\Component\HttpFoundation\Response;
236
249
237
- class DefaultController extends Controller
250
+ class ProductController extends Controller
238
251
{
239
- public function createProductAction()
252
+ /**
253
+ * @Route("/product", name="product")
254
+ */
255
+ public function index()
240
256
{
241
257
$category = new Category();
242
258
$category->setName('Computer Peripherals');
@@ -261,10 +277,18 @@ Now you can see this new code in action! Imagine you're inside a controller::
261
277
}
262
278
}
263
279
264
- Now, a single row is added to both the ``category `` and ``product `` tables.
265
- The ``product.category_id `` column for the new product is set to whatever
266
- the ``id `` is of the new category. Doctrine manages the persistence of this
267
- relationship for you.
280
+ When you go to ``/product ``, a single row is added to both the ``category `` and
281
+ ``product `` tables. The ``product.category_id `` column for the new product is set
282
+ to whatever the ``id `` is of the new category. Doctrine manages the persistence of this
283
+ relationship for you:
284
+
285
+ .. image :: /_images/doctrine/mapping_relations.png
286
+ :align: center
287
+
288
+ If you're new to an ORM, this is the *hardest * concept: you need to stop thinking
289
+ about your database, and instead *only * think about your objects. Instead of setting
290
+ the category's integer id onto ``Product ``, you set the entire ``Category `` *object *.
291
+ Doctrine takes care of the rest when saving.
268
292
269
293
Fetching Related Objects
270
294
------------------------
@@ -276,11 +300,13 @@ did before. First, fetch a ``$product`` object and then access its related
276
300
use App\Entity\Product;
277
301
// ...
278
302
279
- public function showAction($productId )
303
+ public function showAction($id )
280
304
{
281
305
$product = $this->getDoctrine()
282
306
->getRepository(Product::class)
283
- ->find($productId);
307
+ ->find($id);
308
+
309
+ // ...
284
310
285
311
$categoryName = $product->getCategory()->getName();
286
312
@@ -289,7 +315,7 @@ did before. First, fetch a ``$product`` object and then access its related
289
315
290
316
In this example, you first query for a ``Product `` object based on the product's
291
317
``id ``. This issues a query for *just * the product data and hydrates the
292
- ``$product `` object with that data . Later, when you call ``$product->getCategory()->getName() ``,
318
+ ``$product ``. Later, when you call ``$product->getCategory()->getName() ``,
293
319
Doctrine silently makes a second query to find the ``Category `` that's related
294
320
to this ``Product ``. It prepares the ``$category `` object and returns it to
295
321
you.
@@ -301,7 +327,8 @@ What's important is the fact that you have easy access to the product's related
301
327
category, but the category data isn't actually retrieved until you ask for
302
328
the category (i.e. it's "lazily loaded").
303
329
304
- You can also query in the other direction::
330
+ Because we mapped the optiona ``OneToMany `` side, you can also query in the other
331
+ direction::
305
332
306
333
public function showProductsAction($categoryId)
307
334
{
@@ -314,6 +341,8 @@ You can also query in the other direction::
314
341
// ...
315
342
}
316
343
344
+ TODO TODO, STARTING HERE!!!!!!!!!!!!!!!!!!
345
+
317
346
In this case, the same things occur: you first query out for a single ``Category ``
318
347
object, and then Doctrine makes a second query to retrieve the related ``Product ``
319
348
objects, but only once/if you ask for them (i.e. when you call ``getProducts() ``).
@@ -412,4 +441,3 @@ Doctrine's `Association Mapping Documentation`_.
412
441
statement, which *imports * the ``ORM `` annotations prefix.
413
442
414
443
.. _`Association Mapping Documentation` : http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
415
- .. _`Association Updates` : http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html
0 commit comments