Использование $fillable для валидации

Источник: «Using $fillable for validation»
Свойство $fillable в моделях Eloquent может оказаться разумным местом для размещения логики валидации. В конце концов, учитывая, что этот список является практически частью валидации, было бы расточительно не использовать ту же структуру для определения правил соответственно.

Когда вы думаете о свойстве $fillable, то можете прийти к выводу, что оно действительно связано с валидацией: мы сообщаем Laravel, какие атрибуты разрешено заполнять, и это, по крайней мере, на мой взгляд, соотносится с нашими правилами валидации, которые определяют, как эти атрибуты разрешено заполнять.

Учитывая это, почему бы не объединить их, чтобы свойство $fillable включало правила валидации в качестве своих значений, а атрибуты использовались как ключи:

use App\Collections\CustomCollection;
use Illuminate\Database\Eloquent\Model;

class Post extends Model {

/**
* The attributes that are mass assignable.
*
* @var array
*/

protected $fillable = [
'title' => 'required|string|min:3|max:20',
'content' => 'nullable|string|max:2000',
'published' => 'required|boolean',
];
}

Прежде чем это заработает, нам нужно добавить следующее в базовый класс Model:

use Illuminate\Database\Eloquent\Model as BaseModel;

class Model extends BaseModel
{
/**
* Get the fillable attributes for the model.
*
* @return array<string>
*/

public function getFillable()
{
return array_is_list($this->fillable)
? $this->fillable
: array_keys($this->fillable);
}

/**
* Validate the data using model's fillable rules.
*
* @source https://marknuyens.nl/tips/using-fillable-for-validation
*
* @param array $data The input data.
* @param array $rules Any custom rules.
* @param array $messages Any custom messages.
* @param array $attributes Any custom attributes.
* @return self
* @throws \Illuminate\Validation\ValidationException
*/

public function validate(
array $data = null,
array $rules = [],
array $messages = [],
array $attributes = [],
) {
$data ??= $this->getAttributes();

if(! array_is_list($this->fillable)) {
$rules = array_merge($this->fillable, $rules);
Validator::make($data, $rules, $messages, $attributes)->validate();
}

return $this->fill($data);
}
}

Теперь мы можем использовать этот новый трюк где угодно, например, внутри нашего контроллера:

class PostController
{
/**
* Store a newly created resource in storage.
*
* @param Illuminate\Http\Request $request
* @param \App\Models\Post $post
* @return int
*/

public function store(Request $request, Post $post)
{
return $post->validate($request->input())->create();
}
}

Хотя это может быть кандидатом на небольшой пакет PHP, просто показать код, вероятно, будет более эффективно.

Примечания

Существуют и другие методы улучшения процесса проверки, один из которых — пакет Data от Spatie, включающий в себя проверку (помимо всего прочего). Однако на мой взгляд, этот пакет содержит слишком много опций.

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

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

Наконец, вероятно, существуют ещё более чистые способы валидации входных данных, связанных с моделью, возможно, с помощью middleware, макросов или за кулисами при создании или обновлении модели. Однако это может просто запутать поведение.

Заключение

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

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

🎁 Бонус

Если вы хотите сделать ещё один шаг вперёд, вы можете применить приведённый ниже трейт к вашей базовой модели. Это позволит проверять входные значения модели перед их сохранением в базе данных, избавляя вас от необходимости вызывать validate() вручную.

Однако я могу предположить, что кто-то может возразить против этого, поскольку это может стать более сложным для чтения. Я бы счёл этот метод наиболее эффективным, но, возможно, не самым читабельным. Тем не менее он сохраняет чистоту.

trait FillableValidation {
/**
* Boot the trait.
*
* @return void
*/

protected static function bootFillableValidation()
{
static::saving(fn ($model) => $model->validate());
}
}

Дополнительные материалы

Предыдущая Статья

Все рекурсивные функции в PHP

Следующая Статья

Функции высшего порядка в JavaScript