Проект «Статистика дрифта». Часть 2. Базовые сущности

38a3649be5223ae45c13fea32cfb890e.jpg

Первая часть серии — Проект «Статистика дрифта». Часть 1. Настройка
Паблик во ВКонтакте с новыми сериями без задержек выпуска на habr — Пихта DEV

Сущность машины

Машина — «рабочая лошадка» пилота дрифта. Давайте уделим ей внимание и сформируем сущность. Что должно быть у машины как сущности? Раз она представляет нам интерес, то как минимум идентификатор. Интерес? Да, это то, что мы будем считать важным для нас. А что такое важно? Это то, что участвует в бизнес логике (например, поиске). А то, что важно, имеет идентификатор. Поэтому, наша машина будет иметь идентификатор, наименование (модель и марка) и параметры двигателя. Почему мы не разделяем наименование на марку и модель? Пока точно не знаю, кажется, что сейчас это не очень актуально. Возможно, в будущем будем разделять для статистики, но пока нет.

Теперь, давайте реализуем нашу сущность, но перед ее написанием нам надо реализовать ValueObject-ы для наших «запчастей» сущности «машина». Создаем директории:

- Domain
    - Car
        - Entity
        - ValueObject
            - Engine

Все наши ValueObject будут похожи друг на друга, так что не удивляйтесь. Для начала сделаем VO для идентификатора и наименования машины.

ValueObject для машины

Domain\Car\ValueObject\CardId.php:

value === 0;
    }

    public function equals(self $id): bool
    {
        return $this->value === $id->getValue();
    }

    public function getValue(): int
    {
        return $this->value;
    }

}

Что мы тут используем? По факту мы будем создавать все через статику. Зачем? Как по мне, так проще будет работать с экземплярами, которые создают не снаружи, а внутри класса. Тем самым, мы можем закрыть логику создания объектов. Как видно, get(int $value) создает экземпляр класса с переданным значением, а getNull() делает то же самое, что и get(int $value), но «зашивает» внутри себя логику создания объекта «без значения»/«пустого» ну и прочие синонимы. Ну и дополнительно делаем 2 проверки: isNull(), которая проверяет объект «без значения»/«пустого», и equals(self $id), которая принимаем такой же объект, чтобы сравнить их значения на совпадение.

Domain\Car\ValueObject\CardName.php:

value === '';
    }

    public function equals(self $name): bool
    {
        return $this->value === $name->getValue();
    }

    public function getValue(): string
    {
        return $this->value;
    }

}

Думаю, что класс имени машины не требует детального пояснения, так как он аналогичен классу идентификатора машины. Теперь, давайте создадим VO по двигателю. Почему VO, а не Entity? Как мне кажется, двигатель не представляет индивидуального интереса, как это делает машина. И так, что мы тут будем делать: наименование и мощность двигателя. Но, как мне кажется, их лучше будет объединить в VO «двигатель» для удобства использования.

Domain\Car\ValueObject\Engine\EngineName.php:

value === '';
    }

    public function equals(self $name): bool
    {
        return $this->value === $name->getValue();
    }

    public function getValue(): string
    {
        return $this->value;
    }

}

Что-то знакомое да? Ну конечно, так как такие вещи представляют по всему проекту ± одно и тоже.

Domain\Car\ValueObject\Engine\EnginePower.php:

value === 0;
    }

    public function equals(self $power): bool
    {
        return $this->value === $power->getValue();
    }

    public function getValue(): int
    {
        return $this->value;
    }

}

Тут вообще промолчу… А то каждый раз объяснять все — будет «крайне полезно». А теперь, объединим все в одну VO:

Domain\Car\ValueObject\Engine\Engine.php:

name->isNull() || $this->power->isNull();
    }

    public function getName(): EngineName
    {
        return $this->name;
    }

    public function getPower(): EnginePower
    {
        return $this->power;
    }

}

Вот, тут немного уточнений. Что мы считаем «отсутствием» двигателя? По-хорошему, когда его нет в машине, но в коде — это отсутствие наименования или мощности. Да, может быть это немного некорректно, но пока пойдет.

Теперь перейдем к созданию сущности машины.

Entity машины

Для начала нам надо определить интерфейс сущности. Для чего? А потому что мы будем иметь как реальную сущность машины, так и сущность «отсутствия» машины (аля nullable object).

Domain\Car\Entity\ICarEntity.php:

Пояснения нужны? Думаю, что нет, то на всякий случай один раз объясню. В интерфейсе есть получение VO: наименования, двигателя и идентификатора машины. Для чего isNull()? А вот это как раз для проверки: «является ли объект машины Nullable Object?», то есть ее «отсутствие».

Теперь давайте реализуем сущность «отсутствия» машины:

Domain\Car\Entity\NullCarEntity.php:

Просто же? Ну вот и я так думаю. А теперь давайте реализуем сущность машины.

Domain\Car\Entity\CarEntity.php:

name;
    }

    public function getEngine(): Engine
    {
        return $this->engine;
    }

    public function getId(): CarId
    {
        return $this->id;
    }

    public function isNull(): bool
    {
        return false;
    }

}

Как видите, здесь мы через конструктор класса передаем нужные VO для создания, а потом просто возвращаем их при получении.

С машиной мы закончили. Теперь надо машину кому-то дать в пользование. А кому? Гонщику, конечно. А начнем мы с небольшого введения.

Сущность гонщика

Тут немного разгуляемся. У гонщика будут: идентификатор, полное имя, номер (под которым выступает), дата рождения, страна, город и машина. Для чего так много? Мне кажется, что такой набор может как-то удачно лечь в более расширенную статистку как программную, так и интеллектуальную (мозгом). Например, мне было бы интересно сравнить двух пилотов, а еще узнать их возраст, чтобы понять, что молодые могут или нет… Да, это странно, но на самом деле даже разница в возрасте может стать интересным предметов аналитики.

Создаем директории:

- Domain
    - Racer
        - Entity
        - ValueObject

Начнем. Сразу скажу, я не буду останавливаться на неинтересных моментах. Так что, читайте внимательно и вникайте.

ValueObject гонщика

Domain\Racer\ValueObject\RacerId.php:

value === 0;
    }

    public function equals(self $id): bool
    {
        return $this->value === $id->getValue();
    }

    public function getValue(): int
    {
        return $this->value;
    }

}

Domain\Racer\ValueObject\RacerNumber.php:

value === 0;
    }

    public function equals(self $value): bool
    {
        return $this->value === $value->getValue();
    }

    public function getValue(): int
    {
        return $this->value;
    }

}

Domain\Racer\ValueObject\RacerCity.php:

value === '';
    }

    public function equals(self $value): bool
    {
        return $this->value === $value->getValue();
    }

    public function getValue(): string
    {
        return $this->value;
    }

}

Domain\Racer\ValueObject\RacerCountry.php:

value === '';
    }

    public function equals(self $value): bool
    {
        return $this->value === $value->getValue();
    }

    public function getValue(): string
    {
        return $this->value;
    }

}

А теперь к интересному. Начнем с полного имени.

Domain\Racer\ValueObject\RacerFullName.php:

first_name}{$this->last_name}{$this->patronymic}" === '';
    }

    public function getFirstName(): string
    {
        return $this->first_name;
    }

    public function getLastName(): string
    {
        return $this->last_name;
    }

    public function getValue(): string
    {
        return $this->last_name . ' ' . $this->first_name . ' ' . $this->patronymic;
    }

    public function getPatronymic(): string
    {
        return $this->patronymic;
    }

    public function equals(self $full_name): bool
    {
        return $this->getValue() === $full_name->getValue();
    }

}

Мы на вход получаем фамилию, имя и отчество. В получении значения getValue() мы конкатенируем это через пробел. В методе сравнения equals() мы просто сравниваем значения. Тут вот есть нюанс… По факту, будет сравнение трех пробелов с тремя пробелами, если сравнить два null объекта. В принципе, ничего страшного, но и не так хорошо.

А теперь переходим в дате рождения. Тут почти то же самое, но есть небольшие отличия — тип данных.

Domain\Racer\ValueObject\RacerDateOfBirth.php:

day + $this->month + $this->year === 0;
    }

    public function getDay(): int
    {
        return $this->day;
    }

    public function getMonth(): int
    {
        return $this->month;
    }

    public function getYear(): int
    {
        return $this->year;
    }

    public function equals(self $date_of_birth): bool
    {
        $current = "{$this->day}{$this->month}{$this->year}";
        $to_find = "{$date_of_birth->day}{$date_of_birth->month}{$date_of_birth->year}";

        return $current === $to_find;
    }

}

Вооот… Тут нет getValue(). Почему? А потому что нет смысла возвращать значение даты рождения без применения форматирования. А форматирование даты можно оставить тут, но лучше это использовать вне VO, как мне кажется. Хотя, с другой стороны, мы можем заложить стандартный формат даты, а потом когда-нибудь сделаем форматирование. А давайте!

public function getValue(): string
{
    return $this->year . '-' . $this->month . '-' . $this->day;
}

Как видели, мы в методе equals(self $date_of_birth) сделали странное сравнение. Взяли цифры, превратили в строку, а потом сравнили. Что??? Да, если суммировать их, то можно попасть на комбинацию данных, когда разные даты будут выдавать одно и то же число. Поэтому, было сделано так. Но, мы тут же решили добавить getValue()… Тогда, сравнение equals(self $date_of_birth) теперь можно написать так:

public function equals(self $date_of_birth): bool
{
    return $this->getValue() === $date_of_birth->getValue();
}

Куда проще и логичней. Все, наконец-то закончили с VO и можем переходить к сущностям.

Entity гонщика

Начнем как обычно с интерфейса, где опишем аналогичным образом методы.

Domain\Racer\Entity\IRacerEntity.php:

Ну тут все просто, но только отличие от сущности машины в том, что добавляется машина в сущность гонщика. Таким образом, мы связали гонщика и машину. Но, здесь может быть проблема, что у гонщика может быть несколько машин. Например, гонщик меняет машину каждый год. Получается, что когда мы будем как-то записывать статистику, то нам надо учитывать машину в конкретный момент. Когда это надо? Когда мы будем смотреть детальную статистку, то машина нужна, но в общей статистике нет. Поэтому, есть ощущение, что текущая машина гонщика — просто текущая машина. Короче, давайте пока оставим так, а когда дойдем до статистики, возможно, переделаем эту историю с гонщиком. Пока я точно не знаю и уже сам немного запутался. По-хорошему, забегая вперед, нужна какая-то таблица в базе данных (БД), где будет связь гонщика и машины. А ссылка на эту конкретную связь надо указывать в статистике. Тогда мы получим уже более логичный вариант. Да, звучит логично, но на сколько нужен весь список машин гонщика — не знаю. Возможно, чтобы просто вывести эту информацию на личной странице гонщика. Но, в таком случае, нужна еще и дата смены машины, ее «активность» и так далее. Давайте, наверное, пока опустим это свойство и вернемся к нему, когда будем прорабатывать статистику. А там, поверьте, будет оооочень весело.

Domain\Racer\Entity\IRacerEntity.php:

Давайте потом решим, а теперь напишем сущность RacerEntity и NullRacerEntity.

Domain\Racer\Entity\NullRacerEntity.php:

Domain\Racer\Entity\RacerEntity.php:

id;
    }

    public function getFullName(): RacerFullName
    {
        return $this->full_name;
    }

    public function getNumber(): RacerNumber
    {
        return $this->number;
    }

    public function getDateOfBirth(): RacerDateOfBirth
    {
        return $this->date_of_birth;
    }

    public function getCounty(): RacerCountry
    {
        return $this->county;
    }

    public function getCity(): RacerCity
    {
        return $this->city;
    }

}

Вот. На данном моменте мы закончим формирование базовых сущностей для последующей статистики. По факту, получилось на верхнем уровне описать базовые сущности, которые дальше будут фигурировать везде. Остаются только вопросы технической реализации хранения данных для статистики, о которой мы поговорим в другой раз.

Оригинальная статья и новые серии в моем паблике во ВКонтакте Пихта DEV

© Habrahabr.ru