美文网首页
zero-length keys are not allowed

zero-length keys are not allowed

作者: 莫帆海氵 | 来源:发表于2021-08-23 23:38 被阅读0次

Doctrine mongodb 在保存数据的时候遇到下面的错误

zero-length keys are not allowed, did you use $ with double quotes?

遇到问题的环境如下

  • php 5.5
  • mongodb 4.0.26
  • mongo 1.6.0

问题的原因

模型的 ODM 声明中包含有空字符串@ODM\DiscriminatorField(fieldName="")

问题的定位

// 错误堆栈如下
Stack trace:
\#0 /var/www/rms/vendor/doctrine/mongodb/lib/Doctrine/MongoDB/Collection.php(983): MongoCollection->batchInsert(Array, Array)
\#1 /var/www/rms/vendor/doctrine/mongodb/lib/Doctrine/MongoDB/Collection.php(153): Doctrine\MongoDB\Collection->doBatchInsert(Array, Array)
\#2 /var/www/rms/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php(245): Doctrine\MongoDB\Collection->batchInsert(Array, Array)
\#3 /var/www/rms/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/UnitOfWork.php(1156): Doctrine\ODM\MongoDB\Persisters\DocumentPersister->executeInserts(Array)
\#4 /var/www/rms/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/UnitOfWork.php(430): Doctrine\ODM\MongoDB\UnitOfWork->executeInserts(Object(Doctrine\ODM\MongoDB\Mapping\ClassMetadata), Array, Array)
\#5 /var/www/rms/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/DocumentManager.php(528): Doctrine\ODM\MongoDB\UnitOfWork->commit(NULL, Array)
\#6 /var/www/rms/module/Setup/src/Setup/Mapper/AbstractMapper.php(26): Doctrine\ODM\MongoDB\DocumentManager->flush()
\#7 /var/www/rms/module/Setup/src/Setup/Service/AbstractService.php(64): Setup\Mapper\AbstractMapper->save(Object(Order\Document\Order))
\#8 /var/www/rms/module/Order/src/Order/Controller/OrderController.php(679): Setup\Service\AbstractService->add(Array)
\#9 /var/www/rms/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/AbstractActionController.php(82): Order\Controller\OrderController->addAction()
\#10 [internal function]: Zend\Mvc\Controller\AbstractActionController->onDispatch(Object(Zend\Mvc\MvcEvent))
\#11 /var/www/rms/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php(444): call_user_func(Array, Object(Zend\Mvc\MvcEvent))
\#12 /var/www/rms/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php(205): Zend\EventManager\EventManager->triggerListeners('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
\#13 /var/www/rms/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/AbstractController.php(118): Zend\EventManager\EventManager->trigger('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
\#14 /var/www/rms/vendor/zendframework/zendframework/library/Zend/Mvc/DispatchListener.php(93): Zend\Mvc\Controller\AbstractController->dispatch(Object(Zend\Http\PhpEnvironment\Request), Object(Zend\Http\PhpEnvironment\Response))
\#15 [internal function]: Zend\Mvc\DispatchListener->onDispatch(Object(Zend\Mvc\MvcEvent))
\#16 /var/www/rms/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php(444): call_user_func(Array, Object(Zend\Mvc\MvcEvent))
\#17 /var/www/rms/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php(205): Zend\EventManager\EventManager->triggerListeners('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
\#18 /var/www/rms/vendor/zendframework/zendframework/library/Zend/Mvc/Application.php(314): Zend\EventManager\EventManager->trigger('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
\#19 /var/www/rms/public/index.php(58): Zend\Mvc\Application->run()
\#20 {main}

网上参照方法:

通过这些知道的原因都是保存到数据库的对象里含有空字符串的键,但具体哪里的问题依然毫无头绪。尝试去模型里查找各种 private 字段,接着就按照最后的方法跟着堆栈依次查看数据,在查看保存前的数据时发现确实多了一个 “” 空字符串的键。

  1. MongoCollection->batchInsert

从最后这里查看保存的数据发现在保存的对象增加一个空字符的 key "" => "Order\Document\Order"

/**
     * Wrapper method for MongoCollection::batchInsert().
     *
     * This method will dispatch preBatchInsert and postBatchInsert events.
     *
     * @see http://php.net/manual/en/mongocollection.batchinsert.php
     * @param array $a       Array of documents (arrays/objects) to insert
     * @param array $options
     * @return array|boolean
     */
    public function batchInsert(array &$a, array $options = array())
    {
        if ($this->eventManager->hasListeners(Events::preBatchInsert)) {
            $this->eventManager->dispatchEvent(Events::preBatchInsert, new EventArgs($this, $a, $options));
        }

        $result = $this->doBatchInsert($a, $options);

        if ($this->eventManager->hasListeners(Events::postBatchInsert)) {
            $eventArgs = new MutableEventArgs($this, $result);
            $this->eventManager->dispatchEvent(Events::postBatchInsert, $eventArgs);
            $result = $eventArgs->getData();
        }

        return $result;
    }
  1. UnitOfWork->executeInserts

查看这里的 $document 没有发现多余的空字符串

/**
     * Executes all document insertions for documents of the specified type.
     *
     * @param ClassMetadata $class
     * @param array $documents Array of documents to insert
     * @param array $options Array of options to be used with batchInsert()
     */
    private function executeInserts(ClassMetadata $class, array $documents, array $options = array())
    {
        $persister = $this->getDocumentPersister($class->name);

        foreach ($documents as $oid => $document) {
            $persister->addInsert($document);
            unset($this->documentInsertions[$oid]);
        }

        $persister->executeInserts($options);

        $hasPostPersistLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postPersist]);
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);

        foreach ($documents as $document) {
            if ($hasPostPersistLifecycleCallbacks) {
                $class->invokeLifecycleCallbacks(Events::postPersist, $document);
            }
            if ($hasPostPersistListeners) {
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
            }
            $this->cascadePostPersist($class, $document);
        }
    }
  1. DocumentPersister->executeInserts

data =this->pb->prepareInsertData($document); 前后分别打印发现数据在这里追加了一个空字符串的 key

/**
     * Executes all queued document insertions.
     *
     * Queued documents without an ID will inserted in a batch and queued
     * documents with an ID will be upserted individually.
     *
     * If no inserts are queued, invoking this method is a NOOP.
     *
     * @param array $options Options for batchInsert() and update() driver methods
     */
    public function executeInserts(array $options = array())
    {
        if ( ! $this->queuedInserts) {
            return;
        }

        $inserts = array();
        foreach ($this->queuedInserts as $oid => $document) {
            $data = $this->pb->prepareInsertData($document);

            // Set the initial version for each insert
            if ($this->class->isVersioned) {
                $versionMapping = $this->class->fieldMappings[$this->class->versionField];
                if ($versionMapping['type'] === 'int') {
                    $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
                    $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
                } elseif ($versionMapping['type'] === 'date') {
                    $nextVersionDateTime = new \DateTime();
                    $nextVersion = new \MongoDate($nextVersionDateTime->getTimestamp());
                    $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
                }
                $data[$versionMapping['name']] = $nextVersion;
            }

            $inserts[$oid] = $data;
        }

        if ($inserts) {
            try {
                $this->collection->batchInsert($inserts, $options);
            } catch (\MongoException $e) {
                $this->queuedInserts = array();
                throw $e;
            }
        }

        /* All collections except for ones using addToSet have already been
         * saved. We have left these to be handled separately to avoid checking
         * collection for uniqueness on PHP side.
         */
        foreach ($this->queuedInserts as $document) {
            $this->handleCollections($document, $options);
        }

        $this->queuedInserts = array();
    }
  1. PersistenceBuilder->prepareInsertData

在这里发现会给保存的对象增加一个 $class->discriminatorField 的 key,然后就去查看 model 的定义发现声明了一个空字符串的

/**
     * Prepares the array that is ready to be inserted to mongodb for a given object document.
     *
     * @param object $document
     * @return array $insertData
     */
    public function prepareInsertData($document)
    {
        $class = $this->dm->getClassMetadata(get_class($document));
        $changeset = $this->uow->getDocumentChangeSet($document);

        $insertData = array();
        foreach ($class->fieldMappings as $mapping) {

            $new = isset($changeset[$mapping['fieldName']][1]) ? $changeset[$mapping['fieldName']][1] : null;

            if ($new === null && $mapping['nullable']) {
                $insertData[$mapping['name']] = null;
            }

            /* Nothing more to do for null values, since we're either storing
             * them (if nullable was true) or not.
             */
            if ($new === null) {
                continue;
            }

            // @Field, @String, @Date, etc.
            if ( ! isset($mapping['association'])) {
                $insertData[$mapping['name']] = Type::getType($mapping['type'])->convertToDatabaseValue($new);

            // @ReferenceOne
            } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) {
                $insertData[$mapping['name']] = $this->prepareReferencedDocumentValue($mapping, $new);

            // @EmbedOne
            } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_ONE) {
                $insertData[$mapping['name']] = $this->prepareEmbeddedDocumentValue($mapping, $new);
            }
        }

        // add discriminator if the class has one
        if (isset($class->discriminatorField)) {
            $insertData[$class->discriminatorField] = isset($class->discriminatorValue)
                ? $class->discriminatorValue
                : $class->name;
        }

        return $insertData;
    }

model 的声明如下:

/**
 * Order\Document\Order
 *
 * @ODM\Document(
 *     collection="order"
 * )
 * @ODM\InheritanceType("COLLECTION_PER_CLASS")
 * @ODM\DiscriminatorField(fieldName="")
 * @ODM\DiscriminatorMap({})
 * @ODM\ChangeTrackingPolicy("DEFERRED_IMPLICIT")
 */
class Order

通过移除@ODM\DiscriminatorField(fieldName="") 即可解决问题

相关文章

网友评论

      本文标题:zero-length keys are not allowed

      本文链接:https://www.haomeiwen.com/subject/wiuriltx.html