chocochaos/propel-rulable-behavior

允许在集中式类中定义业务规则,并将它们添加到您的模型和查询类中。

安装数: 8,760

依赖项: 0

建议者: 0

安全性: 0

星标: 0

关注者: 2

分支: 0

开放问题: 0

类型:propel-behavior

1.0 2018-05-12 14:36 UTC

This package is not auto-updated.

Last update: 2024-09-15 05:02:54 UTC


README

在编写现实世界的应用程序时,您常常需要处理复杂的业务规则。维护这些规则可能是繁琐且易出错的,尤其是当它们需要在多个地方进行检查时。在更改时,很容易遗漏一个规则的实现。

在Propel中,您已经可以在查询和对象类中添加自己的方法。即便如此,您仍然在多个地方定义相同的逻辑:在查询类中以规则过滤查询结果,以及在模型本身中检查特定对象是否满足某个规则。

此行为允许您在集中式类中定义业务规则。您的查询过滤和模型检查都可以定义在一个地方。在构建架构时,查询和模型类将添加方便的规则检查方法,从而实现更干净、更易于维护的代码。

安装

composer require chocochaos/propel-rulable-behavior

用法

定义规则

要定义规则,首先在模型目录中创建一个名为 Rules 的新目录。此目录应与由Propel生成的 BaseMap 目录处于同一级别。

Screenshot of the directory structure.

接下来,在此目录中创建一个用于规则的新类(当然要有正确的命名空间)。例如

<?php

namespace Chocochaos\SampleProject\Models\User\Rules;

use Chocochaos\SampleProject\Models\User\Map\UserGroupFunctionTableMap;
use Chocochaos\SampleProject\Models\User\UserGroupFunction;
use Chocochaos\SampleProject\Models\User\UserGroupFunctionQuery;
use Chocochaos\Rulable\RuleInterface;
use DateTime;
use LogicException;
use Propel\Runtime\ActiveQuery\BaseModelCriteria;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;

/**
 * Class UserGroupFunctionIsActive
 *
 * @package Chocochaos\SampleProject\Models\User\Rules
 */
class UserGroupFunctionIsActive implements RuleInterface
{
    /**
     * @param ActiveRecordInterface $object
     *
     * @return bool
     */
    public function objectMeetsRule(ActiveRecordInterface $object): bool
    {
        if ($object instanceof UserGroupFunction) {
            if (!($object->getStart() instanceof DateTime
                && $object->getStart() <= new DateTime())) {
                return false;
            }
            if ($object->getEnd() instanceof DateTime
                && $object->getEnd() < new DateTime()) {
                return false;
            }

            return true;
        }

        throw new LogicException(
            sprintf(
                'The rule %s can only be applied to objects of type %s.',
                static::class,
                UserGroupFunction::class
            )
        );
    }

    /**
     * @param BaseModelCriteria $query
     *
     * @return BaseModelCriteria
     */
    public function filterByMeetsRule(
        BaseModelCriteria $query
    ): BaseModelCriteria {
        if ($query instanceof UserGroupFunctionQuery) {
            return $query
                ->condition(
                    'has_start_date',
                    UserGroupFunctionTableMap::COL_START . ' IS NOT NULL'
                )
                ->condition(
                    'start_date_in_past',
                    UserGroupFunctionTableMap::COL_START . ' <= NOW()'
                )
                ->condition(
                    'has_no_end_date',
                    UserGroupFunctionTableMap::COL_END . ' IS NULL'
                )
                ->condition(
                    'end_date_in_future',
                    UserGroupFunctionTableMap::COL_END . ' >= NOW()'
                )
                ->combine(
                    ['has_start_date', 'start_date_in_past'],
                    Criteria::LOGICAL_AND,
                    'start_date_valid'
                )
                ->combine(
                    ['has_no_end_date', 'end_date_in_future'],
                    Criteria::LOGICAL_OR,
                    'end_date_valid'
                )
                ->where(
                    ['start_date_valid', 'end_date_valid'],
                    Criteria::LOGICAL_AND
                );
        }

        throw new LogicException(
            sprintf(
                'The rule %s can only be applied to queries of type %s.',
                static::class,
                UserGroupFunctionQuery::class
            )
        );
    }

    /**
     * @param BaseModelCriteria $query
     *
     * @return BaseModelCriteria
     */
    public function filterByFailsRule(
        BaseModelCriteria $query
    ): BaseModelCriteria {
        if ($query instanceof UserGroupFunctionQuery) {
            return $query
                ->condition(
                    'has_no_start_date',
                    UserGroupFunctionTableMap::COL_START . ' IS NULL'
                )
                ->condition(
                    'start_date_in_future',
                    UserGroupFunctionTableMap::COL_START . ' > NOW()'
                )
                ->condition(
                    'has_end_date',
                    UserGroupFunctionTableMap::COL_END . ' IS NOT NULL'
                )
                ->condition(
                    'end_date_in_past',
                    UserGroupFunctionTableMap::COL_END . ' < NOW()'
                )
                ->combine(
                    ['has_no_start_date', 'start_date_in_future'],
                    Criteria::LOGICAL_OR,
                    'start_date_invalid'
                )
                ->combine(
                    ['has_end_date', 'end_date_in_past'],
                    Criteria::LOGICAL_AND,
                    'end_date_invalid'
                )
                ->where(
                    ['start_date_invalid', 'end_date_invalid'],
                    Criteria::LOGICAL_OR
                );
        }

        throw new LogicException(
            sprintf(
                'The rule %s can only be applied to queries of type %s.',
                static::class,
                UserGroupFunctionQuery::class
            )
        );
    }
}

如你所见,你需要实现三个方法

  • objectMeetsRule:检查一个对象是否满足规则。
  • filterByMeetsRule:将过滤器应用于查询,只查找满足规则的项。
  • filterByFailsRule:将过滤器应用于查询,只查找不满足规则的项。

在上面的例子中,实现了 RuleInterface。这不是必需的,实际上在大多数情况下,我不建议这样做,因为不实现接口允许你应用适当的类型提示,而不是在方法中手动检查类型。上面的例子可以简化为

<?php

namespace Chocochaos\SampleProject\Models\User\Rules;

use Chocochaos\SampleProject\Models\User\Map\UserGroupFunctionTableMap;
use Chocochaos\SampleProject\Models\User\UserGroupFunction;
use Chocochaos\SampleProject\Models\User\UserGroupFunctionQuery;
use DateTime;
use Propel\Runtime\ActiveQuery\Criteria;

/**
 * Class UserGroupFunctionIsActive
 *
 * @package Chocochaos\SampleProject\Models\User\Rules
 */
class UserGroupFunctionIsActive
{
    /**
     * @param UserGroupFunction $userGroupFunction
     *
     * @return bool
     */
    public function objectMeetsRule(UserGroupFunction $userGroupFunction): bool
    {
        if (!($userGroupFunction->getStart() instanceof DateTime
            && $userGroupFunction->getStart() <= new DateTime())) {
            return false;
        }
        if ($userGroupFunction->getEnd() instanceof DateTime
            && $userGroupFunction->getEnd() < new DateTime()) {
            return false;
        }

        return true;
    }

    /**
     * @param UserGroupFunctionQuery $query
     *
     * @return UserGroupFunctionQuery
     */
    public function filterByMeetsRule(
        UserGroupFunctionQuery $query
    ): UserGroupFunctionQuery {
        return $query
            ->condition(
                'has_start_date',
                UserGroupFunctionTableMap::COL_START . ' IS NOT NULL'
            )
            ->condition(
                'start_date_in_past',
                UserGroupFunctionTableMap::COL_START . ' <= NOW()'
            )
            ->condition(
                'has_no_end_date',
                UserGroupFunctionTableMap::COL_END . ' IS NULL'
            )
            ->condition(
                'end_date_in_future',
                UserGroupFunctionTableMap::COL_END . ' >= NOW()'
            )
            ->combine(
                ['has_start_date', 'start_date_in_past'],
                Criteria::LOGICAL_AND,
                'start_date_valid'
            )
            ->combine(
                ['has_no_end_date', 'end_date_in_future'],
                Criteria::LOGICAL_OR,
                'end_date_valid'
            )
            ->where(
                ['start_date_valid', 'end_date_valid'],
                Criteria::LOGICAL_AND
            );
    }

    /**
     * @param UserGroupFunctionQuery $query
     *
     * @return UserGroupFunctionQuery
     */
    public function filterByFailsRule(
        UserGroupFunctionQuery $query
    ): UserGroupFunctionQuery {
        return $query
            ->condition(
                'has_no_start_date',
                UserGroupFunctionTableMap::COL_START . ' IS NULL'
            )
            ->condition(
                'start_date_in_future',
                UserGroupFunctionTableMap::COL_START . ' > NOW()'
            )
            ->condition(
                'has_end_date',
                UserGroupFunctionTableMap::COL_END . ' IS NOT NULL'
            )
            ->condition(
                'end_date_in_past',
                UserGroupFunctionTableMap::COL_END . ' < NOW()'
            )
            ->combine(
                ['has_no_start_date', 'start_date_in_future'],
                Criteria::LOGICAL_OR,
                'start_date_invalid'
            )
            ->combine(
                ['has_end_date', 'end_date_in_past'],
                Criteria::LOGICAL_AND,
                'end_date_invalid'
            )
            ->where(
                ['start_date_invalid', 'end_date_invalid'],
                Criteria::LOGICAL_OR
            );
    }
}

将行为添加到架构并构建

最后,将可规则化行为添加到架构中,并设置正确的参数

<table name="user_group_function">
    <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true" required="true" />
    <column name="user_id" type="INTEGER" required="true" />
    <column name="group_function_id" type="INTEGER" />
    <column name="start" type="TIMESTAMP" required="true" />
    <column name="end" type="TIMESTAMP" />
    <behavior name="timestampable"/>
    <foreign-key foreignTable="group_function" onDelete="CASCADE" onUpdate="CASCADE">
        <reference local="group_function_id" foreign="id" />
    </foreign-key>
    <foreign-key foreignTable="user" onDelete="CASCADE" onUpdate="CASCADE">
        <reference local="user_id" foreign="id" />
    </foreign-key>
    <behavior name="rulable">
        <parameter name="Active" value="UserGroupFunctionIsActive" />
    </behavior>
</table>
  • name 参数定义了规则的 PHPName,此名称将用于方法生成。
  • value 参数定义了要使用的规则类。
  • 在添加或更新行为后,别忘了构建您的架构。

使用生成的方法

在上面的例子中,Active 被作为规则的 PHPName 提供。以下方法现在将可用

  • UserGroupFunction->isActive()
  • UserGroupFunctionQuery->filterByIsActive()
  • UserGroupFunctionQuery->filterByIsNotActive()

其他选项

上面尚未涵盖一些内容

  • 您可以为模型添加多个参数以添加多个规则。
  • 您可以在XML中指定一个简短类名而不是指定多个规则,也可以指定一个完全限定类名(包括命名空间)。这消除了将规则放在与模型相同的 `Rules` 目录中的要求,这在编写适用于多个实体的规则时可能很有用。
  • 您的规则类可以有一个构造函数来传递额外的参数。这些参数随后将作为生成函数的参数可用,包括类型提示和默认值。然而,目前还不支持可变参数和常量作为默认值。

许可证

请参阅许可证文件