AR focuses on data. Moreover, Eloquent makes this data public. Objects designed around an Eloquent model assume public access to those pieces of data, so encapsulation is harder and cohesion is blurred. Tracking where properties are accessed or modified is harder, even with advanced IDEs, because of magic properties and weak type hinting.
In the context of a Laravel project, data gets validated as arrays (most likely user input extracted from the Request) and, later on, added to an Eloquent model through generic methods:
According to its author, "Laravel has no opinion on where or how you do validation." By default, all your models will inherit generic methods to be constructed or updated:
Model::create(array $attributes);
Model::update($id, array $attributes);
$model->fill(array $attributes);
new Model(array $attributes);
This behavior moves the responsibility of validation and protecting invariants outside of the model. Depending on the architecture of the project, this may be a service layer, a command or an http layer (controller, form request). Having this responsibility outside of the object makes for weak objects, whose state can't be trusted to be valid.
Intercepting this behavior is very difficult, as it involves either overriding multiple methods of the Model class, some of which the Model itself assumes to be safe to call (empty constructors, for example).
While it is entirely possible to do performance optimizations in the AR pattern, Eloquent lacks work in this area. It has no IdentityMap to prevent hitting the database for the same record, does not handle join queries into joined models (with possible column collisions if done wrong!) and has removed it's small cache implementation since version 5.0.
The only methods that allows for query optimization are the with / load
relation eager-loading methods.
Implementing any sort of cache means intercepting internal ORM calls, which by the level of coupling between Model, Query\Builder and Relations, would need to be done at the ORM level. Overriding methods at the Model level cannot accomplish any of this.
Eloquent models inherit a large, generic API that assumes all models will need. This can be a problem when working in large teams, because knowledge of how things should be done is passed through convention instead of enforced by code. For example, a Model::all()
method call could be a self-destruct button in a rather large table. Preventing calls from an already existing public method is more difficult than preventing the creation of a yet-to-be-added method.
While this is true for Doctrine as well (generic repositories also have self-destruct findAll()
methods), Eloquent using inheritance makes this worst: it gets all of these methods closer to the consumer, which is a negative point in the case of unwanted API, and it statically couples to it, so you can't hide it behind an injected dependency.
DataMapper assumes that the object's state can be modeled in a relational manner, but most of the times we end up adapting our modeling decisions to this restriction. This topic is older than Doctrine itself, and while DMs have evolved through the years, it's still a very important constraint.
While database access and usage is no simple task, the mapping layer adds an extra level of complexity to it, one that ActiveRecord explicitly avoids. Reconsitution of objects from the database is dealt by the ORM, and that assumes an internal structure of the mapped objects, which also limits design. Hooking to those processes is possible, but demonstrates how much more complex it is than just overriding a method.
Anemic Domain Modeling is modeling objects with public setters and getters and no real behavior outside of transporting data around. This incurs in the cost of domain modeling, without the benefits of actually adding behavior related to the domain, as described by Fowler in his bliki.
While this is not intrinsic of the DM pattern, it does have something to do with it. In an empty AR model, behavior is always present: AR gives you database access for all your models. But if you design an Entity that does not know about database and does not have any relevant behavior, then you are arguably worst than with AR.
@guiwoda "the drama" is just because it's a recurring topic. Not to be rude...not my intention at all. I said "drama" because it will just pop up every 3 months here and there. As developers we should look at the big picture, and abstract as much as possible. Technologies are replacable and will change. Business rules should remain unaffected. Know the trade offs, embrace both.
About Doctrine, well, just look at Doctrine blog posts or the overall architecture, if having 3 (if I recall correctly: proxies, registry and the mapping itself) caching layers and proxies isn't an overkill for a PHP application that works as a "share nothing" ...
Just try running it without cache and run a heavy query. And yeah, you'll get improvements if you do not hydrate the data, but still. They even reinvented SQL doing DQL. Doctrine is great, and does a lot of abstractions, implements eager and lazy loading. Don't get me wrong.
My main concern in the AR vs DM ongoing topic is, all the abstractions are decided by a library, not by the developer, and this is usually bad, and ends up leading most people doing things around the ORM. And this goes for both Doctrine and Eloquent in this case.