PHP Traits в моделях Laravel

Каждый веб-разработчик, использующий MVC фреймворк, когда-либо встречался с проблемой “толстых” моделей. Это касается не только тех, кто переносит в модели правила валидации и пишет там бизнес-логику, но и вполне приличных программистов, придерживающихся принципов SOLID.

Но этой проблеме есть весьма простое решение, которое почему-то я никогда и ни в одном проекте не встречал.

Код

Давайте взглянем на пример модели, которой не помешало бы скинуть парочку килограммов:

<?php namespace MyApp\Models;

class User extends BaseModel
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function orders()
    {
        return $this->hasMany('Order');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function projects()
    {
        return $this->belongsToMany('Project')
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function articles()
    {
        return $this->hasMany('Article');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function comments()
    {
        return $this->hasMany('Comment');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function role()
    {
        return $this->belongsTo('Role');
    }

    /**
     * @param \Illuminate\Database\Eloquent\Builder $builder
     * @param int $roleId
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeRoleId($builder, $roleId = 0)
    {
        if (!$roleId)
        {
            return $builder;
        }

        return $builder->where('role_id', '=', $roleId);
    }
}

Обилие связей и скоупов делают код этого класса достаточно громоздким, поэтому находить нужное в нем становится сложнее, чем хотелось бы. К счастью, есть очень простое решение данной проблемы.

PHP Traits спешат на помощь!

Да простят меня читатели за капитанство, но жанр требует, чтобы я дал им определение.

Трейт – это набор методов, которые можно “примешивать” в разные классы. Таким образом в PHP решается вопрос множественного наследования.

Как вы уже наверняка догадались, они подходяд для решения нашей проблемы как нельзя лучше. Давайте посмотрим, как будет выглядеть связь “orders”, если ее вынести в трейт:

<?php namespace MyApp\Models\Relations\HasMany;

trait Orders
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function orders()
    {
        return $this->hasMany('Order');
    }
}

Остается теперь только в самом классе модели заменить этот метод на трейт:

<?php namespace MyApp\Models;

class User extends BaseModel
{
    use Relations\HasMany\Orders;

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function projects()
    {
        return $this->belongsToMany('Project')
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function articles()
    {
        return $this->hasMany('Article');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function comments()
    {
        return $this->hasMany('Comment');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function role()
    {
        return $this->belongsTo('Role');
    }

    /**
     * @param \Illuminate\Database\Eloquent\Builder $builder
     * @param int $roleId
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeRoleId($builder, $roleId = 0)
    {
        if (!$roleId)
        {
            return $builder;
        }

        return $builder->where('role_id', '=', $roleId);
    }
}

Учитывая то, что одной строкой мы заменили метод в три строки и убрали один PHPDoc блок, стало гораздо аккуратнее. Когда мы вынесем все методы в трейты, результирующий класс станет таким:

<?php namespace MyApp\Models;

class User extends BaseModel
{
    // Has Many Relations
    use Relations\HasMany\Orders;
    use Relations\HasMany\Articles;
    use Relations\HasMany\Comments;

    // Belongs To Relations
    use Relations\BelongsTo\Role;

    // Belongs To Many Relations
    use Relations\BelongsToMany\Projects;

    // Scopes
    use Scopes\RoleId;
}

Гораздо лучше, не правда ли? Теперь наша модель имеет неплохие шансы попасть в Victoria’s Secret ;)

Само собой, это подходит не только для моделей, но и для любых других классов вашего проекта (сейчас речь про трейты, а не про Victoria’s Secret).

Выводы

Данный подход хорош еще и тем, что большинство связей и скоупов чаще всего используются в нескольких моделях, поэтому такие трейты не только упрощают и уменьшают код в моделях, но и соответствуют принципу DRY.

Несмотря на то, что трейты добавлены в PHP целых три года назад в версии 5.4, они все еще редко встречаются в реальных проектах.

Думаю, данная статья показывает неплохой пример практической пользы от их использования в схожих сценариях.

← Back