These 2 design patterns are described in the Martin Fowler book "Corporate Application Templates" and are ways to work with data storage in object-oriented programming.
Sample Active Record Template
class Foo { protected $db; public $id; public $bar; public function __construct(PDO $db) { $this->db = $db; } public function do_something() { $this->bar .= uniqid(); } public function save() { if ($this->id) { $sql = "UPDATE foo SET bar = :bar WHERE id = :id"; $statement = $this->db->prepare($sql); $statement->bindParam("bar", $this->bar); $statement->bindParam("id", $this->id); $statement->execute(); } else { $sql = "INSERT INTO foo (bar) VALUES (:bar)"; $statement = $this->db->prepare($sql); $statement->bindParam("bar", $this->bar); $statement->execute(); $this->id = $this->db->lastInsertId(); } } }
In this simplified example, a database descriptor is entered in the constructor of Foo (Using dependency injection here allows you to test an object without using a real database), and Foo uses it to save its data. Do_something is just a stub method that replaces business logic.
Benefits of Active Record
- Writing code with Active Record is quick and easy, in the case when the properties of the object correspond directly to the columns in the database.
- Saving happens in one place, which makes it easy to learn how it works.
Disadvantages of Active Record
- Active Record models violate the principles of SOLID . In particular, the principle of common responsibility ( SRP - “S” in the principles of SOLID ). According to the principle, a domain object should have only one area of ​​responsibility, that is, only its own business logic. Calling him to save data, you add to it an additional area of ​​responsibility, increasing the complexity of the object, which complicates its support and testing.
- Implementing data storage is closely related to business logic, which means that if you later want to use another abstraction to save data (for example, to store data in an XML file and not in a database), then you will have to refactor the code.
Data Mapper Example
class Foo { public $id; public $bar; public function do_something() { $this->bar .= uniqid(); } } class FooMapper { protected $db; public function __construct(PDO $db) { $this->db = $db; } public function saveFoo(Foo &$foo) { if ($foo->id) { $sql = "UPDATE foo SET bar = :bar WHERE id = :id"; $statement = $this->db->prepare($sql); $statement->bindParam("bar", $foo->bar); $statement->bindParam("id", $foo->id); $statement->execute(); } else { $sql = "INSERT INTO foo (bar) VALUES (:bar)"; $statement = $this->db->prepare($sql); $statement->bindParam("bar", $foo->bar); $statement->execute(); $foo->id = $this->db->lastInsertId(); } } }
In this case, the Foo class is much simpler and should only worry about its business logic. Not only does he not have to save his own data, he does not even know and does not care about whether all his data has been saved.
')
Advantages of Data Mapper
- Each object has its own area of ​​responsibility, thus following the principles of SOLID and keeping each object simple and to the point.
- Business logic and data storage are loosely coupled, and if you want to save data to an XML file or some other format, you can simply write a new Mapper, without touching the domain object.
Disadvantages Data Mapper
- You will have to think much more before writing the code.
- As a result, you have more objects to manage, which complicates the code and its debugging a little.
Service Objects
When using the Data Mapper design pattern, the calling code must select the Mapper and the business object and link them together. If this is the call code in the controller, then ultimately your model “leaks” into the controller, which can cause major problems with support and unit testing. This problem can be solved by introducing a service object. The service is the gateway between the controller and the model and connects the domain object with the Mapper as needed.
It should be remembered that M in MVC is a model abstraction layer, not a model object. So there can be several types of objects in one model (in the example above, you can have a service object, a domain object, and a Mapper object acting as a single model). On the other hand, if you use Active Record models, your model can be represented by only one object.
Use cases
Active Record objects have historically been very popular due to the fact that they are simpler, easier to understand and faster to write, so many frameworks and ORM use Active Record by default.
If you are sure that you will never need to change the data retention layer (if you are dealing with an object that is an INI file, for example), or you are dealing with very simple objects that do not have much business logic, or just prefer to keep everything in a small number of classes, then the Active Record template is what you need.
Using the Data Mapper, although it leads to cleaner code, easier to test and maintain, and provides more flexibility — the price to it — increases complexity. If you have not tried it yet, then give it a chance - you should like it.
This is a translation of the article by Russell Volcker .