• Время чтения ~2 мин
  • 10.07.2022

Атрибуты Eloquent Castable — одна из наиболее мощных функций Laravel; некоторые люди используют их религиозно, в то время как другие склонны избегать их. В этом уроке я рассмотрю несколько различных примеров их использования и, самое главное, расскажу, почему вам следует их использовать.

Вы можете создать новый проект Laravel или использовать существующий для этого руководства.Не стесняйтесь следовать вместе со мной, или если вы хотите прочитать и запомнить - это тоже хорошо. Важный момент, который следует вынести из этого, — это то, насколько хорошими могут быть красноречивые приведения.

Начнем с нашего первого примера, похожего на тот, что в документации Laravel. Документация Laravel показывает получение адреса пользователя, но также с использованием модели.Теперь представьте, если бы это был столбец JSON для пользователя или любой модели для этого факта. Мы можем получить и установить некоторые данные и добавить дополнительные, чтобы все работало так, как мы хотим. Нам нужно установить пакет Джесса Арчера под названием Приводимый объект передачи данных Laravel.Вы можете установить это с помощью следующей команды композитора:

composer require jessarcher/laravel-castable-data-transfer-object

Теперь, когда мы это установили, давайте создадим класс Castable:

namespace App\Casts;
 
use JessArcher\CastableDataTransferObject\CastableDataTransferObject;
 
class Address implements CastableDataTransferObject
{
	public string $nameOrNumber,
	public string $streetName,
	public string $localityName,
	public string $town,
	public string $county,
	public string $postalCode,
	public string $country,
}

У нас есть класс PHP, который расширяет CastableDataTransferObject, что позволяет нам помечать свойства и их типы, а затем обрабатывает все параметры получения и установки за кулисами.Этот пакет использует под капотом пакет Spatie под названием Data Transfer Object, который довольно популярен в обществе.Так что теперь, если бы мы вообще хотели расширить это, мы могли бы:

namespace App\Casts;
 
use JessArcher\CastableDataTransferObject\CastableDataTransferObject;
 
class Address implements CastableDataTransferObject
{
	public string $nameOrNumber,
	public string $streetName,
	public string $localityName,
	public string $town,
	public string $county,
	public string $postalCode,
	public string $country,
 
	public function formatString(): string
	{
		return implode(', ', [
			"$this->nameOrNumber $this->streetName",
			$this->localityName,
			$this->townName,
			$this->county,
			$this->postalCode,
			$this->country,
		]);
	}
}

У нас есть класс Address, который расширяет класс CastableDataTransferObject из пакета, который обрабатывает все наши операции по получению и установке данных в базу данных. После этого у нас есть все свойства, которые мы хотим сохранить — это адрес в формате Великобритании, где я живу.Наконец, у нас есть метод, который мы добавили, который помогает нам отформатировать этот адрес как строку — если мы хотим отобразить это в любой форме пользовательского интерфейса. Мы могли бы сделать еще один шаг вперед с проверкой почтового индекса с помощью регулярных выражений или API, но это может свести на нет смысл руководства.

Давайте перейдем к другому примеру: деньги.Мы все понимаем деньги; мы знаем, что можем использовать деньги в их наименьшей форме в виде монет и в более крупной форме в виде банкнот. Итак, представьте, что у нас есть магазин электронной коммерции, в котором мы храним наши продукты (простой магазин, поэтому нам не нужно беспокоиться о вариантах и ​​т. д.), и у нас есть столбец с именем price, в котором мы храним в наименьшем знаменателе нашей валюты. Я нахожусь в Великобритании, поэтому я назову это пенсом.Однако в США это центы. Распространенный подход к хранению денежных значений в нашей базе данных, позволяющий избежать проблем с математикой с плавающей запятой. Итак, давайте создадим приведение для этого ценового столбца, на этот раз с помощью пакета php moneyphp/money:

namespace App\Casts;
 
use Money\Currency;
use Money\Money;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
 
class Money implements CastsAttributes
{
    public function __construct(
	    protected int $amount,
	) {}
 
	public function get($model, string $key, $value, array $attributes)
	{
	    return new Money(
			$attributes[$this->amount],
			new Currency('GBP'),
		);
	}
 
	public function set($model, string $key, $value, array $attributes)
	{
		return [
			$this->amount => (int) $value->getAmount(),
			$this->curreny => (string) $value->getCurrency(),
		];
	}
}

Поэтому этот класс не использует пакет DTO, а вместо этого возвращает новый экземпляр со своими собственными методами. В нашем конструкторе мы передаем количество.Когда мы получаем и устанавливаем сумму, мы либо приводим ее к массиву для хранения в базе данных, либо анализируем массив, чтобы вернуть новый денежный объект. Конечно, мы могли бы вместо этого сделать это объектом передачи данных и немного больше контролировать, как мы обращаемся с деньгами. Тем не менее, библиотека php money довольно хорошо протестирована и надежна, и, если честно, я бы мало что сделал по-другому.

Перейдем к новому примеру. У нас есть приложение CRM, и мы хотим хранить часы работы или дни работы. Каждый бизнес должен иметь возможность отмечать дни, когда он открыт, но только простым истинным или ложным способом. Таким образом, мы можем создать новое приведение, но сначала мы создадим класс, к которому мы хотим привести, очень похоже на PHP-класс для денег выше.

namespace App\DataObjects;
 
class Open
{
    public function __construct(
	    public readonly bool $monday,
		public readonly bool $tuesday,
		public readonly bool $wednesday,
		public readonly bool $thursday,
		public readonly bool $friday,
		public readonly bool $saturday,
		public readonly bool $sunday,
		public readonly array $holidays,
	) {}
}

Начнем с того, что это нормально; мы просто хотим сохранить, открыт ли бизнес каждый день, и иметь ряд дней, которые они считают выходными. Далее мы можем разработать наш состав:

namespace App\Casts;
 
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
 
class OpenDaysCast implements CastsAttributes
{
	public function __construct(
		public readonly array $dates,
	) {}
 
	public function set($model, string $key, $value, array $attributes)
	{
		return $this->dates;
	}
 
	public function get($model, string $key, $value, array $attributes)
	{
		return Open::fromArray(
			dates: $this->dates,
		);
	}
}

Наш конструктор примет массив дат для упрощения сохранения (однако вам нужно убедиться, что вы правильно проверяете ввод).Затем, когда мы хотим получить данные обратно из базы данных, мы создаем новый объект Open, передавая даты.Однако здесь мы вызываем метод fromArray, который нам еще предстоит создать, поэтому давайте спроектируем его сейчас:

public static function fromArray(array $dates): static
{
    return new static(
	    monday: (bool) data_get($dates, 'monday'),
		tuesday: (bool) data_get($dates, 'tuesday'),
		wednesday: (bool) data_get($dates, 'wednesday'),
		thursday: (bool) data_get($dates, 'thursday'),
		friday: (bool) data_get($dates, 'friday'),
		saturday: (bool) data_get($dates, 'saturday'),
		sunday: (bool) data_get($dates, 'sunday'),
		holidays: (array) data_get($dates, 'holidays'),
	);
}

Поэтому мы вручную создаем наш объект Open с помощью помощника Laravel data_get, который очень удобен, гарантируя, что мы выполняем приведение к правильному типу.Теперь, когда мы запрашиваем, у нас есть доступ:

$business = Business:query()->find(1);
 
// Is this business open on mondays?
$business->open->monday; // true|false
 
// Is the business open on tuesdays?
$business->open->tuesday; // true|false
 
// What are the busines holiday dates?
$business->open->holidays; // array

Как видите, мы можем сделать это чрезвычайно удобочитаемым, чтобы опыт разработчиков был логичным и простым для восприятия. Можем ли мы затем расширить это, чтобы добавить дополнительные методы, например, открыты ли они сегодня?

public function today(): bool
{
    $date = now();
 
	$day = strtolower($date->englishDayOfWeek());
 
	if (! $this->$day) {
	    return false;
	}
 
	return ! in_array(
	    $date->toDateString(),
		$this->holidays,
	);
}
$business = Business:query()->find(1);
 
// Is the business open today?
$business->open->today();

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297