Object structure for complex models

Hi all,
After a quick discussion in mastodon I am referring to you for a discussion on the proper structure of objects and method.

The simplified context:
I have a user in the DB with some properties like email and etc, also several related tables with let’s say inventory, purchases and etc.
So far so good, we keep all in AR models.
We have some objects with business logic on things not related to the particular entity like get stats, or etc.

The case:
We have, though, some user-specific data and functionalities like some score calculations or functionalities get full properties list that are not a field in db but rather cover several tables and/or calculations over random user-related data. They don’t belong to our services/managers or other containers of business logic either but somewhere in-between.

Question:
Where do these user methods belong? How do I organize user business object around the data in the db AND the extra stuff that doesn’t belong in the model.

1 Like

If you have simple calculations that directly belong to the user you can place them in the model as getters or virtual properties.

If you want to separate the code more from the user model you can create separate classes:

class UserStatistics extends \yii\base\Component {
    public function __construct(protected \app\models\User $user) { } // using php 8 syntax here for compactness
    // ... 

    public function getStatisticData() {
        // a lot of statistics code here
        return $result;
    }
}

You could connect that to the User model like this, but you don’t have to:

class User extends \yii\db\ActiveRecord
{
    // ...
    
    public function getStatistics(): UserStatistics
    {
        return new UserStatistics($this);
    }
}

If the UserStatistics class needs more than the user you can add these dependencies to the constructor as well. Provide them from the DI container or load them via other parameters dependent on where they come from.

You could place these classes inside the models directory and make a sub-namespace for them or put them into components either directly or with a sub-namespace, depends on how close they are to the model or control layer. If logic belongs more to the view layer, a Widget might be the right thing to choose.

The above example is just one way to do it. If you have a more concrete example other options may fit better. Depends on the case.