Eloquent 属性转换
发表于 作者 Steve McDougall
Eloquent 可转换属性是 Laravel 最强大的功能之一。有些人经常使用它们,而有些人则倾向于避开它们。在本教程中,我将介绍几个使用它们的示例,最重要的是,为什么你应该使用它们。
你可以创建一个新的 Laravel 项目或使用现有的项目来完成本教程。请随意跟着我一起做,或者如果你想阅读并记住它们 - 也可以。从本教程中要带走的最重要的一点是 Eloquent 转换有多好。
让我们从第一个例子开始,类似于 Laravel 文档中的例子。Laravel 文档展示了如何获取用户的地址,但使用了 Model。现在想象一下,如果这是用户模型上的 JSON 列,或者实际上是任何模型上的 JSON 列。我们可以获取和设置一些数据,并添加一些额外的功能来让它按我们想要的方式工作。我们需要安装一个由 Jess Archer 开发的软件包,名为 Laravel Castable 数据传输对象。你可以使用以下 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
类,它处理我们对数据库的所有数据获取和设置。然后,我们拥有我们想要存储的所有属性 - 这是我居住地的英国格式地址。最后,我们添加了一个方法,帮助我们将这个地址格式化为字符串 - 如果我们想在用户界面中显示它。我们可以使用正则表达式或 API 来进一步进行邮政编码验证,但这可能背离了本教程的目的。
让我们继续另一个例子:货币。我们都理解货币;我们知道我们可以使用最小的硬币形式和更大的钞票形式的货币。所以想象一下,我们有一个电子商务商店,我们在其中存储我们的产品(一个简单的商店,所以我们不必担心变体等),并且我们有一个名为 price
的列,我们在其中存储我们货币的最小单位。我在英国,所以我会称之为便士。然而,在美国是美分。在我们的数据库中存储货币值的常用方法,因为它避免了浮点数学问题。所以让我们为这个 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 货币库经过了充分的测试并且可靠,说实话 - 我不会做太多改变。
让我们来看一个新的例子。我们有一个 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, );}
所以这个方法可能假设我们正在以日期字符串的形式存储假日,但逻辑如下:获取星期几,以英语表示,因为这是我们的属性名称;如果该天是 false,则返回 false。但是,我们还需要检查假日。如果当前日期字符串不在假日数组中,则营业;否则,则关闭。
所以我们可以然后检查企业是否营业,使用我们转换上的 today 方法
$business = Business:query()->find(1); // Is the business open today?$business->open->today();
如你所想,你可以向其中添加许多方法,使你能够检查多种事物,甚至添加一些方法来很好地显示这些信息。
你在你的应用程序中使用属性转换吗?你还有其他一些很酷的例子可以分享吗?请在推特上与我们分享你的例子,并分享知识的力量。
Laravel 新闻 的技术作家,Treblle 的开发者倡导者。API 专家,经验丰富的 PHP/Laravel 工程师。 YouTube 直播主。