Ответ
Перехват и обработка данных перед сохранением — это стандартная задача. Выбор способа зависит от фреймворка и архитектуры. Вот основные подходы, которые я использовал:
1. Мутаторы (Mutators) в ORM (Eloquent/Doctrine): Идеально для простых преобразований полей модели (например, форматирование, хеширование).
-
В Laravel Eloquent:
// app/Models/User.php class User extends Model { // Мутатор автоматически вызовется при установке $user->email public function setEmailAttribute(string $value): void { $this->attributes['email'] = strtolower(trim($value)); } // Аксессор для получения данных public function getFullNameAttribute(): string { return $this->first_name . ' ' . $this->last_name; } }
2. События моделей (Model Events / Lifecycle Callbacks): Подходят для более сложной логики, которая может зависеть от нескольких полей или внешних сервисов.
-
В Laravel (наблюдатели или в модели):
// В модели можно использовать методы типа saving(), creating() class Post extends Model { protected static function booted(): void { static::saving(function (Post $post) { // Генерация slug из заголовка перед сохранением $post->slug = Str::slug($post->title); // Подсчёт примерного времени чтения $post->read_time = ceil(str_word_count($post->body) / 200); }); } } -
В Doctrine (используя аннотации/атрибуты):
// src/Entity/Product.php #[ORMEntity] #[ORMHasLifecycleCallbacks] class Product { // ... #[ORMPrePersist] #[ORMPreUpdate] public function updateTimestampsAndSlug(): void { $this->updatedAt = new DateTimeImmutable(); if ($this->slug === null) { $this->slug = $this->generateSlug($this->name); } } }
3. Сервисный слой (Service Layer) или Действия (Actions): Наиболее контролируемый и явный способ. Вся бизнес-логика по обработке данных инкапсулирована в отдельном классе.
// src/Service/ProductService.php
class ProductService
{
public function __construct(private ProductRepository $repository) {}
public function createProduct(CreateProductDto $dto): Product
{
// 1. Валидация DTO уже пройдена на уровне контроллера
// 2. Преобразование DTO в сущность с дополнительной логикой
$product = new Product();
$product->setName($dto->name);
$product->setPrice($dto->price);
// 3. Перехват и обработка данных ПЕРЕД сохранением
$this->applyBusinessRules($product, $dto);
// 4. Вызов репозитория для сохранения
$this->repository->save($product);
return $product;
}
private function applyBusinessRules(Product $product, CreateProductDto $dto): void
{
// Например, установка цены со скидкой для оптовиков
if ($dto->customerType === 'wholesale') {
$product->setFinalPrice($product->getPrice() * 0.9);
}
// Или логирование изменения цены
if ($product->getPrice() !== $dto->price) {
$this->logger->info('Price adjusted', ['product_id' => $product->getId()]);
}
}
}
Рекомендация по выбору:
- Мутаторы — для простого форматирования данных одного поля.
- События модели — для логики, тесно связанной с жизненным циклом сущности, но которая не должна «засорять» сервис.
- Сервисный слой — для сложной бизнес-логики, требующей доступа к другим сервисам (логирование, внешние API), или когда нужно явно видеть все преобразования в одном месте.