Symfony 3流体实体关系

时间:2018-01-09 13:44:59

标签: symfony entity entity-relationship

我正在尝试在自定义项目管理应用程序中实现时间跟踪机制。

此应用包含多个实体(门票,项目,维基页面,冲刺,......)

我希望我的时间跟踪能够成为“通用”,因为我希望用户能够在故障单,项目,维基页面......以及任何实体上记录时间。

现在,我试图找出用于我的TimeLog实体的数据库模式(关系)。

理论上我可以在我的应用程序中为每个实体创建一个关系,但这需要我在以后引入新实体时继续更新模式。

是否每个人都实施过这样的事情?

欢迎所有建议。

非常感谢提前。

1 个答案:

答案 0 :(得分:0)

我在尝试添加评论,喜欢和其他类型的元素时,在我的应用中遇到了类似的情况,这些元素的行为并不真正取决于它们所附加的实体。

我最终选择的解决方案是在我的引用实体(例如Comment)中有两个字段来保存被引用实体的id及其类型。由于我多次使用它,我将属性放入以下特征:

namespace AppBundle\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;

trait EntityReferenceTrait
{
    /**
     * @ORM\Column(name="reference_id", type="integer")
     */
    private $referenceId;

    /**
     * @ORM\Column(name="reference_type", type="integer")
     */
    private $referenceType;

    /* ... setters & getters ... */
}

然后我可以在持有这种引用的实体中使用它:

/**
 * @ORM\Table(name="comments", indexes={@ORM\Index(name="references", columns={"reference_id", "reference_type"})})
 * @ORM\Entity(repositoryClass="AppBundle\Repository\Comment\CommentRepository")
 */
class Comment
{
    /* ... other traits ... */
    use \AppBundle\Entity\Traits\EntityReferenceTrait;

    /* ... other fields & methods ... */
}

注意:我为引用添加了一个索引,但整个过程没有必要正常工作。如果你使用这样的索引,如果你想从中受益,请注意WHERE子句的顺序

为了提高性能并根据所引用实体的类型添加其他配置,我直接在我的应用程序的配置中处理了设置。因此,我有类似的东西:

commentables:
    news:
        classname: AppBundle\Entity\News\News
        type_id: 1
        browse_route: news_comments
        multiple_locales: false

    ...

这使我能够准确了解我的Comment实体可以引用的实体类型。它还允许我自动将特定侦听器挂钩到被引用的实体,以便删除引用的实体触发例如删除相关注释。我通过处理AppBundle/DependencyInjection/AppExtension.php中的配置(更多关于此here)并将所需的侦听器列表保存到参数中来完成此操作。然后,通过向loadClassMetadata事件添加侦听器,我可以有效地处理相关实体的删除,例如。

这是通过在ClassMetadata实例上使用addEntityListener来挂接侦听器的特定生命周期事件的侦听器的侦听器:

namespace AppBundle\Listener;

use Doctrine\ORM\Event\LoadClassMetadataEventArgs;

class MappingListener
{
    private $entityListenersMapping = [];

    /**
     * @param array $mappingConfig Associative array with keys being listeners classnames and values being arrays associating an event to a method name
     */
    public function __construct(array $mappingConfig)
    {
        $this->entityListenersMapping = $mappingConfig;
    }

    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
    {
        $classMetadata = $eventArgs->getClassMetadata();
        if(!array_key_exists($classMetadata->name, $this->entityListenersMapping))
        {
            return;
        }

        // Hook the entity listeners in the class metadata
        foreach($this->entityListenersMapping[$classMetadata->name] as $listenerClassName => $eventsCallbacks)
        {
            foreach($eventsCallbacks as $event => $methodName)
            {
                $classMetadata->addEntityListener($event, $listenerClassName, $methodName);
            }
        }
    }
}

无论哪种方式,对于这部分,它主要取决于您的实体的特定需求,但我想这些“软”外键通过preRemove和{模仿ON DELETE CASCADE行为是非常普遍的需要。 {1}}事件。

考虑到这些引用和拥有它们的实体的处理,我还创建了一个postRemove来轻松创建管理这些实体的服务,以便与其交互的其他组件不必担心底层配置。

这些管理者的大多数公共方法的界面通常需要:

  • 被引用实体的类名
  • 被引用的实体的数字ID

使用我的经理服务中检索到的这两个信息和配置,我可以轻松地与数据库进行交互,即使在我的情况下,它将配置中定义的整数存储为引用类型而不是实体的类名被提及。

基于此,我可以为我的任何app实体启用评论,赞,投票,订阅等(只要它的主键是一个整数),我的配置文件中只有几行。无需更新数据库模式,并且需要使用正确的生命周期事件侦听器,而不必担心数据库中的孤立条目。

在旁注中,应该提到的是,您将无法从反面检索引用实体,因为它不是真正的关联。您也不会受益于外键行为。因此,即使您通过侦听删除事件来模拟EntityRefererManagerTrait行为,如果通过DQL直接执行某些ON DELETE CASCADE操作,您也无法确保数据库中没有孤儿。