0

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.

update #1

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;
         }
    }
}
Jack
  • 227
  • 3
  • 8

1 Answers1

2

Separate Domain and Data layer

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.


Accessing Data layer

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 = '11@gmail.com';

Note, that UserDomain is extended from \yii\base\Object to make setter available.


Set ActiveRecord attributes automatically

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.

wormi4ok
  • 602
  • 7
  • 11
  • I have a question, by using composition, I have to write mapping code to map ActiveRecord's attributes to domain's attributes which I think is familiar with data mapper... Or I can use php magic function '__set' and '__get' in domain, to set and get attributes from ActiveRecord by using this way? The sample code is in my post above in **update #1** – Jack Apr 17 '17 at 08:33
  • No, using magic function could cause unexpected behavior. I'll update answer to explain myself. – wormi4ok Apr 17 '17 at 08:48
  • em... My question is how to map ActiveRecord attributes to domain attributes automatically... IoC just helping you to creating object with its dependency, but how can I set value to ActiveRecord automatically.. For example, if I want to change user email, I can use `$uesr_domain->email="11@gmail.com";`, than I want to save it to database, how can I set this attribute to it's `$model`? – Jack Apr 17 '17 at 09:02
  • Yes, you're right, @Jack, my mistake. I just intended to warn you against using the wrong architecture. But seems that you made a correct decision. Best regards! – wormi4ok Apr 17 '17 at 10:30
  • Yeah, I got it. Very thank you for your patience to give me a solution and I learn more from this discussion. Best regards! – Jack Apr 17 '17 at 12:05
  • check out "behaviors" http://www.yiiframework.com/doc-2.0/guide-concept-behaviors.html – e-frank Apr 17 '17 at 21:28
  • this might show you DI in action http://stackoverflow.com/questions/38582341/yii2-configurable-models-inside-module/38591495#38591495 – e-frank Apr 17 '17 at 21:29
  • I have a rather technical question. What I want to do is not injecting these ORM entities to the domain entities. I would rather use repositories or services for saving the data. Is there a way to avoid somehow this constructor auto injection into the models? I am pretty new to Laravel and I am porting an old project to it where we used domain modelling and doing active record instead sounds gross. I just don't understand based on a fast read of the manual how I can solve this properly with Laravel. Or what kind of implicit data transfer happens in the background with this framework. – inf3rno Jan 17 '23 at 14:39