Skip to content

Commit 68ac454

Browse files
committed
WIP - updating associations article
1 parent 7e77d9c commit 68ac454

File tree

1 file changed

+128
-100
lines changed

1 file changed

+128
-100
lines changed

doctrine/associations.rst

Lines changed: 128 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,76 @@
44
How to Work with Doctrine Associations / Relations
55
==================================================
66

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+
732
Suppose that each product in your application belongs to exactly one category.
833
In this case, you'll need a ``Category`` class, and a way to relate a
934
``Product`` object to a ``Category`` object.
1035

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:
1437

1538
.. code-block:: terminal
1639
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+
// ...
2046

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+
}
2363

24-
Relationship Mapping Metadata
25-
-----------------------------
64+
Mapping the ManyToOne Relationship
65+
----------------------------------
2666

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,
2868
each product can be associated with only *one* category. This relationship
2969
can be summarized as: *many* products to *one* category (or equivalently,
3070
*one* category to *many* products).
3171

3272
From the perspective of the ``Product`` entity, this is a many-to-one relationship.
3373
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.
3774

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:
4077

4178
.. configuration-block::
4279

@@ -50,10 +87,20 @@ property on the ``Product`` class, annotated as follows:
5087
// ...
5188
5289
/**
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)
5592
*/
5693
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+
}
57104
}
58105
59106
.. code-block:: yaml
@@ -64,11 +111,10 @@ property on the ``Product`` class, annotated as follows:
64111
# ...
65112
manyToOne:
66113
category:
67-
targetEntity: Category
114+
targetEntity: App\Entity\Category
68115
inversedBy: products
69116
joinColumn:
70-
name: category_id
71-
referencedColumnName: id
117+
nullable: true
72118
73119
.. code-block:: xml
74120
@@ -83,22 +129,19 @@ property on the ``Product`` class, annotated as follows:
83129
<!-- ... -->
84130
<many-to-one
85131
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" />
91135
</many-to-one>
92136
</entity>
93137
</doctrine-mapping>
94138
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``
96140
column on the ``product`` table to relate each record in that table with
97141
a record in the ``category`` table.
98142

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:
102145

103146
.. configuration-block::
104147

@@ -108,20 +151,29 @@ to hold those associated objects.
108151
109152
// ...
110153
use Doctrine\Common\Collections\ArrayCollection;
154+
use Doctrine\Common\Collections\Collection;
111155
112156
class Category
113157
{
114158
// ...
115159
116160
/**
117-
* @ORM\OneToMany(targetEntity="Product", mappedBy="category")
161+
* @ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="category")
118162
*/
119163
private $products;
120164
121165
public function __construct()
122166
{
123167
$this->products = new ArrayCollection();
124168
}
169+
170+
/**
171+
* @return Collection|Product[]
172+
*/
173+
public function getProducts()
174+
{
175+
return $this->products;
176+
}
125177
}
126178
127179
.. code-block:: yaml
@@ -132,7 +184,7 @@ to hold those associated objects.
132184
# ...
133185
oneToMany:
134186
products:
135-
targetEntity: Product
187+
targetEntity: App\Entity\Product
136188
mappedBy: category
137189
# Don't forget to initialize the collection in
138190
# the __construct() method of the entity
@@ -150,7 +202,7 @@ to hold those associated objects.
150202
<!-- ... -->
151203
<one-to-many
152204
field="products"
153-
target-entity="Product"
205+
target-entity="App\Entity\Product"
154206
mapped-by="category" />
155207
156208
<!--
@@ -160,68 +212,29 @@ to hold those associated objects.
160212
</entity>
161213
</doctrine-mapping>
162214
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.
168220

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?
214222

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

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:
221230

222231
.. code-block:: terminal
223232
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!
225238

226239
Saving Related Entities
227240
-----------------------
@@ -234,9 +247,12 @@ Now you can see this new code in action! Imagine you're inside a controller::
234247
use App\Entity\Product;
235248
use Symfony\Component\HttpFoundation\Response;
236249

237-
class DefaultController extends Controller
250+
class ProductController extends Controller
238251
{
239-
public function createProductAction()
252+
/**
253+
* @Route("/product", name="product")
254+
*/
255+
public function index()
240256
{
241257
$category = new Category();
242258
$category->setName('Computer Peripherals');
@@ -261,10 +277,18 @@ Now you can see this new code in action! Imagine you're inside a controller::
261277
}
262278
}
263279

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

269293
Fetching Related Objects
270294
------------------------
@@ -276,11 +300,13 @@ did before. First, fetch a ``$product`` object and then access its related
276300
use App\Entity\Product;
277301
// ...
278302

279-
public function showAction($productId)
303+
public function showAction($id)
280304
{
281305
$product = $this->getDoctrine()
282306
->getRepository(Product::class)
283-
->find($productId);
307+
->find($id);
308+
309+
// ...
284310

285311
$categoryName = $product->getCategory()->getName();
286312

@@ -289,7 +315,7 @@ did before. First, fetch a ``$product`` object and then access its related
289315

290316
In this example, you first query for a ``Product`` object based on the product's
291317
``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()``,
293319
Doctrine silently makes a second query to find the ``Category`` that's related
294320
to this ``Product``. It prepares the ``$category`` object and returns it to
295321
you.
@@ -301,7 +327,8 @@ What's important is the fact that you have easy access to the product's related
301327
category, but the category data isn't actually retrieved until you ask for
302328
the category (i.e. it's "lazily loaded").
303329

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::
305332

306333
public function showProductsAction($categoryId)
307334
{
@@ -314,6 +341,8 @@ You can also query in the other direction::
314341
// ...
315342
}
316343

344+
TODO TODO, STARTING HERE!!!!!!!!!!!!!!!!!!
345+
317346
In this case, the same things occur: you first query out for a single ``Category``
318347
object, and then Doctrine makes a second query to retrieve the related ``Product``
319348
objects, but only once/if you ask for them (i.e. when you call ``getProducts()``).
@@ -412,4 +441,3 @@ Doctrine's `Association Mapping Documentation`_.
412441
statement, which *imports* the ``ORM`` annotations prefix.
413442

414443
.. _`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

Comments
 (0)