vicgutt / laravel-auto-model-cast
根据数据库列自动转换模型属性
Requires
- php: ^8.1
- illuminate/contracts: ^10.0
- spatie/laravel-package-tools: ^1.14.0
- vicgutt/laravel-inspect-db: ^0.1.2
- vicgutt/laravel-models-finder: ^0.1.0
- vicgutt/php-enhanced-enum: ^0.1.0
Requires (Dev)
- laravel/pint: ^1.0
- nunomaduro/collision: ^6.0
- nunomaduro/larastan: ^2.0.1
- orchestra/testbench: ^8.0
- pestphp/pest: ^1.21
- pestphp/pest-plugin-laravel: ^1.1
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- phpunit/phpunit: ^9.5
README
本软件包允许您通过检查数据库列,自动转换模型属性。
安装
您可以通过composer安装此软件包
composer require vicgutt/laravel-auto-model-cast
您可以使用以下命令发布配置文件
php artisan vendor:publish --tag="laravel-auto-model-cast-config"
发布配置文件的内容如下: config/auto-model-cast.php。
工作原理
该软件包通过查找项目中所有模型,并根据其数据库列类型确定它们的属性转换类型来工作。
以下是采取的步骤
- 尝试根据给定的目录、基础项目路径和基础项目命名空间找到项目中所有模型 (使用
vicgutt/laravel-models-finder)。 - 对于每个模型,提取数据库表名和使用的连接。
- 对于给定连接的每个表,提取所有列 (使用
vicgutt/laravel-inspect-db)。 - 对于每个列,根据其模式提取列的类型 (使用
vicgutt/laravel-inspect-db,它反过来使用doctrine/dbal)。 - 对于每个列类型,尝试将其映射为Laravel 支持的转换类型。
- 最后,将转换类型添加到模型的
protected $casts数组中。
注意
请参阅文档末尾的 "注意事项" 部分。
缓存
所有自动转换功能都在配置文件中完成,其结果从配置文件中检索。这意味着每个模型的转换类型都存储在配置文件中,当给定模型需要检索其转换信息时,它会从配置文件中获取。
这种行为的最大优点是防止不断检查数据库以获取相同的静态且很少更改的列名和类型信息。但这也意味着,一旦项目配置文件被缓存,除非清除配置缓存,否则不会考虑新添加的列。
用法
准备模型
您的模型需要通过实现 AutoCastable 接口和使用 HasAutoCasting 特性来选择自动转换行为
declare(strict_types=1); namespace App\Models; use Illuminate\Database\Eloquent\Model; use VicGutt\AutoModelCast\Contracts\AutoCastable; use VicGutt\AutoModelCast\Concerns\HasAutoCasting; final class MyModel extends Model implements AutoCastable { use HasAutoCasting; }
覆盖自动转换
您仍然可以通过 protected $casts 属性数组以通常方式转换模型属性。这些值将优先于任何确定的自动转换。
例如,假设您有以下列和定义的转换
// a migration file Schema::create('examples', function (Blueprint $table): void { $table->json('extras'); }); // a model file use Illuminate\Database\Eloquent\Casts\AsCollection; final class Example extends Model implements AutoCastable { use HasAutoCasting; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'extras' => AsCollection::class, ]; }
默认情况下,当使用提供的类型映射 (TypeMapper::defaults() 或 TypeMapper::opinionated()) 时,任何 json 列类型将被自动转换为 json 或 Illuminate\Database\Eloquent\Casts\AsArrayObject。本质上做的是
protected $casts = [ 'extras' => 'json', // or AsArrayObject::class ];
但是,由于模型指定了它自己的 extras 属性的铸造,因此将使用指定的值。因此,Example 模型的 extras 属性将被铸造为 AsCollection。
发现模型
可以通过 Casts 类的 discoverModelsUsing 方法将参数传递给底层的 VicGutt\ModelsFinder\ModelsFinder 类,以自定义模型发现的方式。
示例
Casts::new()->discoverModelsUsing( directory: app_path('Models'), basePath: app_path(), baseNamespace: 'App', );
默认情况下,包将在 app/Models 目录中搜索模型。
有关 ModelsFinder 类的更多信息,请参阅此处文档:[https://github.com/vicgutt/laravel-models-finder](https://github.com/vicgutt/laravel-models-finder)。
创建自定义类型映射器
“类型映射器”只是一个类,它负责将特定的 Doctrine 列类型映射到内置的 Laravel 铸造类型。映射器接收一个列作为输入,并输出一个铸造类型。
要创建自己的映射器,创建一个扩展 VicGutt\AutoModelCast\Support\TypeMapper 的类,并按需覆盖任何给定方法。要使用您创建的新映射器,将其实例的完全限定类名传递给 Casts 类的 useTypeMapper 方法。
示例
use VicGutt\AutoModelCast\Support\TypeMapper; final class MyCustomTypeMapper extends TypeMapper { // } Casts::new()->useTypeMapper(MyCustomTypeMapper::class);
创建自定义铸造器
“铸造器”只是一个类,它负责将给定的数据库列集合转换为支持的 Laravel 铸造类型的数组。
要创建自己的铸造器,创建一个扩展 VicGutt\AutoModelCast\Support\Casters\BaseCaster 的类,并按照 BaseCaster 的说明实现 handle 方法。
示例
use VicGutt\InspectDb\Entities\Column; use Illuminate\Database\Eloquent\Model; use VicGutt\AutoModelCast\Support\Casters\BaseCaster; final class MyCustomCaster extends BaseCaster { public function handle(Column $column, Model $model): ?string { // } }
指定默认铸造器
要为所有模型默认使用自定义铸造器,将您的铸造器的完全限定类名传递给 Casts 类的 useDefaultCaster 方法。
示例
Casts::new()->useDefaultCaster(MyCustomCaster::class);
按模型指定自定义铸造器
要为给定的模型使用自定义铸造器,提供数组给 Casts 类的 withCustomCasters 方法。数组的键应该是模型的完全限定类名,值应该是自定义铸造器的完全限定类名。
数组中未指定的任何模型将使用默认铸造器。
示例
Casts::new()->withCustomCasters([ \App\Models\User::class => \App\Support\AutoCast\Casters\UserCustomCaster::class, ]);
指定默认类型映射
“类型映射”是一个数组,其中 Doctrine 类型作为键,Laravel 铸造类型作为值。它实际上是上述“映射器”的简化版本。它允许在不需要实现自定义类的情况下轻松快速地自定义 Doctrine 类型与 Laravel 铸造类型之间的映射。
实际上,映射器在底层使用了提供的类型映射。
要为映射器提供默认使用的自定义类型映射,只需将数组传递给 Casts 类的 withDefaultTypesMap 方法。
示例
Casts::new()->withDefaultTypesMap([ DoctrineTypeEnum::BIGINT->value => CastTypeEnum::INT->value, DoctrineTypeEnum::DATE->value => CastTypeEnum::IMMUTABLE_DATE->value, DoctrineTypeEnum::JSON->value => CastTypeEnum::AS_ARRAY_OBJECT_CLASS->value, ]);
为了方便,提供了两个类型映射
VicGutt\AutoModelCast\Support\TypeMapper\TypeMapper::opinionated()VicGutt\AutoModelCast\Support\TypeMapper\TypeMapper::defaults()
默认值
在这里,Doctrine 列类型简单地映射到它们的 Laravel 铸造类型。
意见
在这里,我们在默认值的基础上提供了一种合理的意见映射。基本上将任何日期相关类型铸造为不可变。
检索模型的自动铸造
如果您想检索给定模型的自动铸造,您有三个选择
- 使用模型的实例的
getAutoCasts方法。 - 使用
Casts类的for静态方法。 - 使用配置。
示例
$model = new MyModel; $casts = $model->getAutoCasts(); // Or $casts = Casts::for($model::class); // Or $casts = Casts::for($model); // Or $casts = config("auto-model-cast.casts.{$model::class}", []);
返回的数组将以模型的属性名称作为键,铸造类型作为值。
注意
在底层,辅助方法使用配置文件。因此,这些方法只能在配置文件设置之后使用。
注意事项
依赖于数据库平台的列类型
根据所使用的数据库平台,某些列类型可能由底层的 doctrine/dbal 以不同的方式检测,从而导致属性转换不一致。例如,在 SQLite 中,json 列类型最可能被转换为 string 类型,而在 MySQL 或 Postgres 中则会被转换为 array 类型。
如果你不打算在项目中期更改数据库平台,这显然不是问题。
doctrine/dbal 的类型转换
为了实现数据库无关的应用程序,doctrine/dbal 内置了一个类型转换系统,支持从任何数据库平台转换到和从 PHP 值。这在大多数情况下都很好,但也导致我们使用案例中的数据丢失,因为我们无法访问实际的原始列类型,而只有转换。
例如,使用 MySQL,TINYINT(1) 类型将被转换为并返回为 boolean 类型,这通常是我们要的,在这种情况下,我们可以很容易地找出原始列类型。但一个不希望的情况是将 YEAR 列类型转换为返回 date 类型。在我们的使用案例中,YEAR 列只接受四位数字(或 MySQL 8.0 之前的两位数字)的值,无论是作为字符串还是整数。
示例
Schema::create('...', function (Blueprint $table): void { $table->year('birth_year'); $table->year('adulthood_year'); }); // $model->create([ 'birth_year' => '2000', 'adulthood_year' => 2018, ]);
然而,在上面的例子中,如果两个 birth_year 和 adulthood_year 属性被自动转换为日期类型,它们将被转换为日期,由 Laravel 转换为值为 1970-01-01T00:00:00.000000Z 的 Carbon 实例,这会在尝试插入值时导致数据库抛出异常。
这是一个我将来想重新审视的问题。
变更日志
有关最近更改的更多信息,请参阅 变更日志。
贡献
如果您有兴趣为该项目做出贡献,请在提交拉取请求之前阅读我们的 贡献文档 。
安全漏洞
有关报告安全漏洞的详细信息,请参阅 我们的安全策略。
致谢
许可证
MIT 许可证 (MIT)。有关更多信息,请参阅 许可证文件。