pointybeard / symphony-classmapper
将部分映射到自定义模型类中,简化了在 Symphony CMS 中创建、修改、删除和检索条目的过程。
Requires
- php: >=7.2
- pointybeard/helpers-foundation-factory: ~1.0.0
- pointybeard/helpers-functions-flags: ~1.0.0
- pointybeard/symphony-pdo: ~0.1.7
Requires (Dev)
README
将部分映射到自定义模型类中,简化了在 Symphony CMS 中创建、修改、删除和检索条目的过程。
要求
此库需要 PHP 7.2 或更高版本。对于使用更早版本的 PHP,请使用 1.0.x(composer require pointybeard/symphony-classmapper:\<2.0
)。
安装
通过 Composer 安装 Symphony Class Mapper。要安装,请使用 composer require pointybeard/symphony-classmapper
或将 "pointybeard/symphony-classmapper": "~2.0"
添加到您的 composer.json
文件中。
用法
最基本的使用方法是让 Section Class Mapper 为您创建一个匿名类,并使用 Classmapper\create()
将其映射到您的部分。例如,假设您有一个名为 'articles' 的部分和一个名为 'title' 的字段
declare(strict_types=1); include 'vendor/autoload.php'; use pointybeard\Symphony\Classmapper; Classmapper\create( 'Article', // Name of class to be created 'articles' // Handle of section to map );
请注意,create()
的第二个参数,部分处理,是可选的。如果省略,Classmapper 将尝试从您的类名(在本例中为 'Article')中推断您的部分处理。它通过假设您的部分是类名的复数形式来完成此操作。
在上面的示例中,'Article' 的类名将用于推断相应的部分处理为 articles
。如果类映射器无法找到部分,将抛出 ClassmapperException
。
设置部分处理对于您的部分名称不遵循复数化假设或可能返回模糊结果(即,多个匹配的部分)是有用的。
创建后,可以通过实例化新创建的 Article
类、设置字段值和调用 save()
来创建文章。
// Create a new article $article = new Article; $article->title('My Article'); $article->save(); // Classmapper also supports method chaining like so (new Article) ->title('My New Article') ->save() ;
可以使用两个内置方法访问现有文章:all()
和 loadFromId()
。
// Get article with id of 1 $article = Article::loadFromId(1); // Iterate over all articles foreach(Article::all() as $article) { printf("%d: %s\r\n", $article->id, $article->title); }
其他有用的方法包括 hasBeenModified()
、toXml()
和 delete()
。
# Check if it was modified $article->hasBeenModified(); # Get the XML representation of your Article $article->toXml(); # Remove the article $article->delete() # Alternatively, you can delete entries like this Article::loadFromId(3)->delete();
创建自定义模型类
通过调用 Classmapper::create()
生成的自动生成的类很有用,但有些有限。最大的限制是它们无法具有自定义字段映射以适应非标准字段。对于没有与其他部分复杂关系的简单部分很有用。为了克服这种限制,我们需要创建一个具体的类。这为您提供了所有内置方法,但允许您扩展其 API,最重要的是,定义字段。
要创建自定义 Classmapper 模型,扩展 AbstractModel
并使用 HasModelTrait
特性。例如,使用相同的 Articles 例子
<?php namespace Your\Project\Namespace; use pointybeard\Symphony\Classmapper; final class Article extends Classmapper\AbstractModel { use Classmapper\Traits\HasModelTrait; }
HasModelTrait
特性提供了三个静态成员变量:$sectionFields
、$fieldMapping
和 $section
。它们在内部使用,并持有对 Symphony 部分及其字段的映射;所有这些都是在运行时由父对象自动填充的。
如果部分有一个非标准处理,可以通过覆盖 AbstractModel::getSectionHandle()
来手动设置,以返回部分处理。例如。
... public function getSectionHandle(): string { return 'articles'; } ...
在此阶段,自定义 Article 类与由 Classmapper::create('Article')
生成的类相同,但我们有一个添加额外功能、逻辑和定义字段的框架。
访问值
类映射器自动为部分中的所有字段创建类成员名称。这些名称使用字段处理并转换为 camelCase
生成。例如,“published-date”变为 publishedDate
,“my-awesome-field”变为 myAwesomeField
。
创建自定义字段映射
类映射器假定数据库中所有字段都有一个 value
字段,并且该值始终是字符串,但是并非所有字段都如此。例如,一个选择框链接字段有一个名为 relation_id
的字段,它是一个整数。在这种情况下,您必须告诉类映射器字段应该如何映射以及其类型。这是通过重载 AbstractModel::getCustomFieldMapping()
方法来完成的。
以上面的文章示例为例,假设现在有一个名为“作者”的字段,它是一个指向“作者”部分的选择框链接字段。我们将告诉类映射器,作者字段是一个整数,并有一个数据库字段 relation_id
(而不是默认的 value
)。最后,我们将字段名称重映射为 authorId
而不是 author
。
... # Create a mapping for the Author field, mapping the id to 'authorId' protected static function getCustomFieldMapping() { return [ 'author' => [ 'databaseFieldName' => 'relation_id', 'classMemberName' => 'authorId', 'flags' => self::FLAG_INT ], ]; } # Create a method that allows easy retrieval of an Author object. # Note, this assumes an Author class model exists. public function author() { return Author::fetchFromId($this->authorId); } ...
您可以看到我们如何快速将 Articles 模型连接起来,以便了解 Authors 以及如何检索它们。
使用标志
您可以为自定义字段映射指定一个 flag
属性(在上面的“创建自定义字段映射”中简要介绍),以在检索和保存数据时触发不同的行为。
可以使用位或(|
)运算符组合标志。注意,某些标志不能或没有意义与其它标志组合。
以下是一个显示更完整模型自定义字段映射的示例
... protected static function getCustomFieldMapping() { return [ 'related-entries' => [ 'databaseFieldName' => 'relation_id', 'classMemberName' => 'relatedEntryIDs', 'flags' => self::FLAG_ARRAY | self::FLAG_INT | self::FLAG_NULL ], 'published' => [ 'flags' => self::FLAG_BOOL ], 'date' => [ 'classMemberName' => 'dateCreatedAt', 'flags' => self::FLAG_SORTBY | self::FLAG_SORTDESC | self::FLAG_REQUIRED ], 'title' => [ 'flags' => self::FLAG_STR | self::FLAG_REQUIRED ], 'author' => [ 'databaseFieldName' => 'relation_id', 'classMemberName' => 'authorId', 'flags' => self::FLAG_INT | self::FLAG_REQUIRED ], 'subtitle' => [ 'flags' => self::FLAG_STR | self::FLAG_NULL ], ]; } ...
类型
类型标志在数据检索或保存时通知类映射器,应该进行类型转换
FLAG_INT、FLAG_STR 和 FLAG_FLOAT
这些标志用于对提取的数据进行类型转换,并直接映射到原生 PHP 方法 intval()
、floatval()
和 strval()
。它们可以与 FLAG_ARRAY
组合,在这种情况下,数组中的所有项都将转换为该类型。
FLAG_CURRENCY
与 FLAG_FLOAT
类似,但将结果限制为两位小数。
FLAG_BOOL
将数据库中的数据从 Yes|No
字符串值转换为 true|false
。在保存时,将其转换回 Yes|No 字符串值。可以与 FLAG_ARRAY
组合
行为
FLAG_ARRAY
当字段有多个数据行时,如多选,请使用此标志。返回的数据将是一个包含值的数组。可以与 FLAG_INT
、FLAG_STR
、FLAG_FLOAT
、FLAG_BOOL
、FLAG_CURRENCY
和 FLAG_NULL
组合
FLAG_FILE
将字段值设置为包含 file
、size
、mimetype
和 meta
的数组。注意,在保存时仅使用 file
,因为其他字段可以通过检查文件(如果需要)重新构建。只能与 FLAG_NULL
组合
FLAG_NULL
将空值(例如,int(0)、string("")、(array)[] 等)转换为 NULL
。可以与所有其他标志组合。当类映射器为模型构建数据时,如果字段的值为空,它将将其设置为 NULL。
排序
排序标志用于检索数据。类映射器在构建用于从数据库中提取数据的 SQL 时会查找这些标志。
要为您的模型启用排序,请确保实现 SortableModelInterface
并使用 Traits\HasSortableModelTrait
特性。例如:
use pointybeard\Symphony\Classmapper; class Articles extends Classmapper\AbstractModel implements Classmapper\Interfaces\SortableModelInterface { use Classmapper\Traits\HasSortableModelTrait; ... }
FLAG_SORTBY
设置时,结果集将按此字段排序。注意,用于排序的具体表列是 value
,例如 [field].value 或如果设置了,则为 databaseFieldName
。
FLAG_SORTDESC 和 FLAG_SORTASC
表示排序方向;ASC 或 DESC。不能同时使用 FLAG_SORTASC
和 FLAG_SORTDESC
。默认为 FLAG_SORTASC
。
验证
这些标志在保存时应用。
FLAG_REQUIRED
表示该字段必须有一个非空值,否则保存将失败。请注意,FLAG_NULL
不是 FLAG_REQUIRED
的对立面。一个字段可能有空值,但是 FLAG_REQUIRED
将确保在允许您保存之前它有值。
保存时的验证
在保存条目时,您可以告诉类映射器您希望它有多严格。例如,$articles->save(self::FLAG_ON_SAVE_ENFORCE_MODIFIED)
。标志可以使用位或(|
)运算符组合(所有 FLAG_*
常量都是这样)。
以下标志受支持
FLAG_ON_SAVE_VALIDATE
在保存时,所有字段都将根据任何自定义字段 flags
映射进行验证。目前相关的唯一标志是 FLAG_REQUIRED
,它将确保字段有一个非空值。如果验证失败,将抛出 ModelValidationFailedException
异常。此标志默认启用。传递 NULL
、0
或另一个标志以防止在保存时验证。例如,$article->save(null)
FLAG_ON_SAVE_ENFORCE_MODIFIED
如果您尝试保存一个未修改的条目,这将触发一个 ModelHasNotBeenModifiedException
异常。请检查 hasBeenModified()
在获取时提供自定义 SQL
可能需要在类映射器加载对象时提供自定义 SQL。为此,重载 AbstractModel::fetchSQL()
方法。它应返回一个 SQL 字符串。您可以使用 self::$sectionFields
来轻松访问您部分中的字段 ID 值。例如:
... protected static function fetchSQL($where = 1) { return sprintf(' SELECT SQL_CALC_FOUND_ROWS t.entry_id as `id`, t.value as `title`, f.file as `file`, a.value as `available` FROM `tbl_entries_data_%d` AS `t` INNER JOIN `tbl_entries_data_%d` AS `f` ON f.entry_id = t.entry_id LEFT JOIN `tbl_entries_data_%d` AS `a` ON a.entry_id = f.entry_id WHERE %s ORDER BY t.entry_id ASC', self::$sectionFields['title'], self::$sectionFields['file'], self::$sectionFields['available'], $where ); } ...
请注意,重载 fetchSQL()
方法意味着您需要自己处理正确的字段映射、过滤和排序,而不是让 AbstractModel
为您处理。
在保存之前修改数据
有时您可能需要在将数据保存到条目之前即时更改数据。您可以通过重载 AbstractModel::getData()
方法来实现这一点。例如,您部分可能有一个“修改日期”字段。通过重载 getData
方法,您可以确保它自动更新。
... protected function getData() { $data = parent::getData(); // Check if anything has changed and, if so, set the new modified date if($this->hasBeenModified()) { $data['modified'] = 'now'; } return $data; } ...
过滤结果
类映射器提供了 fetchById()
和 all()
方法。然而,您很快就会需要一个更强大的方式来过滤结果。这就是 Filter 类发挥作用的地方。
要启用模型上的结果过滤,实现 FilterableModelInterface
接口并使用 HasFilterableModelTrait
特性。例如:
use pointybeard\Symphony\Classmapper; class Articles extends Classmapper\AbstractModel implements Classmapper\Interfaces\FilterableModelInterface { use Classmapper\Traits\HasFilterableModelTrait; ... }
这将为您提供访问 5 个新方法:fetch()
、filter()
、appendFilter()
、clearFilters()
和 getFilters()
,以及使用 5 个内置 Filter 类的接口:Basic
、FindInSet
、IsNotNull
、IsNull
和 Now
获取
过滤结果的最简单方法是调用 fetch()
。它期望获取扩展 AbstractFilter
的对象(请注意,在没有任何过滤器的情况下调用 fetch()
与调用 all()
相同)。
可以直接实例化 Filter 对象,例如 new Filter\Basic(...)
,但是,类映射器包含一个工厂类以使过程更一致。
以下是一个简单的示例
## Find all articles that are published and have a creation date less than now
$article->fetch(
Classmapper\FilterFactory::build('Basic', 'published', 'Yes'),
Classmapper\FilterFactory::build('Now', 'dateCreatedAt', Classmapper\Filters\Basic::COMPARISON_OPERATOR_LT),
);
调用 fetch()
的结果将是一个 SymphonyPDO ResultIterator
对象。可以使用 foreach
循环或使用自定义函数的 each()
方法来访问结果。例如:
Article::fetch(...)->each(function ($article) { // do something with $article here }); foreach(Article::fetch(...) as $article) { // do something with $article here }
每个 Filter 在实例化时都有略微不同的要求,但它们都将遵循以下顺序(方括号中的值可能是必需的或不必需的)
FILTER, FIELD_NAME, [VALUE], [TYPE], [COMPARISON], OPERATOR
您可以通过查看它们的构造函数来检查每个 Filter 类的具体要求。
FILTER
这是要使用的 Filter 类的名称。内置的 Filter 包括 Basic
、FindInSet
、IsNotNull
、IsNull
和 Now
。
FIELD_NAME
这是类映射器定义的部分中的字段名称。它将是您的字段映射中指定的 classMemberName
的值,或者是字段处理程序的 camelCase 版本。
TYPE
这是PDO预定义常量之一。[PDO预定义常量] 默认值为 PDO::PARAM_STR
。
比较
这是比较操作符,用于将 value
与 fieldName
中的值进行比较。这些操作符由 Filter\Basic
提供。
COMPARISON_OPERATOR_EQ // '=' COMPARISON_OPERATOR_NEQ // '!=' COMPARISON_OPERATOR_GT // '>' COMPARISON_OPERATOR_GTEQ // '>=' COMPARISON_OPERATOR_LT // '<' COMPARISON_OPERATOR_LTEQ // '<=' COMPARISON_OPERATOR_LIKE // 'LIKE' COMPARISON_OPERATOR_NOT_LIKE // 'NOT LIKE'
操作符
这告诉类映射器如何连接过滤器。此操作符应用于当前过滤器与上一个过滤器之间。可用选项为 OPERATOR_OR
和 OPERATOR_AND
。默认为 OPERATOR_AND
。
过滤器类
5个内置过滤器是 Basic
、FindInSet
、IsNotNull
、IsNull
和 Now
。
Filters\Basic
这适用于简单的 a 与 b 比较
类型比较。它提供了 =
、!=
、>
、>=
、<
、<=
、LIKE
和 NOT LIKE
操作符,这些操作符作为类常量提供(参见上方的 比较)。
Basic 在实例化时预期最多5个参数。
public function __construct( string $field, $value, int $type = \PDO::PARAM_STR, string $comparisonOperator = self::COMPARISON_OPERATOR_EQ, string $operator = self::OPERATOR_AND )
Filters\FindInSet
此过滤器期望获取一个值数组。它将检查字段值是否与提供的任何值相同。
Basic 在实例化时预期最多3个参数。
public function __construct( $field, array $values, string $operator = self::OPERATOR_AND )
Filters\IsNull 和 Filters\IsNotNull
这些过滤器将检查一个值是否为空或非空。
Basic 在实例化时预期2个参数。
public function __construct( $field, string $operator = self::OPERATOR_AND )
Filters\Now
此过滤器扩展了 Filters\Basic
,提供了访问SQL的 NOW()
功能。
Basic 在实例化时预期最多3个参数。
public function __construct( string $field, string $comparisonOperator = self::COMPARISON_OPERATOR_EQ, string $operator = self::OPERATOR_AND )
使用 filter()
您不需要在调用 fetch()
时动态提供过滤器,而是可以创建模型类的实例,然后使用 appendFilter()
添加过滤器。一旦构建了所需的过滤器集,就可以调用 filter()
来返回结果集。例如:
## Create an instance of the Article model $article = new Article; ## Append filters with appendFilter(). Note method chaining is supported $article ->appendFilter(Classmapper\FilterFactory::build('Basic', 'published', 'Yes')) ->appendFilter(Classmapper\FilterFactory::build('Now', 'dateCreatedAt', Classmapper\Filters\Basic::COMPARISON_OPERATOR_LT)) ; ## Returns the results $result = $article->filter(); ## Optionally clear the filters from this instance $article->clearFilters();
使用 appendFilter()
和 filter()
的主要好处是可以传递模型,允许代码的其他部分在最终调用 filter()
之前添加/删除过滤器。此外,结果在该实例中缓存,因此您可以多次调用 filter()
而不会影响性能。
要获取具有不同过滤器的结果,可以调用 clearFilters()
或创建模型的新实例。
支持
如果您认为您已发现一个错误,请使用 GitHub问题跟踪器 报告错误,或者更好的是,分支库并提交拉取请求。
贡献
我们鼓励您为此项目做出贡献。请查阅 贡献文档 了解如何参与。
许可证
"Symphony CMS: Section Class Mapper" 在 MIT许可证 下发布。