addiks/symfony_rdm

帮助在 symfony/doctrine 应用程序中使用领域驱动设计和丰富的领域模型。


README

Build Status Build Status Scrutinizer Code Quality Code Coverage

是什么

本项目旨在丰富 doctrine2-ORM 映射能力,使得实体不再需要专门为 doctrine 开发,而是可以借助 doctrine 将任何对象映射到数据库,即使它并非为此目的而开发。它提供了许多将数据从数据库映射到对象(及反向)的新方法。

当前实现的功能包括以下:

这些可以与任何其他组合使用,从而提供非常动态的 ORM 映射能力。

如何

它挂钩到 doctrine 的事件并使用描述的值填充标记的字段。有几种方式可以定义哪些映射应该放在服务的哪些字段中:通过注解、YAML、XML、PHP 或静态 PHP。YAML 映射尚未完全实现,可能很快会被删除。

我建议您使用 XML(或 YAML)映射,因为实体应该是框架无关的。我本人更喜欢 XML,因为至少 XML 有模式,而 YAML 通常需要猜测允许哪些键、所有键的含义以及谁在使用它们。有关更多详细信息,请参阅上述链接文档。

设置

要启用此功能,首先使用以下命令通过 composer(symfony 通常自带 composer)安装项目:composer require addiks/symfony_rdm

然后 在您的 symfony 应用程序中注册该捆绑包。在 symfony-4.0 之前,这是在 "app/AppKernel.php" 文件中的 "registerBundles" 方法内完成的。从 4.0 开始,这是在 "config/bundles.php" 文件中完成的。(如果您知道如何自动化此过程,请告知我。)

Symfony 2.x & 3.x

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
        # ...
        new Addiks\RDMBundle\AddiksRDMBundle(), # <== Add this line
        new AppBundle\AppBundle(),
    );

    …

    return $bundles;
}

Symfony >= 4.0

// config/bundles.php
return [
    // 'all' means that the bundle is enabled for any Symfony environment
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    …
    Addiks\RDMBundle\AddiksRDMBundle::class => ['all' => true], # <== Add this line

];

之后,此捆绑包应该可以工作。如果不行,请在此处创建一个问题,并尽可能提供有关使用环境的详细信息,我可能能够帮助。

两种获取数据的方式:安全或快速

使用此扩展扩展 doctrine 实体的 ORM 映射可能会(根据 ORM 映射配置)引入 doctrine 通常不知道的新数据库列。在加载或存储这些附加列的数据时,有两种方式与之交互

安全但慢的方法

处理这些额外数据库列的安全但缓慢的方法是在Doctrine之外逐个加载它们,每次实体被实体化时,当Doctrine实体管理器刷新时,将所有已修改的实体存储(更新/插入)到数据库中。这种方法比快速方法更安全,因为所有操作都在Doctrine的作用域之外,并且Doctrine或其他Doctrine插件无法干扰加载和存储过程的功能。同时,这种方法比快速方法慢得多——有时甚至慢得多——因为现在我们可能需要为每个实体化实体执行一个额外的选择语句。这可能会对性能产生严重影响!尽管如此,这种方法是默认方法,因为虽然速度较慢,但更安全。

快速但不稳定的方法

在Doctrine之外通过额外的选择语句加载和存储所有数据到和从数据库的替代方案是让Doctrine通过它自己的机制为我们加载和存储这些数据。这种方式性能(几乎)与这些列是原生Doctrine列时的性能相同,不需要执行额外的选择、更新或插入语句。这个解决方案的问题在于,为了使其工作,我们需要让Doctrine了解所有这些额外的数据库列,这样Doctrine就可以为我们处理它们,而无需Doctrine在它自己的实体化过程中实际使用这些数据。这些是数据库列,但不是实体字段。如果Doctrine试图将其映射到实体字段,它将会失败,因为这些字段没有对应实体字段。为了避免这种情况,这种方法深入到Doctrine自己的反射机制中,并伪造这些实体字段以供Doctrine使用。从Doctrine的角度来看,即使它们是伪造的,这些实体字段实际上也存在。这种深入到Doctrine内部挂钩的结构对Doctrine的内部结构做了很多假设。如果其中任何一个假设失败(因为Doctrine在新的版本中更改了它们,或者另一个扩展更改了它们),那么这个扩展可能无法正常工作!由于这种不稳定/不确定性,这种方法不是默认的,而是可选的。要使用此方法,您必须定义一个名为addiks_rdm.data_loader.stability的symfony服务参数,并将其设置为fast-and-unstable

app/config/config.yml:

parameters:
    addiks_rdm.data_loader.stability: 'fast-and-unstable'

之后,您应该在入口脚本(index.phpbin/consoleapp.phpapp_dev.php等)中调用一个名为symfony_rdm_composer_hook的设置函数,在包含composers的vendor/autoload.php之后,以及symfony内核创建之后。composers的vendor/autoload.php文件返回类加载器对象,这应该与symfony内核一起传递给提到的函数。

示例

# ...
use Composer\Autoload\ClassLoader;
use function Addiks\SymfonyRDM\symfony_rdm_composer_hook;
# ...
/** @var ClassLoader $loader */
$loader = require('/vendor/autoload.php');
# ...
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
# ...
symfony_rdm_composer_hook($loader, $kernel);
# ...

这个函数symfony_rdm_composer_hook在项目的根目录下的一个名为composer_hook.php的文件中声明。在正常情况下,此文件应该始终由composer自动包含。如果没有,您可能会收到一个错误消息,指出该函数是未知的。在这种情况下,您可能(暂时)需要手动包含该文件。

require_once('vendor/addiks/symfony_rdm/composer_hook.php');

服务-表单类型

此包还提供了一个名为“ServiceFormType”的新表单类型,这应该与该包的服务实体化能力结合使用非常有价值。它允许指定一个服务ID列表作为选择,可以在表单中选择,并设置在实体上。

<?php

use Addiks\RDMBundle\Symfony\FormType\ServiceFormType;

class MyEntityFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add("someField", ServiceFormType::class, [
            'required' => false,
            'choices' => [
                'app.example_services.foo' => 'foo',
                'app.example_services.bar' => 'bar',
            ]
        ]);
    }
    …
}

未来

该项目未来可能会扩展更多功能,以下是我的一些想法: