@vism

Правильно так использовать ValueObject в Cast атрибутах с точки зрения архитектуры?

Дисклэймер: это не тру DDD и всё такое, и без тэстов. Речь о реальной бизнес задаче, где нужно соблюсти баланс и бюджет не резиновый.

По сути тестовый VO, т.к. раньше я ими не пользовался. Хочу попробовать на основе Адреса.
В модели есть поля для отображения адреса, системный адрес(который может даже отличаться от пользовательского) и координаты.

Второй момент, что VO, CastsAttributes должен быть по сути универсальный, а поля моделей могут отличаться.
Получилось что-то типо такого (только наверное еще координаты туда надо запихнуть в виде объекта)

Третий момент.
По сути VO это практически DTO с логикой? Как минимум нормально имплиментиться от Spatie\DataTransferObject и использовать его функционал?

namespace App\Casts;

use App\Exceptions\BusinessLogicException;
use App\Models\Property;
use App\ValueObjects\Address;
use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class AddressCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes)
    {
        if ($model instanceof Property) {
            return new Address([
                'street_number'             => $model->address_street_number,
                'street'                    => $model->address_street,
                'city'                      => $model->address_city,
                'neighborhood'              => $model->address_neighborhood,
                'province'                  => $model->address_province,
                'google_address_components' => $model->address_raw_google_details,
                'location'                  => $model->location,
            ]);
        }

        throw new BusinessLogicException(self::class . ' model not specified');
    }

    public function set($model, $key, $value, $attributes)
    {
        if ($model instanceof Property) {
            return [
                'address_street_number'      => $value->street_number,
                'address_street'             => $value->street,
                'address_city'               => $value->city,
                'address_neighborhood'       => $value->neighborhood,
                'address_province'           => $value->province,
                'address_raw_google_details' => $value->google_address_components,
                'location'                   => \Arr::get($attributes, 'location') instanceof SpatialExpression ? new SpatialExpression($value->location) : $value->location,
            ];
        }

        throw new BusinessLogicException(self::class . ' model not specified');
    }
}


namespace App\ValueObjects;

use App\Http\Requests\Site\PropertyCreateRequest;
use Grimzy\LaravelMysqlSpatial\Types\Point;
use Spatie\DataTransferObject\DataTransferObject;

class Address extends DataTransferObject
{
    public string $street_number = '';
    public string $street = '';
    public string $city = '';
    public string $neighborhood = '';
    public string $province = '';
    public ?\Illuminate\Support\Collection $google_address_components = null;
    public \Grimzy\LaravelMysqlSpatial\Types\Point $location;

    public static function fromRequest(PropertyCreateRequest $request): self
    {
        $self = new self([
            'google_address_components' => collect(json_decode($request->input('raw_google_address_components'), true)),
            'location' => new Point(
                $request->input('latitude'),
                $request->input('longitude')
            ),
        ]);
        $self->updateFromGoogleAddressComponent();

        return $self;
    }

    public function updateFromGoogleAddressComponent()
    {
        if (!empty($this->google_address_components)) {
            $componentByType = $this->google_address_components->keyBy(function ($item) {
                return \Arr::get($item, 'types.0', '');
            });

            $this->street_number = \Arr::get($componentByType, 'street_number.long_name', '');
            $this->street = \Arr::get($componentByType, 'route.short_name', '');
            $this->city = \Arr::get($componentByType, 'locality.long_name', '');
            $this->neighborhood = \Arr::get($componentByType, 'neighborhood.short_name', '');
            $this->province = \Arr::get($componentByType, 'administrative_area_level_1.short_name', '');
        }
    }

    public function fullAddress()
    {
        $fullAddress = '';
        if ($this->street) {
            $fullAddress = $this->street;
            if ($this->street_number) {
                $fullAddress = $this->street_number . ' ' . $fullAddress;
            }
        }

        if ($this->city) {
            $fullAddress .= ', ' . $this->city;
        }

        if ($this->neighborhood) {
            $fullAddress .= ', ' . $this->neighborhood;
        }

        if ($this->province) {
            $fullAddress .= ', ' . $this->province;
        }

        return $fullAddress;
    }

    public function __toString()
    {
        return $this->fullAddress();
    }
}


Update
Вобщем более менее адекватный VO получается и ни разу не DTO. Вроде всё норм.
  • Вопрос задан
  • 128 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы