activecollab / databasestructure
使用PHP定义数据库结构,并让库生成对象类和表
Requires
- php: >=8.0
- ext-mysqli: *
- activecollab/databaseobject: ^6.0
- activecollab/filesystem: ^0.10
- activecollab/user: ^4.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.0
- phpunit/phpunit: ^9.0
This package is auto-updated.
Last update: 2024-09-09 09:41:16 UTC
README
版本 1.0 待办事项
- 获取代码覆盖率超过 90%,
- 将
serialize
方法添加到字段,以便在将字段添加到类型时自动将其添加到序列化列表中, - 将所有基类前缀为
Base
, - 将所有管理器和集合后缀分别为
Manager
和Collection
, - 检查由关联添加的字段和属性之间的可能冲突,
- 向 Has Many 和 Has Many Via 关联添加
release
和clear
方法, - 添加
ChildInterface
,并确保ParentField
将其添加到包含它的模型中, - 关联应自动将连接字段添加到要序列化的字段列表中,
- 关联级联选项和测试,
字段
布尔字段(以 is_
、has_
、had_
、was_
、were_
和 have_
开头)也获得一个简短的获取器。例如,如果字段名称是 is_awesome
,构建器将生成两个获取器:getIsAwesome()
和 isAwesome()
。
密码字段
密码字段是用于存储密码散列的字段。默认情况下,它将 password
设置为字段名称。它与 StringField
类似(使用 VARCHAR
列),但不能有默认值(唉!),并且它没有用于轻松索引的方法(如果您愿意,您仍然可以自己添加索引)。
<?php namespace MyApp; use ActiveCollab\DatabaseStructure\Field\Scalar\PasswordField; new PasswordField(); // Use default name (password). new PasswordField('psswd_hash'); // Specify field name.
JSON 字段
JSON 字段将 JSON 字段添加到类型中。它将在读写时自动序列化和反序列化
$this->addType('stats_snapshots')->addFields( new JsonField('stats') );
除了常规的获取器和设置器外,JSON 字段还添加了一个 modify
方法。该方法接收一个回调函数,该函数将使用解码的 JSON 值调用。然后自动将回调函数的结果存储在字段中
$object->modifyStats( function ($stats) { $stats['something-to-add'] = true; unset($stats['something-to-remove']); return $stats; } );
JSON 字段可以存储很多不同的数据类型,因此您可能无法总是知道将传递给回调函数的类型。在我们的日常使用中,我们注意到数组是存储在 JSON 字段中最常见的数据类型。为了确保您始终获得数组,无论字段中有什么内容,请传递第二个 $force_array
参数
$object->modifyStats( function (array $this_will_be_array_for_sure) { return $this_will_be_array_for_sure; }, true );
系统支持从 JSON 字段中提取值。这些值由 MySQL 自动提取,并且可以存储和索引。
添加提取器有两种方式。首先是通过自己构建提取器实例并添加它
$execution_time_extractor = (new FloatValueExtractor('execution_time', '$.exec_time', 0)) ->storeValue() ->addIndex(); $this->addType('stats_snapshots')->addFields( new DateField('day'), (new JsonField('stats')) ->addValueExtractor($execution_time_extractor) );
第二种是通过调用 extractValue
方法,该方法使用提供的参数构建适当的提取器,配置它并将其添加到字段中。方法参数
field_name
- 生成的字段名称,expression
- 用于从 JSON 中提取值的表达式。有关详细信息,请参阅 https://dev.mysqlserver.cn/doc/refman/5.7/en/json-search-functions.html#function_json-extract MySQL 函数,default_value
- 如果expression
返回NULL
,则使用的值。extractor_type
- 应使用的提取器实现类的名称。默认为ValueExtractor
(字符串值提取器),但也支持 int、float、bool、日期和日期时间值的提取器。is_stored
- 值是否应永久存储,或者是否应为虚拟的(在读取时动态计算)。默认值是存储。is_indexed
- 值是否应被索引。当设置为TRUE
时,在生成的字段上添加索引。默认为FALSE
。
示例
$this->addType('stats_snapshots')->addFields( new DateField('day'), (new JsonField('stats')) ->extractValue('plan_name', '$.plan_name', 'Unknown', ValueExtractor::class, true, true) ->extractValue('number_of_active_users', '$.users.num_active', 0, IntValueExtractor::class, true) ->extractValue('is_used_on_day', '$.is_used_on_day', null, BoolValueExtractor::class, false), );
自动为所有生成的字段添加获取器方法。
$snapshot = $pool->getById(StatsSnapshot::class, 1); print $snapshot->getPlanName() . "\n"; print $snapshot->getNumberOfActiveUsers() . "\n"; print ($snapshot->isUsedOnDay() ? 'yes' : 'no') . "\n";
请注意,生成的字段的值不能直接设置。此代码将引发异常。
$snapshot = $pool->getById(StatsSnapshot::class, 1); $snapshot->setFieldValue('number_of_active_users', 123); // Exception!
关联
属于
面向接口编程
“属于”关联支持“面向接口编程”方法。这意味着您可以将其设置为接受(并返回)实现特定接口的实例。
<?php namespace MyApp; use ActiveCollab\DatabaseStructure\Association\BelongsToAssociation; (new BelongsToAssociation('author'))->accepts(AuthorInterface::class);
有多个
<?php namespace App; use ActiveCollab\DatabaseStructure\Association\BelongsToAssociation; use ActiveCollab\DatabaseStructure\Association\HasManyAssociation; use ActiveCollab\DatabaseStructure\Field\Composite\NameField; use ActiveCollab\DatabaseStructure\Structure; class HasManyExampleStructure extends Structure { public function configure(): void { $this->addType('writers')->addFields( (new NameField('name', ''))->required(), )->addAssociations( new HasManyAssociation('books'), ); $this->addType('books')->addFields( (new NameField('name', ''))->required(), )->addAssociations( new BelongsToAssociation('writer'), ); } }
此关联将为 Writer
模型添加以下方法:
getBooksFinder(): FinderInterface
- 为此作者准备一个书籍查找实例,所有默认值都已设置(例如排序)。就像使用任何其他查找器一样使用它:通过添加额外条件扩展它,使用它来计数记录、获取所有记录或第一条记录等。getBooks(): ?iterable
- 返回属于作者的所有书籍。如果没有找到书籍,则此方法返回NULL
。getBookIds(): ?iterable
- 返回属于作者的所有书籍 ID 列表。如果没有找到书籍,则此方法返回NULL
。countBooks(): int
- 返回书籍的总数。
属性
“有多个”关联还为模型添加以下属性:
books
- 通过提供其实例来设置关联的书籍。这些实例可以持久化到数据库,或它们可以是新实例。如果是新的,则当父作者对象被保存时将保存它们。book_ids
- 通过提供其 ID 来设置关联的书籍。
<?php namespace App; // Set books using an attribute: $writer = $pool->produce(Writer::class, [ 'name' => 'Leo Tolstoy', 'books' => [$book1, $book2, $book3], ]); // Or, using ID-s: $writer = $pool->produce(Writer::class, [ 'name' => 'Leo Tolstoy', 'book_ids' => [1, 2, 3, 4], ]);
面向接口编程
“有多个”关联支持“面向接口编程”方法。这意味着您可以将其设置为接受(并返回)实现特定接口的实例。
示例
<?php namespace MyApp; use ActiveCollab\DatabaseStructure\Association\HasManyAssociation; (new HasManyAssociation('books'))->accepts(BookInterface::class);
有一个
面向接口编程
“有一个”关联支持“面向接口编程”方法。这意味着您可以将其设置为接受(并返回)实现特定接口的实例。
示例
<?php namespace MyApp; use ActiveCollab\DatabaseStructure\Association\HasOneAssociation; (new HasOneAssociation('book'))->accepts(BookInterface::class);
通过
多对多
<?php namespace App; use ActiveCollab\DatabaseStructure\Association\HasAndBelongsToManyAssociation; use ActiveCollab\DatabaseStructure\Field\Composite\NameField; use ActiveCollab\DatabaseStructure\Structure; class HasManyExampleStructure extends Structure { public function configure(): void { $this->addType('writers')->addFields( (new NameField('name', ''))->required(), )->addAssociations( new HasAndBelongsToManyAssociation('books'), ); $this->addType('books')->addFields( (new NameField('name', ''))->required(), )->addAssociations( new HasAndBelongsToManyAssociation('writers'), ); } }
此关联将为 Writer
模型添加以下方法:
getBooksFinder(): FinderInterface
- 为此作者准备一个书籍查找实例,所有默认值都已设置(例如排序)。就像使用任何其他查找器一样使用它:通过添加额外条件扩展它,使用它来计数记录、获取所有记录或第一条记录等。getBooks(): ?iterable
- 返回属于作者的所有书籍。如果没有找到书籍,则此方法返回NULL
。getBookIds(): ?iterable
- 返回属于作者的所有书籍 ID 列表。如果没有找到书籍,则此方法返回NULL
。countBooks(): int
- 返回书籍的总数。&addBooks(...$books): void
- 向作者添加一个或多个书籍。&removeBooks(...$books): void
- 移除与作者关联的一个或多个书籍。&clearBooks(): void
- 清除与作者关联的所有书籍连接(书籍对象不会被删除)。
属性
多对多关联还为模型添加以下属性:
books
- 通过提供其实例来设置关联的书籍。这些实例可以持久化到数据库,或它们可以是新实例。如果是新的,则当父作者对象被保存时将保存它们。book_ids
- 通过提供其 ID 来设置关联的书籍。
<?php namespace App; // Set books using an attribute: $writer = $pool->produce(Writer::class, [ 'name' => 'Leo Tolstoy', 'books' => [$book1, $book2, $book3], ]); // Or, using ID-s: $writer = $pool->produce(Writer::class, [ 'name' => 'Leo Tolstoy', 'book_ids' => [1, 2, 3, 4], ]);
结构选项
结构对象通过 setConfig()
方法支持通过配置设置配置选项。此方法可以在对象配置期间或创建后调用。
class MyStructure extends Structure { public function configure(): void { $this->setConfig('option_name', 'value'); } }
以下选项可用:
add_permissions
- 向对象添加 CRUD 权限检查。更多信息…base_class_doc_block_properties
- 指定要添加到生成的类的 DocBlock 部分的属性数组。更多信息…base_class_extends
- 指定应扩展对象构建的类(默认为ActiveCollab\DatabaseObject\Object
)。
add_permissions
此选项告诉结构自动为其添加到其中的所有类型调用 permissions()
方法。默认情况下,此选项是关闭的,但可以通过将其设置为两个值之一来启用它。
StructureInterface::ADD_PERMISSIVE_PERMISSIONS
启用权限和方法检查权限是否设置,默认返回true
。StructureInterface::ADD_RESTRICTIVE_PERMISSIONS
启用权限和方法检查权限是否设置,默认返回false
。
示例
class MyStructure extends Structure { public function configure(): void { $this->setConfig(‘add_permissions’, StructureInterface::ADD_RESTRICTIVE_PERMISSIONS); } }
base_class_doc_block_properties
一些编辑器会从类的 DocBlock 部分读取 @property
,并知道哪些属性可以通过魔法方法访问,它们的类型是什么,并根据这些信息提供各种功能(如代码补全、类型检查等)。使用 base_class_doc_block_properties
来指定要添加到类中的属性列表。配置示例:
class MyStructure extends Structure { public function configure(): void { $this->setConfig(‘base_class_doc_block_properties’, [ 'jobs' => '\\ActiveCollab\\JobsQueue\\Dispatcher' ]); } }
构建内容
<?php namespace Application\Structure\Namespace\Base; /** * @property \ActiveCollab\JobsQueue\Dispatcher $jobs * * … */ abstract class Token extends \ActiveCollab\DatabaseObject\Entity\Entity { }
deprecate_long_bool_field_getter
如果您希望将布尔字段的长 getter 方法标记为已弃用,当存在短 getter 方法(isAwesome()
与 getIsAwesome()
)时,请将此选项设置为 true。
header_comment
添加一个注释,该注释将被包含在所有自动生成的文件的开头。此选项在您需要在源代码中包含许可信息时很有用。
行为
行为是类型和字段添加到结果对象类的接口和接口实现。这些行为可以执行各种操作:允许您在集合中定位元素,在对象级别存储额外的信息,检查用户权限等。
权限行为
应用后,权限行为会将 ActiveCollab\DatabaseStructure\Behaviour\PermissionsInterface
添加到对象类中,它添加了四个检查给定对象用户权限的方法
canCreate($user)
canView($user)
canEdit($user)
canDelete($user)
所有四个方法仅接受一个参数,并且该参数需要是实现 \ActiveCollab\User\UserInterface
接口的实例。
有两个默认实现可以作为 PermissionsInterface
的实现添加
ActiveCollab\DatabaseStructure\Behaviour\PermissionsInterface\PermissiveImplementation
默认返回true
,ActiveCollab\DatabaseStructure\Behaviour\PermissionsInterface\RestrictiveImplementation
默认返回false
。
注意:生成的代码在执行 CRUD 操作之前不会强制执行这些检查。强制应用这些限制的责任在于包含 DatabaseStructure 库的应用程序(例如在 ACL 或控制器层)。
可以将结构配置为自动将权限行为应用于类型(请参阅 add_permissions
结构选项)。当结构自动将权限行为添加到类型时,但您希望为特定类型关闭此功能,只需再次调用 permissions(false)
即可。
class MyStructure extends Structure { public function configure(): void { $this->setConfig(‘add_permissions’, StructureInterface::ADD_RESTRICTIVE_PERMISSIONS); $this->addType(‘reverted_elements’) ->addFields() ->permissions(false); } }
受保护字段行为
此行为向对象添加一个简单的受保护字段列表(可通过 getProtectedFields()
方法访问)。系统的其余部分需要决定如何处理此列表,但最常见的情况是在使用 POST 请求添加对象或使用 PUT 请求更新对象时禁用这些字段的设置。
class MyStructure extends Structure { public function configure(): void { $this->addType('elements')->protectFields('created_at', 'created_by_id')->unprotectFields('created_by_id'); // will record ['created_at'] } }
protectFields
忽略空字段值,并且可以多次调用。
class MyStructure extends Structure { public function configure(): void { $this->addType('elements')->protectFields('field_1', 'field_2')->protectFields('', '')->protectFields('field_2', 'field_3'); // will only record ['field_1', 'field_2', 'field_3'] } }