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)
它工作正常,但我真的很好奇"正确"的做法,我也有这个方法的一些问题:
它看起来非常像隐藏在实体类中的业务逻辑 - 如果实体应该是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)
但它仍然无法解决第二个问题:
很难在视图中重复使用这些数据,让我们以一个树枝为例 - 我必须创建一个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现在也将表示逻辑隐藏在实体中.
如果我的某些代码依赖于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
您的经验是"正确的方法"来解决这个问题?
我建议你摆脱常数,只需在你的Booking实体上用一个新BookingStatus实体创建一个多对一的关联 .为什么?好吧有很多原因:
BookingStatus实体中存储有关状态的其他信息,例如名称.也可以在不改变代码的情况下更新此信息.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) 实体?”),再加上您自己提出的重构问题。简而言之:不要过度设计——祝你好运。;-)