pointybeard/symphony-classmapper

将部分映射到自定义模型类中,简化了在 Symphony CMS 中创建、修改、删除和检索条目的过程。

2.0.0.12 2021-10-27 00:50 UTC

README

Latest Stable Version License

将部分映射到自定义模型类中,简化了在 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_INTFLAG_STRFLAG_FLOAT

这些标志用于对提取的数据进行类型转换,并直接映射到原生 PHP 方法 intval()floatval()strval()。它们可以与 FLAG_ARRAY 组合,在这种情况下,数组中的所有项都将转换为该类型。

FLAG_CURRENCY

FLAG_FLOAT 类似,但将结果限制为两位小数。

FLAG_BOOL

将数据库中的数据从 Yes|No 字符串值转换为 true|false。在保存时,将其转换回 Yes|No 字符串值。可以与 FLAG_ARRAY 组合

行为

FLAG_ARRAY

当字段有多个数据行时,如多选,请使用此标志。返回的数据将是一个包含值的数组。可以与 FLAG_INTFLAG_STRFLAG_FLOATFLAG_BOOLFLAG_CURRENCYFLAG_NULL 组合

FLAG_FILE

将字段值设置为包含 filesizemimetypemeta 的数组。注意,在保存时仅使用 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_SORTDESCFLAG_SORTASC

表示排序方向;ASC 或 DESC。不能同时使用 FLAG_SORTASCFLAG_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 异常。此标志默认启用。传递 NULL0 或另一个标志以防止在保存时验证。例如,$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 类的接口:BasicFindInSetIsNotNullIsNullNow

获取

过滤结果的最简单方法是调用 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 包括 BasicFindInSetIsNotNullIsNullNow

FIELD_NAME

这是类映射器定义的部分中的字段名称。它将是您的字段映射中指定的 classMemberName 的值,或者是字段处理程序的 camelCase 版本。

TYPE

这是PDO预定义常量之一。[PDO预定义常量] 默认值为 PDO::PARAM_STR

比较

这是比较操作符,用于将 valuefieldName 中的值进行比较。这些操作符由 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_OROPERATOR_AND。默认为 OPERATOR_AND

过滤器类

5个内置过滤器是 BasicFindInSetIsNotNullIsNullNow

Filters\Basic

这适用于简单的 a 与 b 比较 类型比较。它提供了 =!=>>=<<=LIKENOT 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\IsNullFilters\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许可证 下发布。