Laravel fit models.
Tags: laravel, php
Published: 2020-10-03 14:32:51

Time ago in the Laravel community, people were talking about how to balance responsibilities between models and controllers. Some people say that it was better making fat models and thin controllers, while others say the opposite. This time I'm going to talk about a strategy to reduce a model when it becomes too "fat".

The concept of "fit models" came up from the need of reducing the amount of methods within a model. Months ago I was looking and thinking for a better way to reduce the amount of methods in a "fat" model and I came up with a solution, and searching for some articles I didn't found anyone who applied this, until I recently found that Freek Van Der Herten tried this same approach.

In a medium-large scale application is normal to have lots of methods for relationships, accessors, mutators and more.

Let's look at this example:

```php

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'birth_date' => 'datetime',
    ];

    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    public function followers(): HasMany
    {
        return $this->hasMany(Follower::class);
    }

    public function favorites(): HasMany
    {
        return $this->hasMany(Favorite::class);
    }

    public function socialNetworks(): HasMany
    {
        return $this->hasMany(SocialNetwork::class);
    }

    public function addresses(): HasMany
    {
        return $this->hasMany(Address::class);
    }

    public function phones(): HasMany
    {
        return $this->hasMany(Phone::class);
    }

    public function messages(): HasMany 
    {
        return $this->hasMany(Message::class);
    }

    public function getFullNameAttribute(): string
    {
        return $this->first_name . ' ' . $this->last_name;
    }

    public function getShortNameAttribute(): string
    {
        return $this->first_name . ' ' . Str::limit($this->last_name, 1);
    }

    public function getAgeAttribute(): string
    {
        return now()->diffInYears($this->birth_date);
    }

    public function getCreatedDateAttribute(): string
    {
        return $this->created_at->toFormattedDateString();
    }

    public function getProfilePicturePathAttribute(): string
    {
        return storage_path('pictures/' . $this->id . '/' . $this->profile_picture);
    }

    public function setFirstNameAttribute(string $firstName)
    {
        $this->attributes['first_name'] = Str::studly($firstName);
    }

    public function setLastNameAttribute(string $lastName)
    {
        $this->attributes['last_name'] = Str::studly($lastName);
    }
}

```


This ``User`` model has some relationships, accessors and mutators, also remember that methods could increase a lot. At this time we can see too many things in our code and it is hard to read despite it's not a "fat" model. 

Moreover, in a collaborative project developers try to define how to organize those methods. In the previous code we saw that relationships were first, then accessors and then mutators. When new relationships or accessors come in, developers will try to keep that structure, but when the team is growing new developers will add new methods wherever they want, making this previous structure a mess.


### Traits to the rescue
So, what are traits? In simple words, they are a way to reuse code and reduce limitations of single inheritance. Although, a trait is something that can't be inheritable and is more like an extension of a class.

What if we separate each type of methods and put them in a trait? This way we can create a specific trait for each one. For example, we can create a ``HasUserAccessors`` for keeping all user's accessors and a ``HasUserMutators`` for keeping all user's mutators.

With this approach our ``User`` model will look like this:

```php
class User extends Authenticatable
{
    use HasFactory, Notifiable;
    use HasUserAccessors;
    use HasUserMutators;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'birth_date' => 'datetime',
    ];

    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    public function followers(): HasMany
    {
        return $this->hasMany(Follower::class);
    }

    public function favorites(): HasMany
    {
        return $this->hasMany(Favorite::class);
    }

    public function socialNetworks(): HasMany
    {
        return $this->hasMany(SocialNetwork::class);
    }

    public function addresses(): HasMany
    {
        return $this->hasMany(Address::class);
    }

    public function phones(): HasMany
    {
        return $this->hasMany(Phone::class);
    }

    public function messages(): HasMany 
    {
        return $this->hasMany(Message::class);
    }
    
}

```

Better?

In fact, we can go further by creating a ``HasUserRelationships``:

```php
class User extends Authenticatable
{
    use HasFactory, Notifiable;
    use HasUserAccessors;
    use HasUserMutators;
    use HasUserRelationships;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'birth_date' => 'datetime',
    ];
    
}

```

I know that some of you will say that is not a valid approach because we are not reusing a trait and it goes against the definition of trait, but remember that just because something can be reused doesn't mean that you have to. For example, classes can be extended but do you extend every class? Of course not, you create them for specific purposes with no need to apply inheritance.

Remember when I said that traits are not inheritable? It is because you cannot have a class that extends a trait, also you can't hide a trait's properties. If you have a private property inside a trait, when you use that trait in a class that class will own the property, it is like creating the property directly inside the class.

This is why I think using traits to reduce models is totally valid because is like creating methods inside the model but making the model looks fit.