-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Documentation about getters/setters to DTOs and Value Objects #6902
Conversation
edit: This was a reply to a comment that has been deleted, it suggested to use constructor to set value and keep entity immutable only with a getter. Agreed on constructor + immutable state being easier to understand in Getting Started. But behavioral methods should be explained too at some later point. For example, Referencing symfony/symfony-docs#8893 - similar issue for Symfony docs. |
I don't think that information about proper modelling fits to "getting started" section... This is far more complicated topic. You could change examples, like remove setters:
and
Doctrine doesn't enforce users to use setters, so i don't think why it used in documentation. But with getters there are different story. Since #6719 will not be available to users until 3.0, it will be just to complicated to describe why you still need Anyway, right now documentation contains something like:
this literally enforce users to use getters in setters in their models. And the only reason for that is how doctrine use accessors for lazy load. Probably it will be better to replace Also I think that it would be useful to describe concepts of persistence ignorance instead of digging into DTO/UoW stuff in the getting started section. For example some of the devs thinks that Doctrine is calling constructor of entities for each fetch, and many problems comes from the fact that they don't understand that entities this simple concepts. You could provide links on separate sections which will provide more detailed information. Like "how to use entities with forms" and so on. Something for cookbook rather than "getting started". |
The requirement for accessors is no longer needed since Doctrine ORM 2.3,
where we added full support for public properties. In 3.x we will even
support friend objects.
…On 19 Dec 2017 00:18, "Sergey Protko" ***@***.***> wrote:
I don't think that information about proper modelling fits to "getting
started" section... This is far more complicated topic. You could change
examples, like remove setters:
<?php
// src/Product.php
class Product
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
}
and
$product->rename($name);
// instead of
$product->setName($name);
Doctrine doesn't enforce users to use setters. But with getters there are
different story. Since #6719
<#6719> will not be available
to users until 3.0 will be released, it will be just to complicated to
describe why you still need getId() getter so you could fetch ID of
related entity for example without actually preform db query.
Anyway, right now documentation contains something like:
When creating entity classes, all of the fields should be protected or
private (not public), with getter and setter methods for each one (except
$id).
this literally enforce users to use getters in setters in their models.
And the only reason for that is how doctrine use accessors for lazy load.
Also I think that it would be useful to describe concepts of persistence
ignorance instead of digging into DTO/UoW stuff in the getting started
section. You could provide links on separate sections which will provide
more detailed information. Like "how to use entities with forms" and so on.
Something for cookbook rather than "getting started".
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#6902 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAJakB6bL2XF2GNf7pywjIxUJmH-wMdiks5tBvLMgaJpZM4RF-MX>
.
|
For example, when having a ``User`` entity, we could foresee | ||
the following kind of optimization. | ||
|
||
Before, an anemic model with getters and setters: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are sections "Getting Started: Database First" and "Getting Started: Model First". I think this sections could cover "anemic vs rich model" cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a.... rich idea 😎
|
||
public function authenticate(string $password, callable $checkHash) : bool | ||
{ | ||
return $checkHash($password, $this->passwordHash) && ! $this->hasActiveBans(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about cases with password_needs_rehash
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an example retrieved from Marco's talk about doctrine best practices, but of course it can be changed :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fesor I think this is a good example of how a rich domain model aids in better maintainability later on. Not sure if it would fit a simplified example though. Maybe just skip it for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, probably this something that could be covered later in some cookbook (like how to deal with dependencies).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi guys.
I appreciate the effort you put in updating the documentation and promote better programming practices.
I agree with @fesor that the "dealing with dependencies" topic must be covered. Yes, it can be in some cookbook, but it must be covered because it will be an issue people will run into very quickly and not finding anything related to this will probably make them to give up and go to the well known "don't put behavior in your entities, just put data and make some services that will operate on your entities through some setters".
return $this->username; | ||
} | ||
|
||
public function setUsername(string $username) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for types, also add void
$this->passwordHash = $hash($password); | ||
} | ||
|
||
public function ban() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be interesting to see some parameters here. I.e. \DateInterval $duration
+ assert() that it's non-negative. WDYT?
Some pretty big words unnecessarily used here for getting started guide. I suggest following changes (I focused on getter/setters option, not sure how to change the rest) Adding the Entity behaviors
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You have two options to define the accessors mutators that will
-update the state of your objects.
+You have two options how to define methods in your entity.
**Getters and setters**
-The most popular method is to create getter and setter methods
-for each property (except ``$id``). The use of mutators allows
-Doctrine to hook into calls which manipulate the entities in ways
-that it could not if you just directly set the values with ``entity#field = foo;``.
+The most popular option is creating 2 separate methods for reading
+(getter) and updating (setter) for each field in your entity.
+Some fields, such as ``$id`` are undesirable to be modified and as
+such it's ok to not provide setters for them.
-The id field has no setter since, generally speaking, your code
-should not set this value since it represents a database id value.
-(Note that Doctrine itself can still set the value using the
-Reflection API instead of a defined setter function)
+This is a convention which makes it possible to expose each field
+in your entity to external objects in automated way, while allowing
+you to keep type safety in place.
+
+Such approach is good choice for RAD (rapid application development),
+but may prove problematic later down the road, because providing such
+easy way to modify any field in your entity means entity itself cannot
+ensure it's in valid state. Having entity in invalid state is dangerous,
+because it's one step away from being implicitly saved in database.
.. note::
- This method, although very common, is **not** the best approach if
- you want a perfect domain design. It can lead to unexpected behavior
- when dealing with different steps of logic in between different
- calls to ``EntityManager::flush()``, for example when you change
- the state of an object via event listeners or lifecycle callbacks.
+ This method, although very common, is inappropriate for domain driven
+ design, because in such design, methods should be named according to
+ actual business operation and entity should be valid at all times.
**Mutators and DTOs** |
I thought about this a little bit (and asked some of the developers in local community about pit-fails in learning doctrine). The goal of "getting started" section of every tool is to have it running it as soon as possible (within 10-15 minutes). This is very important to not scare new developers with complexity of the tool. So there should be bare minimal information with only hints about preferred way of designing entities (like remove setters from samples). Al other things should be covered in other sections. From what I know, getters/setters are mostly used since getting started guides of other tools (like forms, CRUD generators and so on) are provided this examples as most straightforward way to start. later in documentation most of this tools covers how to handle DTO, value objects and other stuff, but in format of cook books. However most of the tools doesn't cover this information (api-platform for example, or sonata admin bundle) which make it hard to developers to start without at-least setters. So if Doctrine's documentation will push to one direction, and other tools like forms components in to another, and there will be no handy tutorial on how to mix concepts, developers will fall into cognitive dissonance. Another thought. I faced with fact that most of the developers just don't know how to handle updates in CRUD based application. Basically they need to replace most of the entity data in update operation, with some kind of control. I teach my team to rely in immutable data since in this way we could replace "update" to another "create" operation but in different context. This requires understanding of concepts like value objects, which represented in Doctrine as I think that embeddable should be covered right after entities in basics of the ORM. Also I found structure of documentation not user friendly. Use of embeddables are simplifies understanding of how to better structure business logic into separate objects. Another "pattern" in behaviour which I found, is how data modeling is happening. For example in some application we have users. And we also have some kind of referral program. And also users could be both merchants and customers. And each has profiles. Most of the devs which I talked with putted all this logic in same This probably something that should be at lest slightly covered in "Getting Started: Model First". Or maybe instead of two sections there should be one - "Getting Started: Entity Design Approaches" which will have high-level description of different approaches with list of pros/cons of each. Also I think that restructuring welcome page will have much more benefits to simplify learning projects. Documentation structure should provide much more cleaners way of learning Doctrine. |
I'm not sure we should document Embeddables right after the "Getting started" guide. It is related only to specific needs which are not very common, as most of the times, having entities and relations is a better option, could it be for the DBMS or for your domain. For the rest, I take all your comments in account and will make updates on this subject in the next days/weeks. In the meantime, thanks everyone for participating in this very interesting discussion, happy new year, and may 2018 give you more free time for OSS 😄 🎉 |
not right after "getting started". But at least in "Mapping Objects onto a Database"
mostly since many developers even don't know about it. That's why I think it would be better to start from restructuring links on welcome page instead of rewriting getting started. The only thing is to remove obsolete information like "all properties should be protected (private)". |
@@ -103,7 +103,7 @@ Install Doctrine using the Composer Dependency Management tool, by calling: | |||
$ composer install | |||
|
|||
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM, | |||
Symfony YAML and Symfony Console into the `vendor` directory. The Symfony | |||
Symfony YAML and Symfony Console into the `vendor` directory. The Symfony |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should remove Symfony YAML
from this page? I mean v3 already removed support for it, v2.7 deprecates it, and I think we should start phasing it out ASAP.
Can you please also update the version in L90?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I will, I'm just waiting for a moment when I'll have more time for this PR, it's hard work 😉
} | ||
|
||
public function setName($name) | ||
Additionnally, our entities should never see their state change |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionnally -> Additionally
Entities should represent **behavior**, not **data**, therefore | ||
they should be valid even after a ``__construct()``. | ||
|
||
To help creating such objects, we can rely on ``DTO``s, and/or make |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make our entities Value Objects.
Can we rephrase it? From DDD point of view VOs and entities are different concepts.
.. code-block:: php | ||
|
||
<?php | ||
class User { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not PSR compliant, neither is the code below
* DTOs can be reused in other components, like when deserializing | ||
mixed content, using forms... | ||
* Anemic model tends to isolate the entity from the logic, and a | ||
rich model fixes that because entity's logic can be put in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"the entity's logic" I think
Fixed all your review @greg0ire, but next time, could you add them in a Github review so we can hide them when fixed? 😅 |
By the way, for reviewers, I just made some changes to the text, if you could review again 😉 |
@Pierstoval that's what you get for mentioning me in a commit message :P. You can still squash if you want them to disappear though |
|
||
public function getId() | ||
When creating entity classes, all of the fields should be protected or private (not public). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably rephrase to something like:
should be
private
,protected
when strictly needed and very rarely if not everpublic
I can also add that protected
and public
have some performance overhead (minimal, but it is there). Unsure if we can pack it into the article.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that we need to introduce any limitations in getting started at all. Only one that are really limitations of Doctrine. Proper entity modeling could be covered elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure the performance overhead might be interesting for end-users to know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fesor it's not a limitation (of Doctrine) is it? Just a good recommendation. There's no explanation though, and the performance concerns are additional to something else.
How about
When creating entity classes, fields should be
private
,protected
when strictly needed and very rarely if not everpublic
, to make sure encapsulation is not broken by needless exposition.
public function getId() | ||
When creating entity classes, all of the fields should be protected or private (not public). | ||
|
||
Adding the Entity behaviors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding behavior to Entities
?
Adding the Entity behaviors | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
You have two options to define to define methods in your entity: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a good idea to keep things very impersonal.
There are two options
and in entities
Also, to define
is used twice here
The most popular method is to create two kinds of methods to | ||
**read** (getter) and **update** (setter) the object's properties. | ||
|
||
Some fields such as ``$id`` are unlikely to be changed, so it's ok to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall, try not abbreviating it's
- keep it is
|
||
.. note:: | ||
|
||
Setters are unrelated to Doctrine itself, because the ORM does not |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is true that they are unrelated
, but that's not how it should be explained to the reader.
I think a better approach is to say something like:
Doctrine ORM does not use any of the methods you defined: it uses reflection to read and write values to your objects, and will never call methods, not even
__construct
Reflection API instead of a defined setter function) | ||
There are several advantages to using such model: | ||
|
||
* Entity **state is always valid**. No setters means that we only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
highlight Entity
as part of the bold section
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/No setters means that/Since no setters exist,
* Entity **state is always valid**. No setters means that we only | ||
update portions of the entity that should already be valid. | ||
* Instead of having plain getters and setters, our entity now has | ||
**real behaviors**, meaning it is much easier to determine the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/behaviors/behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: you really like the word "meaning" :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be simplified: **real behaviors**: it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I need to improve my english language a bit 🤣
* Instead of having plain getters and setters, our entity now has | ||
**real behaviors**, meaning it is much easier to determine the | ||
logic in the domain. | ||
* DTOs can be reused in other components, like when deserializing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/like when/for example
* DTOs can be reused in other components, like when deserializing | ||
mixed content, using forms... | ||
* Classic and static constructors can be used to manage different | ||
ways to create our objects, and they can also use DTOs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/use/rely on
* Classic and static constructors can be used to manage different | ||
ways to create our objects, and they can also use DTOs. | ||
* Anemic models tend to isolate the entity from logic, whereas | ||
rich models fix that because logic related to the entity can be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix that because
"fix that" is strange to read in this context, because it is not immediately obvious that this is a problem. We only briefly explained that anemic models lead to some issues, but we didn't explain that logic outside entities versus inside is worse.
@Pierstoval I have access to a computer again and deleted my comments to keep this PR relatively clean |
**Anemic models: Getters and setters** | ||
|
||
The most popular method is to create two kinds of methods to | ||
**read** (getter) and **update** (setter) the object's properties. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"read" and "update" sounds weird, how about:
- "read" and "write"?
- "share" and "mutate"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought read & update were nicer. By adding "write", I'm confused with the fact that we read something but we write in something, and I didn't want to bother with my imperfect english 😆
"share" and "mutate" are closer to the DDD & rich entities approach, and these are words that are possibly unknown by many developers, so I didn't want to add them in the common anemic design
of your entity to external services, while allowing you to keep type | ||
safety in place. | ||
|
||
Such approach is a good choice for RAD (rapid application development), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Such an approach"
but may bring problems later down the road, because providing such an | ||
easy way to modify any field in your entity means the entity itself cannot | ||
ensure it's in valid state. Having entity in invalid state is dangerous, | ||
because it's one step away from being implicitly saved in database, thereby |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about:
Having any object in an invalid state can lead to bugs that will be even harder to troubleshoot and fix if that broken state is persisted to a database and detected later on when the corrupted data is loaded.
} | ||
|
||
public function getName() | ||
Here, we avoid all possible logic from the model and only care about injecting | ||
data into it without validation nor consideration about the object's state. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"without any validation but the one provided by the type hints"
|
||
public function ban(\DateInterval $duration): void | ||
{ | ||
assert($duration->invert !== 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL 💡
validated before being sent to the object for update. | ||
|
||
By using DTOs, if we take our previous ``User`` example, we could create | ||
a ``ProfileFormUser`` DTO object that will be a plain model, totally |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DTO object expands to "data transfer object object"
should not set this value since it represents a database id value. | ||
(Note that Doctrine itself can still set the value using the | ||
Reflection API instead of a defined setter function) | ||
There are several advantages to using such model: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be "such a model", BTW
I made another big update, thanks a lot to all reviewers, it makes me both improve my english and my understanding of how we explain such a complex design as DTOs and rich entities 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢 this is good as-is IMO (besides more nitpicking that I will avoid here).
If anybody has additions/suggestions, please send more PRs :-)
become much harder, and you might be aware of the bug too late to fix it in a | ||
proper manner. | ||
|
||
implicitly saved in database, thereby leading to corrupted or inconsistent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Pierstoval this phrase looks a missed leftover. Is it intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dropped in #7137
language. The metadata language describes how entities, their | ||
properties and references should be persisted and what constraints | ||
should be applied to them. | ||
There are several advantages to using such a model: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the previous "anemic entities" you mentioned 1 advantage (RAD) and 4 drawbacks ... here you mention 4 advantages and 0 drawbacks. Do these "rich entities" really have no drawbacks? Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really: being able to have the database as an implementation detail is primary the reason for using a data mapper over other types of persistence layers, so this approach goes much much closer to the business requirements. It still has the flaws of mutable state management, but that's inherent from RDBMS-backed applications in general.
@@ -228,39 +227,235 @@ entity definition: | |||
* @var string | |||
*/ | |||
protected $name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The vairable is protected.
When creating entity classes, all of the fields should be
private
.
Rich entities should represent **behavior**, not **data**, therefore | ||
they should be valid even after a ``__construct()`` call. | ||
|
||
To help creating such objects, we can rely on ``DTO``s, and/or make |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
Example of a rich entity with proper accessors and mutators: | ||
|
||
.. configuration-block:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There needs to a an empty line after the .. code-block:: php
@tomasfejfar this patch is already merged: send further PRs to fix pending issues 👍 |
should be applied to them. | ||
There are several advantages to using such a model: | ||
|
||
* **Entity state is always valid**. Since no setters exist, this means that we |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The next step for persistence with Doctrine is to describe the structure of | ||
the ``Product`` entity to Doctrine using a metadata language. The metadata | ||
language describes how entities, their properties and references should be | ||
persisted and what constraints should be applied to them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The section following this edit mentions again setters and getters. That's IMO confusing.
{ | ||
$this->name = $name; | ||
public function updateFromProfile(ProfileEditingForm $profileForm): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the ProfileEditingForm
is defined above as a DTO, the code itself reads as the domain object (user) depending on the application logic (form). This way, it violates the Open/closed principle: every time a new form is added, the User
class needs to be updated to enable its mutation by the form. Does the user really have to accept multiple types of DTO or at least, could all forms be represented by one DTO type?
I recently tweeted about this, but the original questioning comes from @javiereguiluz:
The "Getting started" guide refers to getters and setters as the base of an entity's behavior, but the Doctrine core team and other experts certainly prefer mapping rich accessors and mutators. @Ocramius also gives this advice in his "Doctrine beset practices" conference (slides here).
This PR is not a final proposal, but more a starter about a change to the documentation to suggest using a rich model for our domain instead of relying only on getters and setters.
I thought it would still be nice to bring the getters and setters and then talk about DTOs and rich model, to keep consistency with the 95% (arbitrary number) entities created by developers these days, especially the ones using Symfony (and mostly because the Symfony Form component uses getters and setters a lot, and most docs propose to inject them in our forms rather than relying on a DTO).
WDYT about this?