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}
网上参照方法:
- https://stackoverflow.com/questions/9809103/unable-to-save-php-objects-in-mongodb
- https://stackoverflow.com/questions/28793821/mongodb-update-embedded-document-doctrine-zero-length-keys-are-not-allowed-did
通过这些知道的原因都是保存到数据库的对象里含有空字符串的键,但具体哪里的问题依然毫无头绪。尝试去模型里查找各种 private 字段,接着就按照最后的方法跟着堆栈依次查看数据,在查看保存前的数据时发现确实多了一个 “” 空字符串的键。
- 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;
}
- 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);
}
}
- DocumentPersister->executeInserts
在 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();
}
- 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="")
即可解决问题
网友评论