I use yii2 and find that it's active record is very convenient.
But sometimes I find that we always put logic functions in active record which I think should belongs to domain.
And I have looked up some books, most of them suggest using data mapper to mapping database record to domain.
Although it is a good way to split domain and data, I don't want to waste active record feature from yii2.
I think we can make a domain extend from active record so that the database operations will in domain's parent class active record, and business logic operations will in domain:
class UserModel extends ActiveRecord{
// do database operations
}
class UserDomain extends UserModel{
// do domain's logic
}
I don't know is this design great? Please tell me your suggests.
class UserDomain {
private $model;
public function __construct(UserModel $model){
$this->model=$model;
}
public function __set($name, $value){
if (isset($this->model->$name)) {
$this->model->$name=$value;
} else {
$this->$name=$value;
}
}
public function __get($name){
if (isset($this->model->$name)) {
return $this->model->$name;
} else {
return $this->$name;
}
}
}
The approach you assume is definitely better, than writing all business logic in ActiveRecord class. It'll make your code maintainable and clear. One thing missed, in my opinion, is flexibility. The main idea is:
Domain classes should use implementation classes, not inherit from them.
It's not an axiom, but in many cases it's true. Here is an article, to help you choose what design do you need.
A simple example shows how to replace inheritance with composition:
class UserDomain {
private $model;
public function __construct(UserModel $model)
{
$this->model = $model;
}
}
This is the first step to get flexibility. It allows you to use more than one model class and make UserDomain
easier to test, cause it has clean and loosely coupled dependency.
Yii2 uses Dependency Injection pattern help you control such dependencies. Here is a link to official docs with example of usage Dependency Injection Container. In a nutshell, you can create instances of your domain class like this:
$user = $container->get('UserDomain');
And Yii will inject all needed dependencies for you.
Another question is about accessing Data layer from Domain. I'm pretty sure that, using PHP magic methods is a bad idea.
At the UserDomain
class you should use a higher level methods, unlike you do at UserModel
. So, normally, you don't have setEmail()
method at Domain layer, and use updateProfile()
instead.
class UserDomain
{
public function updateProfile(string $name, string $email)
{
$this->model->name = $name;
$this->model->email = $email;
$this->model->save();
}
}
If it's important to use attributes, as you assume in comment, I'd prefer to use Yii implementation of Properties. For an email, code will look like this:
/**
* Class UserDomain
*
* @property string email
*/
class UserDomain extends \yii\base\Object
{
public function setEmail(string $email)
{
$this->model->email = trim($email);
}
}
$userDomain = new UserDomain();
$userDomain->email = '[email protected]';
Note, that UserDomain
is extended from \yii\base\Object
to make setter available.
If you really need to set a lot of ActiveRecord
attributes from your domain layer, than it's not a domain layer at all, in my opinion. In this case, inheritance is a good decision, because you are extending basic functionality of ActiveRecord
with you business logic and not using AR as data mapper.
That is what ActiveRecord
was design for. So it will be convenient and optimal as long as your Domain layer stays simple and clear.