Disclaimer: вставлю свои 5 копеек, не претендую на истинность, но мысли озвучу (в самых общих чертах).
- Form получение данных извне (request: POST/GET/ARGS/Array), обработка (валидация, фильтрация), передача в сервисный слой (бизнес логика).
- Model какое-то объектное представления данных в системе (инфрастркутура, repository).
- Service различные сервисы (UoW, UseCase), обработка бизнес логики.
- DTO транспорт, плюс схема данных (комментарии могут рассказывать о том, как и где используются данные). Есть возможность быстро организовать версионность массива данных.
В самом простом исполнении получается следующая схема:
interface Form extends Dtoable
{
/**
* Конвертирует объект в массив.
*
* @param string[] $fields поля которые должны быть в исходном массиве
* @return array<string, mixed>
*/
public function toArray(array $fields = []): array;
/**
* Форма содержит данные, их нужно передать в сервис (например, Service).
*
* @param class-string $dtoClassName
*/
public function toDto(string $dtoClassName): DtoInterface;
}
interface Model
{
public function fromDto(DtoInterface $dto): self;
}
interface Service
{
public function save(DtoInterface $dto): bool;
}
Подходы могут быть разные, но лично мне не нравится передавать данные через конструктор, возможно с приходом php8 и named arguments я поменяю точку зрения.
/**
* @psalm-immutable
*/
final class ModelDto extends BaseDto
{
public int $id;
public string $name;
/**
* nullable в данном случае говорит что значение при Инициализации объекта может быть незадано.
*/
public ?array $props = [];
}
В DTO можно использовать protected
свойства (плюс геттеры), чтобы не было желания заполнять их как-то кроме как
через hydrate()
. Главное - иммутабельность и никакой логики, как только здесь появляется логика, это сразу
превращается в Entity, или быть может какой-то иной подвид Value Object.
Может возникнуть вопрос, зачем здесь DTO, ведь в сервисный слой можно передавать форму напрямую ($form или в виде массива $form->toArray()), и так же получать из сервисного слоя напрямую модель. Суть в том, что Модель, как и Форма, это реализация некоторой логики, и в приложении могут быть несколько компонент, которые реализуют логику субъективно, по своему, с учётом требований БЛ, но сервисный слой для всех компонент один и тот же. Поэтому нужен механизм, который позволит 3-м разным формам работать с одним методом, как например $service->save(DTO). А так же потому что форма, как любой иной объект может менять своё состояние, и нет чётких гарантий неизменности данных.
class Service
{
public function save(DtoInterface $dto): bool
{
$data = $dto->toArray();
...
return true;
}
}
docker pull ghcr.io/kuaukutsu/php:8.1-cli
Container:
ghcr.io/kuaukutsu/php:${PHP_VERSION}-cli
(default)jakzal/phpqa:php${PHP_VERSION}
shell
docker run --init -it --rm -v "$(pwd):/app" -w /app ghcr.io/kuaukutsu/php:8.1-cli sh
The package is tested with PHPUnit. To run tests:
make phpunit
The code is statically analyzed with Psalm. To run static analysis:
make psalm
make phpcs
make rector