Skip to content
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

Remove references to entity generation, replace by proper entity modelling #8893

Closed
Majkl578 opened this issue Dec 16, 2017 · 80 comments
Closed

Comments

@Majkl578
Copy link

Majkl578 commented Dec 16, 2017

Hello,

there are lots of users using doctrine:generate:entities nowadays. This must stop.

Symfony should provide its large user base decent article on designing/modelling entities in a proper way, with business methods and as few setters as possible, not by generating them from database. Such generated entities lead to a tediously known anemic domain model, something that does more harm than good: breaks encapsulation and completely undermines the purpose of ORM. It'd probably be even better to not use ORM at all and use DBAL directly, the result would be very similar without runtime overhead.

Note that Doctrine 3.0 will remove support for code generation entirely, so the better we educate the users, the better.

This issue is a direct response to discussion in doctrine/DoctrineBundle#729.

Thanks.

@javiereguiluz
Copy link
Member

OK, but let's discuss about this specifically for Symfony apps, not for Doctrine or software design in general. My question: if I remove all my setters in all my Doctrine entities, will the rest of Symfony components keep working perfectly as before? For example, the Form and Serializer components? Thanks!

@ostrolucky
Copy link
Contributor

ostrolucky commented Dec 17, 2017

Form and Serializer components will keep working without getters/setters if you make your properties public. won't work with properties because of Doctrine proxies

I don't ever use doctrine:generate:entities, so I won't miss it. However, I use getter/setter generation in PhpStorm. Setters for instance are useful to ensure application doesn't set wrong data types to properties. It also allows to build fluent interface (I'm fan of it, I know ocramius isn't, don't care). Getters are useful to force users to use setters (otherwise properties would have to be public).

IMO getters/setters/public properties are necessary for RAD of CRUD applications. Most of the applications on the market are of this type.

Personally I use this approach with combination of domain specific features. I don't see a problem to have normal getters/setters in conjunction with domain specific methods in my entities. However, I don't like strict DD design. It's very tedious to write and I wish others would stop forcing it on us.

I also don't see how entity generator prevents users to "properly" model entites. If you guys don't like getter/setter generation, just remove that functionality. It's still useful for generating classes and properties.

@javiereguiluz
Copy link
Member

By the way, in the "Getting Started" guide of Doctrine itself you can read this:

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

See http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html

@sstok
Copy link
Contributor

sstok commented Dec 18, 2017

The "problem" with setters is that they make the Model (Entity) anemic (as explained by @Majkl578 ). they don't communicate any meaning, your models should "speak" the language of the application (updatePassword, disable/enable) not setEnabled() 🤦‍♂️

But newcomers tend to struggle with this as it requires a good understanding of the domain and how you interact with it. But most of them are introduced with the easy and Rapid/CRUD building approach of getters and setters.

For CRUD it's good enough, but in a well designed application the Symfony framework is a detail you only care about at a later time (thus don't use CRUD, but DTO, Commands or something).

Most of this information comes from Cleancode from Robert C. Martin (Uncle Bob). highly recommended read for everyone.

Personally I use this approach with combination of domain specific features. I don't see a problem to have normal getters/setters in conjunction with domain specific methods in my entities. However, I don't like strict DD design. It's very tedious to write and I wish others would stop forcing it on us.

Can you give an example? I always try to use clear methods, but nothing forbids me from using a setter when there no better method available.

https://github.com/park-manager/park-manager/blob/master/src/Component/User/Model/User.php#L155

@kironet
Copy link

kironet commented Dec 19, 2017

It's really confusing. They are forcing people to not to use anemic and in the same time anemic is forced to people by their docs.

@Majkl578
Copy link
Author

Majkl578 commented Dec 19, 2017

if I remove all my setters in all my Doctrine entities, will the rest of Symfony components keep working perfectly as before? For example, the Form and Serializer components? Thanks!

Yes, I realized slightly after posting this issue that these components leverage the same antipattern as well, unrelated on Doctrine. It should change there as well, but I can't really talk on that end since I have barely any experience with those.
To answer your question: It depends. It will of course break the automation of i.e. Form, but it's easy to fix with DTOs/DataMapper (as shown in the article on Twitter). And as a benefit, the code will be cleaner and much less coupled. I don't really take an argument "it's too much writing" these days since we all use some IDE that has autocompletion, provides interface implementation etc., plus closures and anonymous classes (could be used as anonymous implementor of DataMapper).

Form and Serializer components will keep working without getters/setters if you make your properties public.

Please, no public properties, that takes us to PHP 4 age. There are better means.

I don't see a problem to have normal getters/setters

I don't see a problem with having getters (if their existence makes sense - not all fields are to be exposed), they don't change behavior. Setters on the other hand are different story, that's what this is all about.

It's really confusing. They are forcing people to not to use anemic and in the same time anemic is forced to people by their docs.

Please don't be confused, there is nothing to be confused about. We have to start somewhere, and it's here. This issue is about Symfony's documentation, Doctrine is going to work on the same issue as well (see cross-linked issue above).

It should also be noted that ORM 2.6, to be released soon™, will still have EntityGenerator. It will be deprecated in 2.7 and removed in 3.0 next year. There is plenty of time to work on improving documentation on all sides of the barricade.

@mvrhov
Copy link

mvrhov commented Dec 19, 2017

@Majkl578 You are citing the twitter post without linking to it!

@GwendolenLynch
Copy link
Contributor

You are citing the twitter post without linking to it!

Well, the post referred to, both above and in the Twitter conversation was Value Objects in Symfony Forms by @webmozart

There is also his talk from Symfony Con Barcelona where he covers all this too.

@javiereguiluz
Copy link
Member

javiereguiluz commented Dec 19, 2017

Update: in my original comment I said "we're not going to make this change" where "we" means "the Symfony Docs team". I've changed it to "I'm against making this change" ... to be clear that this is my personal opinion.


I hope you understand my comment well: we're not going to make this change I'm against making this change. This breaks everything in Symfony for a dubious gain.

However, what we could do is to add a new article explaining how to use this "proper Doctrine modelling" in Symfony apps for those developers who want to use this. That would be a really helpful resource.

@Levure
Copy link
Contributor

Levure commented Dec 19, 2017

IMHO doctrine:generate:entity is quite usefull when you are migrating a legacy application with an existing database to a new Symfony application.

Why do you want to remove this tool ? Making things more complicated than they already are ?

@alcaeus
Copy link
Contributor

alcaeus commented Dec 19, 2017

@Levure honestly, how often do you reverse-engineer entities from an existing database?

@Majkl578
Copy link
Author

@Majkl578 You are citing the twitter post without linking to it!

You're right, sorry, I confused issues - it was mentioned in Doctrine's issue, not here.

I hope you understand my comment well: we're not going to make this change.

This makes me sad. How I understand this is that one person here likes messy code, therefore wants to close this issue down.

This breaks everything in Symfony for a dubious gain.

Not true. Especially since there is plenty of time to make anything in Symfony not break with clean code.

IMHO doctrine:generate:entity is quite usefull when you are migrating a legacy application with an existing database

This is one-off operation and you mostly need only mapping. You don't need whole PHP class full of setters.

@zorn-v
Copy link

zorn-v commented Dec 21, 2017

I like that you want to REORGANIZE a principe. I realy think it is by good thinks.
BUT
Now you like a propel. Mark yourself as "alpha" and go on. (BTW ActiveRecord I like more, but integration...)
Lets break usual things, like unable to regenerate entities while adding field to db (and use Atom for ex.). And ALL BE OK (and you want that field JUST IN ONE PLACE of code).
Great improvement... Seems I go away from PHP...

PS. I'm totally dont understand argument about IDE

@codedmonkey
Copy link
Contributor

I'm glad this issue has been raised, but in my opinion info on this topic is also lacking in the Doctrine documentation. The ideas raised in this thread alone sound very semantic and are underdiscussed in the Symfony community (at least the part I see). If the Doctrine documentation on entities can be improved, the Symfony documentation can focus more on using those models in the associated components like Form.

@Ocramius
Copy link
Contributor

@codedmonkey doctrine/orm#6902

@donquixote
Copy link

(moving my comment from #7460)

A side note about "Anemic domain model".
The idea that "anemic domain model" === bad is not as clear as some may think.

Martin Fowler said in 2003 that he does not like it.

A number of recent articles suggest that an Anemic model may very well be superior to a rich domain model with regards to testing and "SOLID" principles.

It is worthwhile to read some of this.

The main idea, as I see it:

  • A class/object should maintain and guarantee the integrity of its internal constraints.
    So, some encapsulation should still be present.
  • A lot of constraints and behaviors can be considered external concerns and managed in distinct behavioral components, that the entity needs to know nothing about.
  • Such behavioral components can be split into atomic parts, allow for compositional patterns, and are easier to test than entities.
  • Entities that only guarantee some basic internal constraints don't need injected dependencies.
  • Anemic entity classes no longer need to be modified if some behavior changes, which relates to open/closed principle in SOLID.

How this applies to Symfony and Doctrine, I leave for others to discuss.
As said, I have no intention to change the outcome of this discussion.

@sstok
Copy link
Contributor

sstok commented Apr 19, 2018

In reply to articles @donquixote shared:

This is a great example of what DDD is not about, have some of these authors actually read the books on DDD? Domain Driven Design is about an information Domain, at the hearth of the system (it even says so on the cover).

Having a method named movePlayerNorth() doesn't tell me anything, what is north in this direction, also is there a single Player, who is the player? Normally you'd pass it with the method. And why is there no "getter" method to get the current position to verify if the new position is correct.

There is absolutely no Domain concept in the whole article.

If calling a method on the GameState does something special, it should be traceable trough one of the injected dependencies (or is the author using a Singleton of sorts?), it doesn't just end here, if I store something I must able to ask the state, unless this is not part of the model and handled somewhere else.

I see no purpose in the whole article other then trying to convince me that a Ridge Domain Model is bad, but without a proper explanation. Also a pure function is a function that always gives the same result with the same arguments, it has nothing to do with a Model (DTO, Anemic nor Domain), a Model is about something that holds a state or value, that's whole freaking purpose of DDD, to Encapsulate a Domain state and make it's communications explicit. Not about pure functions.

He could have simple explained that Anemic Models are not all bad, that are situations were they are considered acceptable (like CRUD with RAD) for small applications or game development (as there are technical limitations that make DDD unpractical in this situation).

https://blog.inf.ed.ac.uk/sapm/2014/02/04/the-anaemic-domain-model-is-no-anti-pattern-its-a-solid-design/

class Customer : DomainEntity // Base class providing CRUD operations

This is so bad I cannot use words. (image removed) If your using plain-old CRUD in a Domain Model it's not a Domain Model, period! And using a ActiveRecord in a (Domain) Model, that is new level of failure. I, I, give-up... this is stupid, just stupid 💀 (Edit: Anyone who have read the books of DDD knows this goes completely against separation of concerns and pretty much leaves the Domain coupled to Infrastructure, making such bold statements without knowing DDD is... ughh).

ActiveRecord is already about a Persistence Model, you don't put a Persistence Model inside an Domain Model and then complain it doesn't work, the author could have figured this themselves. ActiveRecord is an Infrastructure detail that in the sense of DDD belongs to another layer then the Domain. Yes, you can use ActiveRecord for persistence but it requires a manual mapping process between the Domain Model and the ActiveRecord Models (which is why ORM is favored for DDD, btw).


There is nothing wrong with using a Model that is considered Anemic, if DDD does not fit the design as there is no actual Domain or there are to many technical limitations. Or if the Application is absolutely to be short lived or starting from scratch with a proper Domain in-place doesn't require to much work.

But please for the love of sanity don't call it a Domain Model! Fowler called it an Anemic Domain Model because it's badly designed and contradicts Domain Driven Design. It is an anti-pattern, but a simple Model that is Anemic is not, just don't call it a Domain Model, OK? 👍

Edit: I changed my wording a bit, It wasn't my intent to insult anyone; I was horrible shocked about some of the bold statements in the articles that completely missed there point.

@donquixote
Copy link

@sstok There is no need for insults.

The main question discussed in these articles, or what I take away from it, is which of the following two options is preferable:

  1. Classes for entities and domain objects also contain a lot of behavior / logic related to this entity / domain object. Sometimes they might need injected dependencies to do those things. Also all pluggable behaviors must be somehow injected into those entity objects.
  2. Classes for entities and domain objects are slim, and most of the behavior and logic lives in their own objects.

The articles I mentioned label these two options as RDM and ADM. You might want to disagree with that, fine. To me it seems quite consistent with the way Fowler used the term ADM in 2003.

In both of these options you can make a stupid example and then point out how bad it is.
Some of the examples in some of these articles might indeed be a bit strawman-y.
But I find the arguments convincing, or at least it shows that Fowler's post from 2003 is not universally agreed on nowadays.

a Model is about something that holds a state or value, that's whole freaking purpose of DDD
[..]
But please for the love of sanity don't call it a Domain Model!

If you use "model" as a term for the class itself, the class that holds the data, then yeah fine.
If you use "model" to describe the architecture that maps real-world stuff into code, then I see nothing wrong with still calling it a model. I think this is how the authors of those articles use the term.

They call it anemic, if behavior and data live in separate classes/objects, but the entire collection of classes is still a model in this second meaning of the term.

@sstok
Copy link
Contributor

sstok commented Apr 19, 2018

@sstok There is no need for insults.

It was not targeted at your personally but the articles (and how I think about them) 👍 (I should have made this more clear, sorry). I agree with the points you make, nothing wrong with an Anemic Data (not Domain) Model.

My only problem is with the way the linked articles try to explain there point, and how the authors failed to explain this (ActiveRecord? and then in the second example the author uses a Factory).
I am (can we say) allergic to misinformation and improper explanation from these so-called experts, especially when telling me that a Ridge Domain Model is bad, and then giving poor examples.

I am open to discussion, and I can understand and agree to a difference of opinions, I believe something till someone can provide arguments to prove otherwise 👍

(The DDD books actually talk about situations where DDD is not a suitable, which is why I don't believe the authors have actually read the books on DDD and merely base there opinions on something they heard from somewhere, it's dangerous and leads to misinformation and flame wars (I actually used to believe ORM is inherently bad because someone once told me that 😑 )).

But to be clear, it was not my intent to insult you (or your believes).

@donquixote
Copy link

But to be clear, it was not me intent to insult you (or your believes).

Ok, then take my first sentence as defending those who are not here :)
The picture you posted certainly does spread some negative energy.

My own motivation is this:
There is package code, and there is application-specific code.

  • Package code can be reused in other applications, and it can be reused, unmodified, in future versions of the same application. It is agnostic of the application domain, but it may provide building blocks for the application. Such a package may contain atomic behavior objects with interfaces, and value objects. All of this can be easily tested.
  • Application code is specific to the application domain, and must be modified if you change how the domain works.
  • If you work with a CMS (e.g. Drupal), a lot of this composition does not even happen in code, but in configuration. (I mentioned Drupal, but don't let this side-track us!)

I think it is advisable to have a lot of reusable package-like code, and only a thin application-specific layer on top of that, which does nothing more but combine the building blocks from package code. Especially with a CMS, if you plan to build various sites which are all similar-but-different, then you want most of your logic to live in domain-agnostic components, not in domain-specific code.

As for object types, you have

  • Service-level objects, which are instantiated more or less once per request. I say service-level, not service, because some might just be parameterized behavior which is not registered as a service.
  • Entity-level or value objects, which are instantiated for every entity or value loaded from somewhere.

If all your application-specific behavior is living in the classes that also hold your data, and that are instantiated per "entity", but you still want to compose this from reusable package-level components, then you will have to repeat the same composition for every new instance of a domain object. You could say they become "Frankenstein compositions" (I just made this up). Perhaps the entity instance is now encapsulated from the outside (not sure), but this does not buy you much.

With ADM, you would still have the equivalent composition in your service-level objects. But I think composition on this level is a lot more pleasant than having it in every entity.

@javiereguiluz
Copy link
Member

javiereguiluz commented Apr 19, 2018

@sstok expressions like --> this is stupid, just stupid and the image you posted don't help to maintain calm discussions. Of course I know you from Symfony Slack and I know that you are not a troll and you meant no offense. In any case, we don't have a Code of Conduct yet (it will be approved in the coming days) so we can't do anything about this ... but please, keep calm when discussing with others. Thanks!

@sstok
Copy link
Contributor

sstok commented Apr 19, 2018

Will do ❤️ Like I said, I have some serious issues with the contents of the linked articles, that's the reason for my excessive reaction, not to excuse my actions.

I changed my reply a bit, explaining it wasn't my intent to insult anyone (but I can't find a respectful word to describe my disbelief 🤫 ).

OK. I will shut-up now 🙇🏼‍♂️ 😃 (or at least be less aggressive or condescending).

@maryo
Copy link

maryo commented Apr 20, 2018

As the name of the antipattern implies, anemic model means that the model lacks something very important like blood lacks enough healthy red blood cells or hemoglobin which is always bad if you want to live properly without external services like hospitals. I like the name of the antipattern as it nicely explains itself in a metaphore.

There are very rare situations where having only getters and setters should not be considered anemic because it is complete already and does not need to have more behavior than to exist and does not need to protect more invariants, it's just a data structure (something like simple Settings object for example). But this is inherent more to OOP itself than to DDD. It's an OOP antipattern and it's always bad.

There is always a better approach even in those reusable packages. There are certainly situations where using a hack or a bad practice is a good tradeoff for solving someone else's bad decision but that still does not make it a good practice.

In Symfony I use Commands and https://github.com/thephpleague/tactician-bundle. I bind these commands to Symfony Forms so the default PropertyPathDataMapper is mostly good enough.
For value objects I use my custom form types where I use custom DataMappers by having the form types implement the DataMapperInterface, anonymous class could be also fine. I think this part of the Form component and the DataMapperInterface could be better designed, mapFormsToData(FormInterface[]|Traversable $forms, mixed &$data)
. From my point of view it would be better to get rid of the pass by reference argument and the Traversable argument has no ArrayAcces so you need to convert it to array first using iterator_to_array to get to the elements you want. But otherwise it works fine and one does not need to use them if the one is OK with not having value objects bound to the form and creates them inside command handlers. Maybe a command bus could be a candidate for a new Symfony component.
I haven't used Serializer component yet but serializing should work fine. Deserialization of entities is not a good idea I think, deserialization of commands should also be OK i think.

@donquixote
Copy link

As the name of the antipattern implies, anemic model means that the model lacks something very important like blood lacks enough healthy red blood cells or hemoglobin which is always bad if you want to live properly without external services like hospitals. I like the name of the antipattern as it nicely explains itself in a metaphore.

Some things are actually better if they are are bloodless and inanimate. It makes their behavior more predictable. E.g. you don't want your car keys to obtain life and jump out of your pocket.

There are very rare situations where having only getters and setters should not be considered anemic.

An object with only getters and setters can be an indication of poor design. If that's your definition of anemic, then I think I agree with you.

But should an entity object be able to load related / referenced entities from the database? I would argue an object that can do this has too much blood, not too little.

A good middle ground might be to let an entity object be responsible for a limited amount of internal or object-level constraints / invariants, which do not require injected services (or very few of them, perhaps?). Whereas "external" behaviors and constraints, and transactions between entities, would have to be handled by external services.

With this approach don't necessarily end up with entities which have only getters and setters. But you might want to call it anemic because it has fewer responsibilities.

So, perhaps this is just a debate about terms, in which case I have to apologize for the distraction.

@donquixote
Copy link

So, perhaps this is just a debate about terms, in which case I have to apologize for the distraction.

Perhaps #7460 would have been a more suitable place for this side-track. I just came here because the other one was closed as duplicate.

@sstok
Copy link
Contributor

sstok commented Apr 21, 2018

Maybe a command bus could be a candidate for a new Symfony component.

Actually, we now have the Message (or Messenger) Component 👍

But should an entity object be able to load related / referenced entities from the database? I would argue an object that can do this has too much blood, not too little.

Loading entities from a database (or persistent storage to be more exact) in the Domain layer is an Infrastructure detail, so this would violate the separation of layers. You can make an Entity hold other entities (Aggregate) keeping the information together as you can't for example have a InvoiceRow without an Invoice to hold them, if you create the object but assign the invoice later your InvoiceRow would be an an invalid state as it's invariant is violated (a row cannot exist without an invoice).

Which is indeed against OOP principles in general.

Yes, you can make the $invoice a mandatory argument of the InvoiceRow constructor, this would fix it. It was merely an example 😋

A good middle ground might be to let an entity object be responsible for a limited amount of internal or object-level constraints / invariants, which do not require injected services (or very few of them, perhaps?). Whereas "external" behaviors and constraints, and transactions between entities, would have to be handled by external services.

With this approach don't necessarily end up with entities which have only getters and setters. But you might want to call it anemic because it has fewer responsibilities.

Actually this is perfectly fine 😁 in fact it's Domain Driven Design (or to a degree) 🤪. Many people misbelief that a Domain Model (Entity/Value object) is about doing everything (my former self included), that's in fact not the case at all.

A Domain Model (or Domain Object to be exact) is about encapsulating business knowledge (and process) into a model (in the broad sense), persistence and services do have there existence in-here but in the rightful place. Persistence is handling in Infrastructure (the Domain only defines an interface), while Services (depending on there purpose) reside in the Domain or Application layer. The Domain Services handle what cannot be done in a Entity/Value object because it doesn't belong there (like changing other entities), but is still strictly related to the business process.

The Application Services are usually more leaning towards the interaction (UI) part and e-mail messages (to name something).

Nothing personal, but if you haven't read any of the books on DDD (in particular the ones from Eric Evans), I suggest you do 👍 when I got started with DDD most of my knowledge and information came from articles on the web, which give a good impression and are still helpful for some unclear cases, but don't cover all the topics what DDD is about, including why it was invented in the first place, muddying the waters and leaving me with half broken implementations and unworkable models.
An Anemic Model would have, in fact worked much better then 😅

An anemic domain model as Fowler explained is about Entities without any own responsibility, all the actual business logic is placed in Services only, robbing the Entities from responsibility they are able to handle with ease (like a correct status), which basically turns them into Data transfer objects that are persisted.

I think most people misunderstand this and fear that a Domain Model is just to complex and that an Anemic model is easier to work with, or that they try to hard, putting everything in Entities.
Granted, DDD is complex not something one can easily grasp (I am actually still learning the deeper details), and requires deep understanding of a business process. It's an almost completely new way of thinking that challenges us in many ways.

As I said before, an anemic data model is not bad perse, but it's not a Domain Model for sure.

I just came here because the other one was closed as duplicate.

No problem, it's good to have conversations like these 👍 (my attack of before was completely unnecessary! It wasn't the first time I reacted, like this (but hopefully the last), and I am trying to more respectful and calm; Heck I am the one who wrote the (initial) constructive criticism guide for contributors 😳 ). I hope this doesn't discourage you from having conversations like these 💚

@maryo
Copy link

maryo commented Apr 22, 2018

So, perhaps this is just a debate about terms, in which case I have to apologize for the distraction.
I believe it is. But i think it is quite important to agree that an anemic domain model is always bad practice in OOP applications so Symfony deciders feel the need to present better examples inside docs and so on.

Some things are actually better if they are are bloodless and inanimate. It makes their behavior more predictable. E.g. you don't want your car keys to obtain life and jump out of your pocket.

:-D. Yes. The intent of the key is probably very different.

An object with only getters and setters can be an indication of poor design. If that's your definition of anemic, then I think I agree with you.

Exactly. It's an indicator. But there are situations where having classes with getters and setters only is OK because they are actually not fully-fledged objects. Their intent is just to carry information. But the domain objects with their behavior still must exist. If they don't then it is closer to procedural programming where you don't send messages between objects but manipulate with datastructures (= calling setters or setting public properties on "entities") by calling procedures (mostly by calling methods on so-called service objects).

But should an entity object be able to load related / referenced entities from the database? I would argue an object that can do this has too much blood, not too little.

I would agree.

A good middle ground might be to let an entity object be responsible for a limited amount of internal or object-level constraints / invariants, which do not require injected services (or very few of them, perhaps?)

Sort of. You don't need to put everything into entities. But the object should always be consistent and it should protect all it's invariants ie never get into an invalid state.

Actually, we now have the Message (or Messenger) Component
OH. You're right. I forgot this new component.

And although I like DDD I think we don't need to refer to it since these principles are inherent to OOP.

@JarJak
Copy link
Contributor

JarJak commented Jun 12, 2018

@dunglas

we can all agree that getters and setters bring nothing except complexity compared to public props

This is valid for statically typed languages like Java in the link. In PHP you can't define property type, so it's better to generate those dumb setters just to ensure basic data integrity.

But again, this is valid only to RAD.

@JarJak
Copy link
Contributor

JarJak commented Jun 21, 2018

My previous comment would not be valid anymore: https://wiki.php.net/rfc/typed_properties_v2

@dunglas
Copy link
Member

dunglas commented Jun 21, 2018

@JarJak this RFC is awesome, but in Symfony/API Platform we already have everything needed to ensure type safety using PHPDoc annotations (or other sources of metadata, including Doctrine mappings):

@Majkl578
Copy link
Author

Majkl578 commented Jun 21, 2018

but in Symfony/API Platform we already have everything needed to ensure type safety using PHPDoc annotations

phpDoc is not a type safety, it's merely just a documentation/hint that is usually relied upon as there is no better way (same applies to unions for example).

@dunglas
Copy link
Member

dunglas commented Jun 21, 2018

@Majkl578 of course a native solution would be way better, but please read the links I posted...

@Levure
Copy link
Contributor

Levure commented Jun 21, 2018 via email

@Ocramius
Copy link
Contributor

Here's the news: software design is hard! Yes, it really is!

Believe it or not, but making state cross your application layers via state mutations rather than explicit interactions makes your software even harder to reason about.

@Levure
Copy link
Contributor

Levure commented Jun 21, 2018 via email

@Ocramius
Copy link
Contributor

You don't need to be rude

Probably confusing sarcasm with rudeness here.

for everyone : starters, intermediate and experts.

Right, so steer everyone off the quick way to complexity.

Unsubscribing from the thread BTW: feel free to leave the "marketing features" well exposed.

@donquixote
Copy link

Do we have code examples (entire project, or some snippet) for the "recommended way" of entity modeling?

@Ocramius
Copy link
Contributor

Try with https://github.com/ShittySoft/fwdays-2018-doctrine-tutorial/tree/feature/specification-testing?files=1 - that's the material of my current workshop.

@donquixote
Copy link

@Ocramius interesting.
I find only one entity class, User. Or am I not looking in the right place?

The User class is interesting:

  • It has no setters or getters.
  • It has no injected services or behaviors. Its only properties are value objects that represent the raw data.
  • The existing methods have mostly no effect on the "outside world". E.g. the login method does not set a session cookie, neither by itself nor through an injected service. It only tests if the password is correct.
  • It is not visibly connected to a persistence layer. Perhaps this happens through outside components?
  • The "register" function behaves as a constructor. Imo this is unrealistic or misleading for a PHP application. I would understand the term "register" as an operation that creates the database record for a user entity (and perhaps some side effects), not something that instantiates a runtime User object.
  • Likewise, I would expect "login" to be an operation that sets a session cookie and session user id, which persists the current request. The existing method is more like "check password".

The relation of persistence record vs runtime entity object is one of the main design challenges for entity modeling with PHP.

Perhaps some of the above points are by design, and others are because this is a "stub project"?

@donquixote
Copy link

The existing methods have mostly no effect on the "outside world".

The "register" method does send an email, but only through a behavior object injected into the method itself. This behavior object is not stored in a property, it is only used in the operation itself. This is a special case because the method is a static factory.

Which leads to the question: If we wanted to add a similar behavior in the "login()" operation, would we pass the behavior object as a parameter to the login() method, or would we keep it as a property?

@Ocramius
Copy link
Contributor

Ocramius commented Jun 21, 2018

Besides this bit:

The existing methods have mostly no effect on the "outside world". E.g. the login method does not set a session cookie, neither by itself nor through an injected service. It only tests if the password is correct.

The rest is by design. This is for a full day of challenging (dangerous) assumptions in software design, where attendees slowly revolve to this kind of implementation.

There are optional bits such as "logging a login" or "preventing brute-force", which are trivially implemented if the state is correctly handled ONLY by the aggregate root (the User here), but it is a demonstration that state mutation encapsulation keeps things manageable. The entire specification of the features of the software can even be tested with just the aggregate root as SUT.

not something that instantiates a runtime User object.

The word register means something very specific. If you are instantiating this object from stored state, then you may unserialize it, which is again something very specific. This object cannot be instantiated (in the domain or its public API) by design. This conveys the clear purpose of it to anyone wanting to use it.

The "register" function behaves as a constructor. Imo this is unrealistic or misleading for a PHP application

Built already a few applications with these approaches: they work fine, and it is very hard for new developers to misunderstand how to use the domain components. The words in the API match the ubiquitous language as much as possible too.

Which leads to the question: If we wanted to add a similar behavior in the "login()" operation, would we pass the behavior object as a parameter to the login() method, or would we keep it as a property?

Only retain state that you are responsible for. The lifecycle of services and User instances are different, so a User should never have a state reference to a service. Yes, you'd pass a service at call time (note that this is maybe a bit weird for people that are too used to "OOP", but it is the normal way to do it in FP).

See also http://ocramius.github.io/blog/on-aggregates-and-external-context-interactions/

@donquixote
Copy link

I'm afraid the example is simplified to a level where the questions from this thread are not really touched. Or again I might not be looking in the right place.

  • People might want to add a $user->getName(), to show the username somewhere on a page.
  • People might want to add setter methods so the object can be manipulated when a form is submitted.
    (This would make the object mutable, which currently it is not.)
  • People might want to reference the user object from a different entity, e.g. a blog post, to specify the author. I wonder if the email is meant as the primary key for this purpose, or if there is an id which is just not part of the object.
  • People might want to add mechanisms to load and save user objects.
  • People might want to inject services or behavior objects into the user object, which help with tasks like saving to the database, or for side effects of specific operations.

A good example would demonstrate how these steps are not really necessary, and the problems can be solved in other ways. Unless you actually agree with the above :)

I am writing this because I think the current discussion might be too theoretical.
So if you or anyone tells a person that ORM code generation is bad, people might have very different ideas what should be the alternative.


I personally have implemented different domain model architectures for different purposes, and I have not found the ideal way yet.

But most of the time, I follow the entity model of the CMS I work with (Drupal 7, Drupal 8), which you might like or dislike for various reasons. Some observations from this:

  • Many of the data constraints are defined in configuration, and not known to the framework programmer. This allows users to create new entity bundles (content types, product types etc) and fields via the UI without new code being written or generated.
  • Drupal 7 entities are \stdClass objects with no object-level constraints whatsoever.
  • Drupal 8 entities have a huge inheritance hierarchy, and attempt to do everything within the entity object. They would need injected services, but instead get them via static calls to the container (ouch).

A lot can be said about this, and most of it would be out of scope for this issue.

But I imagine one main reason for the really heavy entity objects in Drupal 8, and their dependency on services, was the implicit or explicit desire to make them not anemic.

@Ocramius
Copy link
Contributor

People might want to add a $user->getName(), to show the username somewhere on a page.

In "real world" apps, this is where the concepts of "read model" and "view model" come in. Aggregates are designed to keep all the state private, and never really tell anything to the outside, which gives you a degree of freedom in changing logic. Read models allow for aggressively optimising read operations via low-level operations (caching, memoising, SQL optimisations, async parallel execution) and for keeping the results on-point with what is expected. I don't expect people to split reads/writes from this discussion, but be aware that valid and battle-tested solutions do exist here. I'd already be very happy to get rid of the concept of "setters" here.

People might want to add setter methods so the object can be manipulated when a form is submitted.

Terrible idea to bind entities to forms. https://webmozart.io/blog/2015/09/09/value-objects-in-symfony-forms/ instead. I personally don't use symfony forms, but all my forms work with arrays or DTOs these days.

People might want to reference the user object from a different entity, e.g. a blog post, to specify the author. I wonder if the email is meant as the primary key for this purpose, or if there is an id which is just not part of the object.

We're probably going too deep into tactical DDD concepts, but:

  • Yes, the PK would be the email
  • No foreign keys outside the graph of objects that are inside a User (also here, has nothing to do with this issue specifically)
  • Most referenced data can be produced by read models

People might want to add mechanisms to load and save user objects.

Yes, Doctrine ORM is a mapper like any other! You should be able to write trivial reflection-based mappers for any kind of aggregate. The referenced repository includes some primitive implementations that are sufficient to save/load entities: https://github.com/ShittySoft/fwdays-2018-doctrine-tutorial/blob/feature/registration/src/Infrastructure/Authentication/Repository/FileUsers.php

People might want to inject services or behavior objects into the user object, which help with tasks like saving to the database, or for side effects of specific operations.

Please don't: dependency injection is only applicable when lifecycles are compatible. This is why it is usually only valid for long-lived instances (services).

I am writing this because I think the current discussion might be too theoretical.
So if you or anyone tells a person that ORM code generation is bad, people might have very different ideas what should be the alternative.

I was asked for a clean code example: well here it is. I think this is the way to write software in the PHP language and Doctrine ORM (at the best of my current knowledge, as of today, 2018-06-22).
Whether this can be interpreted differently is another question, but I demonstrate widely enough that getters/setters can be easily avoided by gaining expressiveness, reduced amount of code, maintainability and ease of use (all of them together). This is what the "designing software" is all about (instead of c&p from tutorials).

I think this quote is strictly related and very much fitting the thread.

A: Please help all my code is shit.
B: Here’s a different way to program.
A: Fuck you that’s absurd.
https://twitter.com/PttPrgrmmr/status/1006431230533959680
@PttPrgrmmr

As for the Drupal bit: I can't comment, as I don't remotely know how Drupal works, and I strive to stay independent from the tooling in order to keep the language close to the business and the upgrades or migrations manageable. My tip: it shouldn't matter if it is in the context of Drupal or not.

@Guikingone
Copy link
Contributor

If I can give a position about this issue, here's what I'm thinking about.

Should Symfony have a position about model design?

No.

If we look at the main goal of Symfony, the main goal is to manage HTTP requests/response, what we do with this requests/response and all the logic which is involved between the request and response is up to us.

So, should Symfony know about our lower-level policies?

No.

Symfony isn't designed for this, even if the Security component use a User model, is core design isn't prepared to handle model, that up to us to include and call model, we can easily tweak the internal logic to use DTO, Value Object or even array, the choice is up to us.

I know that the debate is about rich vs anemic models but well, should Symfony care about this?

No.

Symfony can easily work without a single model and you know what ? That's probably the best idea we can have, our business logic isn't tied to Symfony, we use Symfony in order to have a "set of tools" that help us to build modern, reliable and evolutive applications, no more.

I'm fully committed to the vision of @Ocramius when it comes to the Form component, using DTO or value object is probably the best idea we can have, why ? Simply because depending on a Symfony component is bad, why ? Simply because the framework can evolve and the component can change, what if some functionality disappear and our logic no longer work?
Symfony is a tool and we need to use it as it was designed by the core team, we could use some components but we're not forced to do so, I'm pretty happy with this vision, and you know why ? Simply because sometimes, I don't need all the framework logic to display some data or to handle some user action.

Same things go when it comes to API-Platform and what @dunglas has designed, we can use the internal library but what if we need to design our own logic and adapt API-Platform to our lower-level policies? Well, Dunglas and the core team do a pretty solid work and we can easily do it, we're not tied to his main vision when it comes to handling operations and other internal logic.

For me, Symfony shouldn't give a "sterile" and "final" vision, the developer should be in control of what he do when it comes to lower-level policies, the developer should be able to adapt his logic to the user need and no matter what it does with it, not a single library should say :

You can't do this, that not how we work !

So, what about the idea of bringing a rich model to the documentation ? I'm not fully okay for it, Symfony could easily say :

Here's a way to design your internal logic, keep in mind that you could adapt it to your needs.

Symfony doesn't care about our model logic/design and it shouldn't IMO but well, like said before in this thread, the final choice come to the developer, not Symfony.

@donquixote
Copy link

In "real world" apps, this is where the concepts of "read model" and "view model" come in.
[..]
all my forms work with arrays or DTOs these days.

It sounds interesting, but am not sure I am following.
I assume that "read model", "view model" and "DTO" (data transfer object) are all different classes / objects that contain information about the entity (e.g. the user), just for different purposes, with different roles in the application. This would mean you have to write more than one class per entity type.

But perhaps I got this all wrong? Perhaps a DTO in your sense is not even specific to an entity type, and does not do any validation by itself? E.g. an \stdClass?

I could read about "read model" and "write model" and "DTO", but this does not guarantee that I get the same idea about it as you do.

Terrible idea to bind entities to forms.

Again I am not sure I understand what you mean.
You have a form where a user can create or modify a blog post, or edit their user account. You need to load and save the entity, and map form elements to entity values, and you need to validate and normalize the data before it goes into the database.

Of course not every form maps 1:1 to an entity type, but for sure such forms do exist and are quite common.

The entity object itself should be agnostic about forms.
But it still needs an API that allows a form to interact with it.
Or, if you use a data transfer object, than this DTO needs an API for forms to interact with it, which only shifts the problem. Or, if the DTO is sth unstructured like \stdClass, then perhaps the entity class needs an API to interact with DTOs.

People might want to add mechanisms to load and save user objects.

Yes, Doctrine ORM is a mapper like any other! You should be able to write trivial reflection-based mappers for any kind of aggregate. The referenced repository includes some primitive implementations that are sufficient to save/load entities: https://github.com/ShittySoft/fwdays-2018-doctrine-tutorial/blob/feature/registration/src/Infrastructure/Authentication/Repository/FileUsers.php

This is interesting. The solution uses reflection to access private properties, so you don't need any getters. This looks like a hack to me. I am open to unorthodox solutions, but I would need some convincing why this is a good idea, e.g. why this is better than public properties.

I guess the idea is that only some selected and trusted classes are supposed to access those private properties. So you are emulating something like a "friend class" mechanic, except that without a native "friend class" feature, it is not documented which classes are allowed to access the private properties.

You are then using serialize/unserialize to store the object to disk, which again frees you of the need for setters and getters. I don't really like serialized objects, I think that the persistence model should be independent of the runtime model. Perhaps I am wrong.

People might want to inject services or behavior objects into the user object, which help with tasks like saving to the database, or for side effects of specific operations.

Please don't: dependency injection is only applicable when lifecycles are compatible. This is why it is usually only valid for long-lived instances (services).

I am not talking about dependency injection containers here, just about injecting services into a component which uses them, instead of letting this component access the global container.

We can assume the life cycle of an entity object to be shorter than that of a service. A service should not get an entity injected, but an entity can be injected with a service on construction time. Or, which is perhaps better, avoid having entities depend on services.

There are practical reasons why Drupal uses the global \Drupal::service(..) instead. https://github.com/drupal/drupal/blob/8.6.x/core/lib/Drupal/Core/Entity/Entity.php#L82

The advantage is that the entity does not carry services around. The disadvantage is that testing the entity requires a global \Drupal::service(..) to be wired up correctly.

I was asked for a clean code example: well here it is.
[..]
getters/setters can be easily avoided

Yes, this is what I am looking for. An example that eliminates the common reasons why people add getters and setters to their models.

I am not yet convinced that your example sufficiently shows this, but we may be getting there.
I hope that my questions are somewhat helpful to others and to the general direction of this thread, and not just to entertain my own confusion.

@Ocramius
Copy link
Contributor

Ocramius commented Jun 24, 2018

TIL answering to a thread re-subscribes you to it.

Gonna try to keep this short:

In "real world" apps, this is where the concepts of "read model" and "view model" come in.
[..]
all my forms work with arrays or DTOs these days.

It sounds interesting, but am not sure I am following.
I assume that "read model", "view model" and "DTO" (data transfer object) are all different classes / objects that contain information about the entity (e.g. the user), just for different purposes, with different roles in the application. This would mean you have to write more than one class per entity type.

A read model is an abstraction on top of a read-only data store. It could be a class containing an SQL query, for example.

But perhaps I got this all wrong? Perhaps a DTO in your sense is not even specific to an entity type, and does not do any validation by itself? E.g. an \stdClass?

Usually array only

I could read about "read model" and "write model" and "DTO", but this does not guarantee that I get the same idea about it as you do.

Terrible idea to bind entities to forms.

Again I am not sure I understand what you mean.

Simple: don't let forms talk to entities directly, as you are mutating state without checking business invariants. Just because a field called active can be bool, doesn't mean that all permutations of a checkbox are applicable. The example I do for Employee are interactions such as Employee#hire(), Employee#fire(), Employee#expireContract(), Employee#renewContract() Employee#suspend() and Employee#markAsLost(). These are NOT about slamming state from one layer (form) into the next one. The form abstracts an interaction into a payload of "validated" intent by the frontend user, not state to be copy-pasted. What sort of state mutations are caused by said payload is a completely different story that the form should not know about.

You have a form where a user can create or modify a blog post, or edit their user account. You need to load and save the entity, and map form elements to entity values, and you need to validate and normalize the data before it goes into the database

Mutating a blogpost is not an atomic operation. You can:

  • add metadata
  • remove metadata
  • update the text
  • change the text
  • copy it
  • migrate the URI (extremely important to create 301 redirects here)
  • publish it

Some of these can succeed, some of these can fail depending on what you enforce as "valid" inside your concept of BlogPost (which is a terrible example BTW, because it's the typical developer over-simplification of business domains).

The above ones are either 7 different forms or 7 different interactions to be piped in a queue of operations to be executed all at once (UX decision).

Don't represent state in the UI and then slam it back into the entities à la foie gras, because you end up duplicating biz constraints everywhere. The single "migrate the URI" scenario is already a nightmare once anemic models are in play. Also, do all other scenarios lead to an immediately published post?

Of course not every form maps 1:1 to an entity type, but for sure such forms do exist and are quite common.

The entity object itself should be agnostic about forms.
But it still needs an API that allows a form to interact with it.

No, it doesn't - it is told what should happen once the interaction is translated from a form into a method call (Employee#renewContract(... data from the form here ...), for example).

Or, if you use a data transfer object, than this DTO needs an API for forms to interact with it, which only shifts the problem. Or, if the DTO is sth unstructured like \stdClass, then perhaps the entity class needs an API to interact with DTOs.

The only shifts the problem bit is what I think isn't getting through: state should not be mutated outside the entity, but should be owned by the entity, and mutations should be controlled.
We are removing state mutation problems that lead to extremely complex head-scratchers later on (Employee#fire(), Employee#renewContract() and BlogPost#migrateToUri() are simple interactions that become extremely complex once you let forms talk to entities).
The translation of HTTP-to-domain-call is what you'd usually put in a controller, here's some pseudo-code c&p'd from one of my other examples:

    public function purchaseAction()
    {
        $this->securityCheck->assertValidUser($this->request);

        if (! $this->form->isValid($this->request)) {
            return new ViewModel(['form' => $this->form]);
        }

        $data    = $this->form->getData();
        $user    = $this->authentication->requireUser();
        $product = $this->products->get($data['product']);

        // actual domain logic call. 'amount' was already validated
        $product->purchaseWithUser($user, $data['amount']);

        $this->notifications->send($user, 'Purchase completed');

        return new ViewModel(['success' => true]);
    }

People might want to add mechanisms to load and save user objects.

Yes, Doctrine ORM is a mapper like any other! You should be able to write trivial reflection-based mappers for any kind of aggregate. The referenced repository includes some primitive implementations that are sufficient to save/load entities: https://github.com/ShittySoft/fwdays-2018-doctrine-tutorial/blob/feature/registration/src/Infrastructure/Authentication/Repository/FileUsers.php

This is interesting. The solution uses reflection to access private properties, so you don't need any getters. This looks like a hack to me. I am open to unorthodox solutions, but I would need some convincing why this is a good idea, e.g. why this is better than public properties.

It doesn't matter if you serialize it, json-encode it, gRPC it, transform it to YAML and back (good luck) and then finally push it through memcached served through MySQL.
Reflection is fine here, we are making a snapshot of an object, freezing it in time and then re-constructing it exactly like it was before.
As soon as we change the given entity data, we are making things extremely complex for everyone, which is why things like entity listeners and SQL triggers that modify entity data are a big no-no.

Mapping is not an OO concept, but an FP and procedural concept (when persistence is involved).
Here we are effectively copying data (very important -> WITHOUT MUTATING IT <- important, really, I MEAN IT!) from somewhere to somewhere. As long as it's just about copying, you can even use a dump of the low-level PHP process to achieve that: that's not important.

I guess the idea is that only some selected and trusted classes are supposed to access those private properties. So you are emulating something like a "friend class" mechanic, except that without a native "friend class" feature, it is not documented which classes are allowed to access the private properties.

Just use reflection there.

You are then using serialize/unserialize to store the object to disk, which again frees you of the need for setters and getters. I don't really like serialized objects, I think that the persistence model should be independent of the runtime model. Perhaps I am wrong.

Yes, it's an example aimed at showing that persistence can be easily achieved, and that "thinking in tables" is easily avoided. People should use whichever persistence layer fits their needs best, and not take Doctrine ORM and its semantics for granted.

People might want to inject services or behavior objects into the user object, which help with tasks like saving to the database, or for side effects of specific operations.
Please don't: dependency injection is only applicable when lifecycles are compatible. This is why it is usually only valid for long-lived instances (services).
I am not talking about dependency injection containers here, just about injecting services into a component which uses them, instead of letting this component access the global container.

injecting services into a component which uses them is dependency injection. No containers (DIC) involved. Do not retain dependencies inside objects with different scopes. Services should be given at call-time and then discarded.

About the \Drupal::service(): that is properly horrible, but it takes time to migrate away from it. Magento 2 is an example of a complex piece of software that managed to migrate away from such patterns: yes, some of their constructors are terrible, but still better than the implicit service location happening everywhere at runtime

@carsonbot
Copy link
Collaborator

Thank you for this issue.
There has not been a lot of activity here for a while. Has this been resolved?

@wouterj
Copy link
Member

wouterj commented Dec 18, 2020

Seems like we've removed all mentions of doctrine:generate:entities already. Let's close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests