Каждый веб-разработчик, использующий 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, они все еще редко встречаются в реальных проектах.
Думаю, данная статья показывает неплохой пример практической пользы от их использования в схожих сценариях.