chocochaos / propel-rulable-behavior
允许在集中式类中定义业务规则,并将它们添加到您的模型和查询类中。
1.0
2018-05-12 14:36 UTC
Requires
- php: >=7.1.0
- propel/propel: @dev
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生成的 Base
和 Map
目录处于同一级别。
接下来,在此目录中创建一个用于规则的新类(当然要有正确的命名空间)。例如
<?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` 目录中的要求,这在编写适用于多个实体的规则时可能很有用。
- 您的规则类可以有一个构造函数来传递额外的参数。这些参数随后将作为生成函数的参数可用,包括类型提示和默认值。然而,目前还不支持可变参数和常量作为默认值。
许可证
请参阅许可证文件。