我应该如何处理Doctrine 2中与BusinessLogic相关的数据定义(如状态类型)?

sie*_*iej 7 php symfony doctrine-orm

假设我有一个Booking实体,它有一个state可以设置为几个值的一个领域-让我们把它:NEW,ACCEPTED,和REJECTED

我正在寻找实现这一目标的"正确"方式.到目前为止,我使用了这样的方法:

class Booking
{
    const STATUS_NEW = 0;
    const STATUS_ACCEPTED = 1;
    const STATUS_REJECTED = 2;

    protected $status = self::STATUS_ACTIVE;
}
Run Code Online (Sandbox Code Playgroud)

它工作正常,但我真的很好奇"正确"的做法,我也有这个方法的一些问题:

  1. 它看起来非常像隐藏在实体类中的业务逻辑 - 如果实体应该是a POJO,那为什么它会关心状态呢?所以我可以把它放在一个经理类中:

    class BookingManager
    {
        const STATUS_NEW = 0;
        const STATUS_ACCEPTED = 1;
        const STATUS_REJECTED = 2;
    
        public function setBookingStatus(Booking $b, $status) { }
    
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但它仍然无法解决第二个问题:

  2. 很难在视图中重复使用这些数据,让我们以一个树枝为例 - 我必须创建一个Twig Extension才能将数字转换为实际名称:

    Status type: {{ booking.status }}
    Status name: {{ booking.status|statusName }}{# I don't like this approach #} 
    Status name: {{ booking.getStatusName() }}  {# This seems even worse #}
    
    Run Code Online (Sandbox Code Playgroud)

    所以我可以添加getStatusName方法,BookingManager但我相信它不属于那里.我可以将相同的方法添加到Booking类中,它可以正常工作,但是再次 - 业务逻辑AND现在也将表示逻辑隐藏在实体中.

  3. 如果我的某些代码依赖于MyVendor\MyBundle\Entity\Booking::STATUS_NEW那么在单元测试代码时它会成为一个问题吗?使用静态方法调用问题很明显 - 我不能模拟依赖.是否有任何使用情况,依赖于静态常量可能是一个问题?

我能想到的就是将所有这些逻辑移动到服务层并创建一个服务BookingStatusManager(让我们忽略它可能被错误地用于EntityManager子类的事实) - 它可能看起来像这样:

class BookingStatusManager
{
    const STATUS_NEW = 0;
    const STATUS_ACCEPTED = 1;
    const STATUS_REJECTED = 2;

    public function getStatusName($code) { ... }

}
Run Code Online (Sandbox Code Playgroud)

但现在我需要enum为每个实体的每个属性添加一个额外的类,这是一个痛苦,它似乎仍然是正确的 - 我需要在我想要处理Booking的状态时从这个类引用静态值.

请注意,这是一个一般性问题,而不是一个特定的Booking示例 - 它可以是例如具有可能值为或的字段的 BorderLength实体; 此外,状态转换不限于用户引起的状态转换,必须可以从代码中执行这些转换.lengthUnitMILESKILOMETERS

您的经验是"正确的方法"来解决这个问题?

Tom*_*ess 5

我建议你摆脱常数,只需在你的Booking实体上用一个新BookingStatus实体创建一个多对一的关联 .为什么?好吧有很多原因:

  • 如果您想添加新的预订状态,则不需要编辑代码.这不仅对开发人员来说更容易,而且还允许动态创建状态的可能性.
  • 您可以轻松地在新BookingStatus实体中存储有关状态的其他信息,例如名称.也可以在不改变代码的情况下更新此信息.
  • 允许外部工具了解不同的状态.例如,您可能希望直接在数据库上使用外部报告工具.它不会知道某些整数的意思,但它能够理解多对一的关联.


Mar*_*Lie 3

您的编号问题的答案

1:为什么你的业务模型/对象中没有业务逻辑

毕竟,耦合数据和行为是面向对象的目的之一?我相信您可能误解了“ POJO ”概念(我不是在谈论代表 Java 的 J ;)),其目的是不让框架侵入您的模型,从而将模型限制在特定于框架的上下文中,并且使得单元测试或在任何其他环境中的重用变得困难。

您所说的听起来更像是DTO,这通常不是您的模型应包含的内容。

2:是的,Twig 不太擅长操纵象征意义的数字。您可能会得到基于存储优化(字节数)或数据库流量/查询时间等的各种建议,但对于大多数项目,我更喜欢优先考虑人类体验 - 即除非需要,否则不要针对计算机进行优化。

因此,我个人的偏好(在大多数情况下)是立即开发可读的“枚举”字段,但采用类似键的符号而不是常规单词。例如,"status.accepted"1or相对"Accepted"。类似键的符号非常适合 i18n,使用 twig 的|trans过滤器、{% trans %}标签或类似的东西。

3 :在对模型本身进行单元测试时,模型中的静态“枚举”引用很少会出现问题。

在某些时候,您的模型需要通过使用可用的构建块来定义其语义。虽然能够抽象出实现(尤其是服务)很有用,但能够抽象出意义却很少(从来没有?)富有成果。这让我想起了这个故事。别去那里。:-D

如果您仍然担心它,请将常量放入模型类实现的接口中;那么你的测试只能引用接口。

 

建议的解决方案

型号,替代方案 1:

class Booking {
    const STATUS_NEW      = 'status.new';
    const STATUS_ACCEPTED = 'status.accepted';
    const STATUS_REJECTED = 'status.rejected';

    protected $status = self::STATUS_NEW;
}
Run Code Online (Sandbox Code Playgroud)

型号,替代方案 2:

interface BookingInterface {
    const STATUS_NEW      = 'status.new';
    const STATUS_ACCEPTED = 'status.accepted';
    const STATUS_REJECTED = 'status.rejected';

    // ...and possibly methods that you want to expose in the interface.
}
class Booking implements BookingInterface {
    protected $status = self::STATUS_NEW;
}
Run Code Online (Sandbox Code Playgroud)

枝条:

Status name: {{ ("booking."~booking.status)|trans({}, 'mybundle') }}
Run Code Online (Sandbox Code Playgroud)

(当然,booking.前缀是可选的,取决于您想要构建 i18n 密钥和文件的方式。)

资源/翻译/mybundle.en.yml:

booking.status.new:      New
booking.status.accepted: Accepted
booking.status.rejected: Rejected
Run Code Online (Sandbox Code Playgroud)

 

常量作为实体

关于Tomdarkness将这些常量转变为自己的模型类的建议,我想强调这应该是业务/领域决策,而不是技术偏好问题。

如果您清楚地预见动态添加状态(由系统用户提供)的用例,那么无论如何,新的模型/实体类都是正确的选择。但是,如果状态用于应用程序中的内部状态,它与您正在编写的实际代码耦合(因此在代码也更改之前不会更改),那么您最好使用常量。

常量作为实体使得使用它们变得更加困难(“嗯,我如何再次获得‘接受’的主键?”),不那么容易国际化(“嗯,我是否将可能的区域设置存储为硬BookingStatus 实体上的编码属性,还是我要创建另一个 BookingStatusI18nStrings(id, locale, value) 实体?”),再加上您自己提出的重构问题。简而言之:不要过度设计——祝你好运。;-)