如何在Laravel中使用Eloquent Castable

112 阅读3分钟

Eloquent Castable属性是Laravel更强大的功能之一; 一些人虔诚地使用它们,而另一些人则倾向于避开它们。在本教程中, 我将通过几个不同的例子来说明如何使用它们, 最重要的是, 为什么你应该使用它们.

你可以创建一个新的Laravel项目或使用一个现有的项目来学习本教程。你可以跟着我一起学习, 或者如果你想阅读和记忆 - 这也是可以的.最关键的是,从这里可以看出,雄辩的铸模是多么的好。

让我们从第一个例子开始,类似于Laravel文档中的例子。Laravel文档显示了获取用户的地址,但也使用了一个模型。现在想象一下,如果这是一个JSON列的用户或任何模型的事实。我们可以获得和设置一些数据,并添加额外的内容,使其按照我们的意愿工作。我们需要安装一个由Jess Archer制作的包,叫做Laravel Castable Data Transfer Object。你可以使用下面的composer命令来安装它:

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包,这个包在社区中相当流行。所以现在,如果我们想扩展这个,我们可以。

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 类,它处理我们对数据库数据的所有获取和设置。然后我们有所有我们想要存储的属性--这是英国格式的地址,因为这是我居住的地方。最后,我们添加了一个方法,帮助我们将这个地址格式化为一个字符串--如果我们想在用户界面的任何形式下显示这个地址的话。我们可以更进一步,使用regex或API进行邮编验证--但这可能有悖于本教程的目的。

让我们继续看另一个例子:钱。我们都了解钱;我们知道我们可以使用最小形式的硬币和较大形式的纸币。所以想象一下,我们有一个电子商务商店,在那里我们存储我们的产品(一个简单的商店,所以我们不必担心变体等等),我们有一个叫做price 的列,我们以最小的货币分母存储。我在英国,所以我将其称为便士。然而,在美国这就是美分。在我们的数据库中存储货币价值的常见方法,因为它避免了浮点数学问题。因此,让我们为这个价格列设计一个铸模,这次使用phpmoneyphp/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货币库已经经过了很好的测试,而且很可靠,如果我说实话--我没有什么不同的做法。

让我们来看看一个新的例子。我们有一个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'),
    );
}

所以我们使用Laravel的助手data_get ,手动建立了我们的Open对象,这非常方便,确保我们投递的是正确的类型。现在,当我们查询时,我们有了访问权。

$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,
    );
}

所以这个方法可能会做一个小小的假设,即我们在日期字符串中存储假期,但逻辑是这样的:获取一周中的一天,用英语说,因为这是我们的属性名称;如果这一天是假的,那么返回假的。然而,我们还需要检查假期。如果当前日期字符串不在假期的数组中,那么它是开放的;否则,它是关闭的。

所以我们就可以用我们的cast上的today方法来检查该企业是否开放。

$business = Business:query()->find(1);
 
// Is the business open today?
$business->open->today();

你可能可以想象,你可以在此基础上添加许多方法,允许你检查多种事物,甚至添加显示这些信息的方法,非常好。

你在你的应用程序中使用属性铸造吗?你有其他很酷的例子可以分享吗?请给我们发一条推特,告诉我们你的例子,分享知识的力量。