Кастинг користувацьких об'єктів у моделях Laravel

Перекладено ШІ
Оригінал: Laravel News
Оновлено: 11 липня, 2025
Ви коли-небудь замислювались, як можна перетворити прості атрибути бази даних у складні об'єкти в Laravel? У нашій статті ми розглянемо, як кастомні об'єкти дозволяють вдосконалити обробку даних і спростити роботу з моделями, забезпечуючи зручний інтерфейс для складних операцій. Читайте далі, щоб дізнатися більше
```html

Спеціалізоване кастування об'єктів у Laravel дозволяє перетворювати атрибути бази даних у багаті об'єкти значень, що спрощує обробку даних та зберігає чистоту інтерфейсів моделей. Ця функція заповнює прогалину між плоским зберіганням даних у базі та складною логікою додатка.

Щоб створити кастування, реалізуйте інтерфейс CastsAttributes:

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Money implements CastsAttributes
{
    public function get($model, $key, $value, $attributes)
    {
        return new MoneyValueObject($value, $attributes['currency'] ?? 'USD');
    }

    public function set($model, $key, $value, $attributes)
    {
        return [
            'amount' => $value->amount,
            'currency' => $value->currency,
        ];
    }
}

Об'єкти значень інкапсулюють пов'язані дані та поведінку, забезпечуючи зрозумілий інтерфейс для складних операцій.

class MoneyValueObject implements JsonSerializable
{
    public function __construct(
        public float $amount,
        public string $currency = 'USD'
    ) {}

    public function formatted(): string
    {
        return match ($this->currency) {
            'USD' => '$' . number_format($this->amount, 2),
            'EUR' => '€' . number_format($this->amount, 2),
            'GBP' => '£' . number_format($this->amount, 2),
            default => $this->currency . ' ' . number_format($this->amount, 2),
        };
    }

    public function add(MoneyValueObject $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new InvalidArgumentException('Неможливо додати різні валюти');
        }

        return new self($this->amount + $other->amount, $this->currency);
    }

    public function jsonSerialize(): array
    {
        return [
            'amount' => $this->amount,
            'currency' => $this->currency,
            'formatted' => $this->formatted(),
        ];
    }
}

Ось приклад комплексної системи рахунків-фактур, що демонструє просунуте кастування:

class Invoice extends Model
{
    protected $casts = [
        'billing_address' => ContactInfo::class,
        'shipping_address' => ContactInfo::class,
        'total_amount' => Money::class,
        'tax_amount' => Money::class,
        'discount' => Money::class,
    ];

    public function calculateTotal(): MoneyValueObject
    {
        $subtotal = $this->lineItems->sum(fn($item) => $item->total_price->amount);
        $taxTotal = $this->tax_amount->amount;
        $discountTotal = $this->discount->amount;

        return new MoneyValueObject($subtotal + $taxTotal - $discountTotal, $this->total_amount->currency);
    }

    public function lineItems()
    {
        return $this->hasMany(InvoiceLineItem::class);
    }
}

class ContactInfo implements CastsAttributes
{
    public function get($model, $key, $value, $attributes)
    {
        $prefix = $key === 'billing_address' ? 'billing_' : 'shipping_';

        return new ContactInfoValueObject(
            $attributes["{$prefix}name"] ?? '',
            $attributes["{$prefix}company"] ?? '',
            $attributes["{$prefix}address_line_1"] ?? '',
            $attributes["{$prefix}address_line_2"] ?? '',
            $attributes["{$prefix}city"] ?? '',
            $attributes["{$prefix}state"] ?? '',
            $attributes["{$prefix}postal_code"] ?? '',
            $attributes["{$prefix}country"] ?? ''
        );
    }

    public function set($model, $key, $value, $attributes)
    {
        $prefix = $key === 'billing_address' ? 'billing_' : 'shipping_';

        return [
            "{$prefix}name" => $value->name,
            "{$prefix}company" => $value->company,
            "{$prefix}address_line_1" => $value->addressLine1,
            "{$prefix}address_line_2" => $value->addressLine2,
            "{$prefix}city" => $value->city,
            "{$prefix}state" => $value->state,
            "{$prefix}postal_code" => $value->postalCode,
            "{$prefix}country" => $value->country,
        ];
    }
}

class ContactInfoValueObject implements JsonSerializable
{
    public function __construct(
        public string $name,
        public string $company,
        public string $addressLine1,
        public string $addressLine2,
        public string $city,
        public string $state,
        public string $postalCode,
        public string $country
    ) {}

    public function fullAddress(): string
    {
        $parts = array_filter([
            $this->addressLine1,
            $this->addressLine2,
            $this->city,
            $this->state . ' ' . $this->postalCode,
            $this->country,
        ]);

        return implode(', ', $parts);
    }

    public function displayName(): string
    {
        return $this->company ? "{$this->name} ({$this->company})" : $this->name;
    }

    public function jsonSerialize(): array
    {
        return get_object_vars($this);
    }
}

class InvoiceController extends Controller
{
    public function store(Request $request)
    {
        $invoice = new Invoice();

        $invoice->billing_address = new ContactInfoValueObject(
            $request->input('billing.name'),
            $request->input('billing.company', ''),
            $request->input('billing.address_line_1'),
            $request->input('billing.address_line_2', ''),
            $request->input('billing.city'),
            $request->input('billing.state'),
            $request->input('billing.postal_code'),
            $request->input('billing.country')
        );

        $invoice->total_amount = new MoneyValueObject(
            $request->input('total'),
            $request->input('currency', 'USD')
        );

        $invoice->save();

        return response()->json([
            'invoice_id' => $invoice->id,
            'formatted_total' => $invoice->total_amount->formatted(),
            'billing_contact' => $invoice->billing_address->displayName(),
        ]);
    }

    public function show(Invoice $invoice)
    {
        return view('invoices.show', [
            'invoice' => $invoice,
            'calculatedTotal' => $invoice->calculateTotal()->formatted(),
            'billingDisplay' => $invoice->billing_address->fullAddress(),
        ]);
    }
}

Кастування об'єктів дозволяє створювати багаті доменні моделі, спрощуючи при цьому взаємодію з базою даних у ваших Laravel-додатках

```