【问题标题】:In Symfony2 with Doctrine ODM how can I dynamically create fields in a document?在带有 Doctrine ODM 的 Symfony2 中,如何在文档中动态创建字段?
【发布时间】:2026-02-03 17:15:01
【问题描述】:

我有一个站点记录,其中可以包含任意数量的具有不同信息的 AdProvider 配置字段。不幸的是,fieldNames(提供者的名称)是独一无二的,而且还会有更多。我可以在 Document 中将它们中的每一个硬编码为哈希类型,但是每次添加新的 Provider 时我都必须更新 Document。

我想动态修改 Document 本身,查看可以从另一个 Mongo 集合中获取的 Providers 列表,但我不知道该怎么做。

我的第一次尝试是在 loadClassMetaData 事件上创建一个侦听器并映射新字段。我看到了字段映射,但它们没有反映在文档中。显然这些字段没有任何 getter 和 setter,所以我尝试使用神奇的 __get 和 __set 方法访问它们,但我得到它们不存在的错误。

也许我用错了方法?

Mongo 记录示例:

{
    "_id" : ObjectId("4ff1d29d99c6667722000000"),
    "_type" : [
        "Models_Site"
    ],
    "enabledAdProviders" : [
        "provider1",
        "provider2",
        "provider3",
        "provider4"
    ],
    "provider1" : {
        "id" : "4028cbff38e2d7c00666fd2fdc770208"
    },
    "provider2" : {
        "placements" : {
            "Top_300x50" : "477",
            "Btm_300x50" : "478",
            "Top_320x50" : "477",
            "Btm_320x50" : "478"
        }
    },
    "provider3" : {
        "id" : "8a809449013331fdcdc6662708532b20"
    },
    "siteId" : "PsTl",
    "siteName" : "Publisher Site",
    "provider4" : {
        "placements" : {
            "Top_300x50" : "430",
            "Btm_300x50" : "430"
        }
    }
}

我的听众:

<?php
namespace BIM\DataBundle\Listener;

use BIM\DataBundle\Document\AdPublisherRecord;
use BIM\DataBundle\Document\AdProviderRecord;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;

class AdPublisherSiteSetup
{
private $serviceContainer;

/**
 * This service is called every time Ads doctrine odm loads a document. 
 * We are dynamically creating the ad provider setting nodes on the AdPublisher Record 
 * 
 */
public function __construct($serv){
    $this->serviceContainer = $serv;
}

public function loadClassMetadata(\Doctrine\ODM\MongoDB\Event\LoadClassMetadataEventArgs $args)
{
    $metaData = $args->getClassMetadata();
    $document = (string)$metaData->getName();

    if($document == "BIM\DataBundle\Document\AdPublisherRecord"){
        //query for ad providers
        //create as a hash type to store each providers settings.
        $providerList = $this->serviceContainer->get('ads.publisher.factory')->getProviderList();
        foreach ($providerList as $name => $value) {
            $metaData->mapField(array('fieldName' => $name, 'type' => 'hash'));
        }   
    }
}
}

【问题讨论】:

  • 我不认为你想做的事情是可能的。 Doctrine 文档是“静态的”,因此您要加载的字段必须存在于文档中,否则将不会加载。如果你能解释一下你想要达到的目标,我想有更好的方法来做到这一点。
  • 我希望我的应用程序能够与新输入的广告提供商一起工作(生成创建或编辑站点记录时所需的正确表单字段。
  • 我又玩了一些,如果我在检索记录之前修改了 classMetadata,我就能让它工作。但是,我需要在 Document 中定义属性并使用魔术方法,所以这样做似乎没有优势。我想我只需要添加属性,生成 getter 和 setter 并在新的 AdProvider 添加到数据库时重新部署。

标签: symfony doctrine doctrine-odm odm


【解决方案1】:

你不能用普通的 Doctrine-odm 做到这一点,但是我使用了一个简单的技巧来做到这一点:


由于 MongodDB 文档可以嵌套,因此只需在您的文档中创建一个 @Hash 属性,该属性将包含无模式属性(将其视为哈希包):

class Doc {

   /** 
    * Schema-less dynamic properties goes here:
    * @MongoDB\Hash 
    */
   public $extraFields;

   /** @MongoDB\Date */
   public $createdDate;

   //Other static properties...
}

唯一需要注意的是,无模式属性将放在 $extraFields 属性中,而不是在根目录中。

然而这只是一个美学问题,你可以在这些字段上做任何事情(索引、查询、map reduce、ecc)

通过这种方式,您可以拥有静态定义的属性(如 Id、createdDate、ecc) 和一个动态属性的哈希包。

如果您想记录其中一些动态属性的存在(并避免给您的同事带来常见的拼写错误),您可以在文档中定义一些访问方法:

class Doc {

    public getExtraName() {
        if(empty($this->extraFields['name'])) return null;
         return $this->extraFields['name'];
    }
}

【讨论】:

    【解决方案2】:

    我为 Symfony2 和 Doctrine2-ODM 编写了一个扩展的 EAV 系统,其中任何文档都可以变成传统字段和属性的“混合”。这些属性都在另一个具有特定类型(地址、电话号码、字符串、文档参考等)的文档中定义。代码是公司财产,所以不能分享,但大意如下。

    • 属性的所有值都必须能够简化为数组(多维也可以),因为这些值存储在文档的哈希字段中。
    • 创建一个可以在文档中扩展的抽象类 (AbstractAttributeAware) 是我使用的。在那个类里面应该有以下内容。
      • 一个名为“updateCount”的字段或类似的东西。每当修改属性时,更新此更新计数以强制更新文档。
      • 用于存储压缩值的哈希字段。
      • 存储对象的属性(未存储在 DB 中)。
      • “deflate”和“inflate”方法获取散列值并将其转换为对象或获取对象并将其转换为散列。
    • 创建一个侦听持久化、更新和加载的侦听器。这些侦听器应该“膨胀”和“缩小”任何扩展 AbstractAttributeAware 类的文档。
    • 创建一个类作为处理通货膨胀和通货紧缩的“AttributeHandler”。基本上是一个容纳充气和放气功能的类。我想你可以把它们放在你的听众中而不是你想要的。

    很抱歉,我不能放下更多,但如果需要进一步澄清,我很乐意提供帮助。关键是粗体的 updateCount 字段(我花了很长时间才找到那个字段的底部)。

    【讨论】:

      【解决方案3】:

      经过大量研究,我想出了以下解决方案。希望它可以帮助.... 1.在services.yml中注册一个事件监听器

      服务: my_doctrine_listener: 类:Test\SamplerestBundle\Listener\ModifyColumn 标签: - {名称:教义mongodb.odm.event_listener,事件:loadClassMetadata }

      1. 在 Test\SamplerestBundle\Listener\ModifyColumn 添加以下方法

      公共函数 loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) { $classMetadata = $eventArgs->getClassMetadata(); $document = (string)$classMetadata->getName();

          if($document == "TestBundle\\Document\\TestDocument"){
              $fieldMapping = array(
                  'fieldName' => 'columnTest',
                  'type' => 'string'
              );
      
              /*
              the field is registered as private method
              */
              $classMetadata->reflFields['columnTest'] = new \ReflectionProperty($classMetadata->name,'about');
              $classMetadata->mapField($fieldMapping);
          }
      
      }
      
      1. 在文档中添加魔法方法

        public function __set($column,$value){
            $this->$column = $value;
        }
        
        public function __get($column){
            return $this->$column;
        }
        

      【讨论】:

      • 我收到一个 ReflectionException ,指出该字段不存在。因为,在 ReflectionProperty 构造函数中,它正在检查该属性在第一个参数给出的类中是否可用,因此它会引发异常。而且在 mapField 方法(Doctrine\ODM\MongoDB\Mapping\ClassMetadata)内部,在它添加到 reflFields 数组之前,它试图从反射类中获取新属性,所以它也在那里失败了。跨度>