Использование $fillable для валидации
$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());
}
}