这是交易。使用Doctrine ORM for PHP和我需要将模型从实体持久层"解耦"。假设我们有UserEntity,它为数据库映射保存了所有漂亮的东西,如:注释、属性、setters/getters等等。另一方面,我希望有一个单独的User类,它只包含与业务相关的逻辑,例如:User::getFullName()。此外,我希望User扩展UserEntity,以便User继承所有的访问方法。
我检查过的可能的解决方案对我不起作用:
- 仅仅从实体扩展模型,然后在DQL中指定模型是行不通的
- makeUserEntity/**@MappedSuperclass*/不起作用,因为在这种情况下,UserEntity"本身不是一个实体"
- InheritanceType/DiscriminatorColumn/DiscrimentorMap不能正常工作,原因是模型不是实体
有什么想法吗?
明白了!(>=PHP 5.4解决方案)
简而言之:使实体成为一种特质,在模型类中使用实体特质。签出此文档页:http://doctrine-orm.readthedocs.org/en/latest/tutorials/override-field-association-mappings-in-subclasses.html.
示例:假设我们有用户模型。首次创建用户实体:
/**
* @ORM'Table(name="user")
*/
trait UserEntity {
/**
* @var integer
*
* @ORM'Column(name="id", type="integer", nullable=false)
* @ORM'Id
* @ORM'GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @var string
*
* @ORM'Column(name="first_name", type="string", length=100, nullable=true)
*/
protected $firstName;
/**
* @var string
*
* @ORM'Column(name="last_name", type="string", length=100, nullable=true)
*/
protected $lastName;
// other mapping here...
}
然后创建一个模型并在其中使用一个实体特征:
/**
* @ORM'Entity
*/
class User {
use UserEntity;
public function getFullName() {
return $this->firstName . ' ' . $this->lastName;
}
}
用户模型中的Mind@Entity注释。这是直接使用模型所必需的在实体管理器中。
现在假设我们需要一个Admin模型来扩展User one。这有点棘手。我们必须在User中将@Entity更改为@MappedSuperclass,以便对其进行扩展。然后创建Admin模型,将其声明为@Entity,并在其上重新声明表名使用@Table注释(否则,由于某种原因,Doctrine将混淆从哪个表中获取)。看起来像这样:
/**
* @ORM'MappedSuperclass
*/
class User {
use UserEntity;
public function getFullName() {
return $this->firstName . ' ' . $this->lastName;
}
}
/**
* @ORM'Entity
* @ORM'Table(name="user")
*/
class Admin extends User {
public function getFullName() {
return parent::getFullName() . ' (admin)';
}
}
这样,用户和管理模型(=实体)都可以在条令中使用。
不,我们可以对实体做我们通常会做的事情:通过实体管理器查找(),直接在查询中使用模型,等等。
嘿,孩子们想买一些魔法吗
您可以在PHP中使用魔术方法来转发所有的访问器方法调用,例如:
class UserModel
{
protected $userEntity;
public function __construct($entity)
{
$this->userEntity = $entity;
}
public function __call($name, $arguments)
{
if (!method_exists($this, $name) AND method_exists($this->userEntity, $name)) {
return call_user_func_array(array($this, $name), $arguments);
}
}
}
您可以定义自己的本地EntityRepository类,该类扩展了Doctrine'ORM'EntityRepository
。通过这种方式,您可以确保find
、findBy
和findOneBy
方法将创建Model的新实例,在Model中设置$entity对象,并返回新的Model实例而不是entity。
想要与条令结果输出进行更干净的集成吗
您可以编写一个扩展实体类的模型类(无需将其注册到Doctrine2),并编写自己的自定义Hydrator类(并将其注册在Doctrine2中),以确保您的结果将作为model类的数组而不是entity类的数组返回。
想用Symfony的方式吗
您可以在实体存储库类中定义所有这些"与数据库相关的业务逻辑",也可以在服务类中定义这些"与非数据库相关的商业逻辑"(如果愿意,您可以创建专用于一个实体的服务)。然后使用数据库逻辑参数(实体)调用业务逻辑层(服务、存储库)。
另一方面,Symfony方式非常灵活,因为您可以在预先建立的Symfony结构之外构建任何功能,只要它们是PSR即可。
关于自定义水合器。简言之,这个解决方案对我来说也不是一个选择(不过我可能会错过任何东西)。这里有两个陷阱:
- 从原始数据库数据重新映射到自定义hydrator中的模型对象并不方便。问题是db表中的字段是"单个单词"(如:firstname/lastname),而在对象中,我手动将它们修改为驼色大小写(为了可读性)。这当然可以通过从实体的注释中读取元信息来解决,但这将涉及魔术和额外的时间资源。所以这个解决方案会很"脏"
- 如何在例如"查找"功能中使用自定义水合器?比如:$em->find('My''Model''User',1)?因为我希望我的模型在每个实体使用的地方都能百分之百地使用
因此,米哈伊·斯坦库提出的任何解决方案都不适用于我
- 模型类内部的魔术:不是一个干净的解决方案,因为它涉及将实体传递给构造函数,并且不会给您类型提示(魔术__call的原因)
- 自定义hydrator:看起来更干净,但不能在所有情况下都使用,从数据库原始结果到模型对象的重新映射很复杂(=耗时)
- Symfony的服务方式:也不是干净的解决方案,因为我需要100%分离模型逻辑和数据库相关逻辑(所以我的模型中没有一个映射信息/注释,只有一个纯业务逻辑)
好吧,我最近一直在问自己这个问题,我得到的简单答案是将实体中的所有节点移到yaml或xml中,以清理我的"模型"/实体。
然后,我的用户类中有我所有的属性,但所有的持久性配置都被移走了,然后你也可以对getter和setter做任何你想做的事情(在我看来,尽可能多地删除)。