假设我有一个Booking
实体,并且它有一个state
字段,可以将其设置为几个值之一-让我们使其成为:NEW
,ACCEPTED
和REJECTED
我正在寻找实现此目标的“正确”方法。到目前为止,我使用了这样的方法:
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) { }
}
但它仍然不能解决第二个问题:
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
字段可能值为 MILES
或 KILOMETERS
;此外,状态转换不限于由用户引起的状态转换,必须可以从代码中执行这些转换。根据您的经验,解决此问题的“正确方法”是什么?
最佳答案
对您编号问题的回答
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/