假设我有一个Booking实体,并且它有一个state字段,可以将其设置为几个值之一-让我们使其成为:NEWACCEPTEDREJECTED
我正在寻找实现此目标的“正确”方法。到目前为止,我使用了这样的方法:

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

    protected $status = self::STATUS_ACTIVE;
}

它工作正常,但我真的很好奇“正确”的做法,而且我对这种方法有一些问题:
  • 它看起来非常像隐藏在实体类中的业务逻辑——如果实体应该是 POJO ,那么它为什么要关心状态可能是什么?所以我可以把它放在一个管理器类中,比如:
    class BookingManager
    {
        const STATUS_NEW = 0;
        const STATUS_ACCEPTED = 1;
        const STATUS_REJECTED = 2;
    
        public function setBookingStatus(Booking $b, $status) { }
    
    }
    

    但它仍然不能解决第二个问题:
  • 很难在 View 中重用这些数据,让我们以 Twig 为例 - 我必须创建一个 Twig 扩展才能将数字转换为实际名称:
    Status type: {{ booking.status }}
    Status name: {{ booking.status|statusName }}{# I don't like this approach #}
    Status name: {{ booking.getStatusName() }}  {# This seems even worse #}
    

    所以我可以将 getStatusName 方法添加到 BookingManager 中,但我相信它不属于那里。我可以将相同的方法添加到 Booking 类,它会工作得很好,但话说回来 - 业务逻辑现在也隐藏在实体中。
  • 如果我的某些代码依赖于 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) { ... }
    
    }
    

    但是现在我需要为每个实体的每个 enum 类属性添加一个额外的类,这很痛苦,而且似乎仍然不正确-每当我想处理 Booking 的状态时,我都需要引用此类中的静态值。

    请注意,这是一个一般问题,而不是特定于所呈现的 Booking 示例的问题 - 例如,它可能是 BorderLength 实体,其 lengthUnit 字段可能值为 MILESKILOMETERS ;此外,状态转换不限于由用户引起的状态转换,必须可以从代码中执行这些转换。

    根据您的经验,解决此问题的“正确方法”是什么?

    最佳答案

    对您编号问题的回答

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

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

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

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

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

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

    在某些时候,您的模型无论如何都需要通过使用可用的构建块来定义其语义。虽然能够抽象出实现(尤其是服务)是有用的,但是能够抽象出意义很少(永远?)富有成效。这让我想起了 this 的故事。不要去那里。 :-D

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

    建议的解决方案

    型号,备选方案 1:

    class Booking {
        const STATUS_NEW      = 'status.new';
        const STATUS_ACCEPTED = 'status.accepted';
        const STATUS_REJECTED = 'status.rejected';
    
        protected $status = self::STATUS_NEW;
    }
    

    模型,备选方案 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;
    }
    

    枝条:
    Status name: {{ ("booking."~booking.status)|trans({}, 'mybundle') }}
    

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

    资源/翻译/mybundle.en.yml:
    booking.status.new:      New
    booking.status.accepted: Accepted
    booking.status.rejected: Rejected
    

    常量作为实体

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

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

    作为实体的常量使得使用它们变得更加困难(“嗯,我如何再次获得 'accepted' 的主键?”),不是那么容易国际化(“嗯,我是否将可能的语言环境存储为难BookingStatus 实体上的 -coded 属性,或者我是否创建另一个 BookingStatusI18nStrings(id, locale, value) 实体?”),以及您自己提出的重构问题。简而言之:不要过度设计 - 祝你好运。 ;-)

    关于php - 我应该如何处理 Doctrine 2 中与 BusinessLogic 相关的数据定义(如状态类型)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20133638/

    10-14 15:05