Comments 8
А директория ValueObject ну прям нужна, да? Выглядит как такая же бессмысленная, как классические Service, Manager, Helper, Support, etc (или суффиксы Entity, выглядит как "масло масляное").
P.S. Если уж хочется венгерскую нотацию использовать, то в пыхе приняты суффиксы: IRacerEntity
-> RacerInterface
.
P.P.S. А ещё вместо PSR-2/12 лучше PER-2 использовать.
P.P.P.S А ещё не хватает хотя бы кратенькой ремарки в чём прикол анемичных энтитей. Я не говорю что это плохо, сам считаю что анемичные легче и практичнее, но всё же многие считают это плохой практикой и даже антипаттерном.
P.P.P.P.S. А ещё нулевой даты не существует: RacerDateOfBirth::getNull()
возвращает явно некорректное и недопустимое состояние.
P.P.P.P.P.S. А ещё можно порезать нормально интерфейсы, согласно ISP, например:
// До
interface IRacerEntity
{
public function getId(): RacerId;
public function getFullName(): RacerFullName;
public function getNumber(): RacerNumber;
public function getDateOfBirth(): RacerDateOfBirth;
public function getCounty(): RacerCountry;
public function getCity(): RacerCity;
public function getCar(): ICarEntity;
}
// После (ну например)
// Domain\Shared\
interface ValueObjectInterface
{
public function equals(self $value): bool;
}
// Domain\Shared\
interface IdInterface extends ValueObjectInterface {}
// Domain\Shared\
interface IdentifiableInterface
{
public function getId(): IdInterface;
}
// Domain\Racer\
interface NameableInterface
{
public function getName(): \Stringable|string;
}
// Domain\Racer\ (?)
interface ContainLocationInterface
{
public function getCity(): CityInterface; // Город уже содержит внутри страну
}
// Domain\Car\
interface InteractWithCarInterface
{
public function getCar(): CarInterface;
}
// Domain\Racer\
interface RacerInterface extends
IdentifiableInterface,
NameableInterface,
ContainLocationInterface,
InteractWithCarInterface
{
#[\Override]
public function getId(): RacerId;
public function getNumber(): RacerNumber;
public function getDateOfBirth(): RacerDateOfBirth;
}
А так -- как всегда огонь. Только воды (копипасты) много. Можно было описать пару энетитей, пару VO, а остальное схематично, без приведения кода.
В будущей статье, которая есть в ВК, были проведены работы по рефакторингу. По стандарту PSR можно использовать Interface в конце. А еще выделение Shared моментов в VO.
А ещё вместо PSR-2/12 лучше PER-2 использовать.
Чем?
А ещё не хватает хотя бы кратенькой ремарки в чём прикол анемичных энтитей. Я не говорю что это плохо, сам считаю что анемичные легче и практичнее, но всё же многие считают это плохой практикой и даже антипаттерном.
В проекте на данный момент не предусматривается изменение, лишь вывод. Исходя из этого, нет необходимости в создании методов изменения сущности
А ещё нулевой даты не существует:
RacerDateOfBirth::getNull()
возвращает явно некорректное и недопустимое состояние.
Этим никто пользоваться не будет, поскольку у нас есть просто факт VO без значений. И эти нули нужны просто для заполнения VO. Процесс взаимодействия будет строиться через isNull, где что-то будет не отображаться и так далее.
Чем?
Чтоб не писать вот такое:
public function __construct(
public string $ololo
) // << вот
{ // << это
} // << вот
// А было адекватно
public function __construct(
public string $ololo,
) {}
В проекте на данный момент не предусматривается изменение, лишь вывод. Исходя из этого, нет необходимости в создании методов изменения сущности
Я ничего не говорил про мутабельность, я говорил про бледную модель ;)
Этим никто пользоваться не будет, поскольку у нас есть просто факт VO без значений.
В таком случае почему вместо VO не сделать null? Это же не джава, где null является любым объектом и типизация считай что не работает. В пыхе более строгая и накосячить не получится. Так что выглядит как public ?RacerDateOfBirth $birthDate = null
, не?
Ну и проверка или вывод идентичный: $racer?->birth->getDay()
. ...хотя я бы вообще сделал $racer?->birth->day
, т.к. свойства (в первую очередь) и поля (тут уже спорно) должны отвечать за хранение/отдачу данных, а не методы. Это, типа, их семантика) Методы что-то делают, поля хранят, а свойства предоставляют доступ к полям. В случае же VO у нас ридонли поля, которые смело сделать публичными ридонли.
Про анемичные модели. Тут пока до конца не ясно, надо ли в доменную сущность пихать бизнес логику или нет. Лично я больше сторонник того, чтобы вынесли работу с сущностями в сервисы.
По поводу VO я отвечал в комментариях паблике ВК. Nullable Object позволяет "стабильнее" и однозначней использовать объекты. Дополнительный null тип будет вводить уже меньшую степень "объектности" и двойственности. Поэтому, при подходе максимального использования объектов NULL не вписывается адекватно. Помимо этого в isNull я могу зашить логику "а что такое отсутствие" тем самым "спрятав" это в объект.
Про свойства. Я считаю, что все свойства должны быть приватными или защищёнными. Публичные свойства доступны только для редактирования из вне. Геттеры и сеттеры - та же логика, что я описал выше про isNull.
Про анемичные модели. Тут пока до конца не ясно, надо ли в доменную сущность пихать бизнес логику или нет. Лично я больше сторонник того, чтобы вынесли работу с сущностями в сервисы.
Ну вот лично я полностью поддерживаю подобное, иначе потом сущности превращаются в монстров и божественные объекты. Мой тезис касался исключительно того, что неплохо было бы это в статье как-то объяснить, а то есть много статей, где это считается антипаттерном, например: https://habr.com/ru/articles/224879/
По поводу VO я отвечал в комментариях паблике ВК. Nullable Object позволяет "стабильнее" и однозначней использовать объекты.
Ну тут не паблик ВК, а хабр. Тут подобного нет)
Помимо этого в isNull я могу зашить логику "а что такое отсутствие" тем самым "спрятав" это в объект.
А что мешает тоже самое сделать во время вызова?
final readonly class ExampleName
{
// ... всякий код
public function isEmpty(): bool
{
return $this->name === '';
}
}
// Использование
public ?ExampleName $name {
get => $this->name?->isEmpty() ? null : $this->name;
}
// Ну или через метод
public function getName(): ?ExampleName
{
return $this->name?->isEmpty() ? null : $this->name;
}
А по поводу тезиса "не объектный", в принципе согласен, но по-моему удобство в использовании и поддержке кода в данном случае на порядки важнее, чем довольно спорный околофилософский тезис.
Про свойства. Я считаю, что все свойства должны быть приватными или защищёнными. Публичные свойства доступны только для редактирования из вне. Геттеры и сеттеры - та же логика, что я описал выше про isNull.
Так геттеры и сеттеры и реализуют свойства. По факту это костыль для языков, где нет свойств. Имхо, при наличии на уровне языка свойств ни одного аргумента в пользу их эмуляции через геттеры/сеттеры нет. А вся эта чушь с гет/сет была притянута в древнее время из джавы, где всё чем они занимались - контролировали non-null, коих проблем в PHP нет.
Ну и понятно, что все эти аргументы на тему (из популярного):
"ну это же инкапсуляция" -- а где инкапсуляция-то (?), добавить приват - это не инкапсуляция.
"неизвестен тип" -- не актуально, начиная с 7.4, ранее через
@var
делалалось."любой может записать" -- не актуально, начиная с 8.0, ранее через
@readonly
или@psalm-readonly-allow-private-mutation
делалось."невозможно определить в интерфейсе" -- не актуально, начиная с 8.4, ранее через
@property-read
костыль делалось."невозможно добавить логику на запись/чтение" -- не актуально, начиная с 8.4, ранее через мега-костыль
__get/__set
делалось.
Из реально более-менее аргументированного последние 2 пункта, да и то в современном PHP уже не актуально (ну кроме тех мест, где ещё php 5.6).
Отсюда и мои выводы выше, что поля класса можно смело делать публичными только на чтение (readonly), а свойства уже любые. Отсюда и тезисы о getXxx/setXxx методах, что они фактически легаси, которые тащат зачем-то по-привычке, не имея ни одного аргумента в пользу их применения.
А по поводу тезиса "не объектный", в принципе согласен, но по-моему удобство в использовании и поддержке кода в данном случае на порядки важнее, чем довольно спорный околофилософский тезис.
В любом случае, имхо, возвращать заведомо некорректные данные ну или иметь VO в невалидном состоянии хуже, нежели просто там возвращать null
(при наличии такой возможности), т.к. не все и не всегда будут проверять при каждом вызове if ($entity->getVo()->isNull()) { ... }
, а значит это повышает шанс написать забагованный/некорректный код.
Я правильно понимаю, "сущности" будут иммутабельными?
Проект «Статистика дрифта». Часть 2. Базовые сущности