Сделайте модели Laravel лучше с помощью трейтов

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

Автор блога не разделяет позицию автора статьи.

К счастью, есть глупо простое решение, которое я никогда раньше не видел ни в одном проекте Laravel. Вот почему я решил написать эту короткую статью.

Типичная жирная модель

Начнем с примера “жирной” модели:

<?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);
    }
}

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

Трейты спешат на помощь!

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

И это именно то, что мы ищем!

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

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

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

Я предпочитаю помещать отношения в отдельный Relations каталог внутри Models каталога (обратите внимание на пространство имен).

А вот и класс после нашего небольшого рефакторинга:

<?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);
    }
}

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

<?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 ;)

Выводы

Пожалуйста, не извлекайте каждое отдельное отношение в отдельный трейт. Если вам нужно расшифровать отношение, которое не следует соглашениям об именовании по умолчанию Laravel, было бы лучше не извлекать его, поскольку оно дает больше информации человеку, который читает код. Но если ваше отношение простое и следует условностям, то перенос его на трейт-хорошая идея. Его можно даже повторно использовать в ситуациях, когда, например, две модели имеют сходные belongsTo отношения.

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

Оригинал

Рейтинг
( 4 оценки, среднее 5 из 5 )
Maxyc Webber/ автор статьи
Мне 35 лет. Опыт профессиональной разработки 15 лет. Занимаюсь разработкой и поддержкой корпоративных систем автоматизации бизнеса, а также высоконагруженными проектами. Мне нравится решать нестандартные проблемы бизнеса. Имею опыт формирования команд под проект, налаживания процесса разработки, коммуникации программистов и заказчиков. Есть опыт работы с зарубежными заказчиками (ОАЭ, Польша, Германия, Швейцария).
Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.