Skip to content

Commit

Permalink
Throw hydrator exceptions when encountering invalid types
Browse files Browse the repository at this point in the history
  • Loading branch information
alcaeus committed Oct 1, 2019
1 parent 675dccc commit 5d767f7
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 4 deletions.
24 changes: 24 additions & 0 deletions lib/Doctrine/ODM/MongoDB/Hydrator/HydratorException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Doctrine\ODM\MongoDB\Hydrator;

use Doctrine\ODM\MongoDB\MongoDBException;
use function sprintf;

/**
* MongoDB ODM Hydrator Exception
Expand All @@ -25,4 +26,27 @@ public static function hydratorNamespaceRequired() : self
{
return new self('You must configure a hydrator namespace. See docs for details');
}

public static function associationTypeMismatch(string $className, string $fieldName, string $expectedType, string $actualType)
{
return new self(sprintf(
'Expected association for field "%s" in document of type "%s" to be of type "%s", "%s" received.',
$fieldName,
$className,
$expectedType,
$actualType
));
}

public static function associationItemTypeMismatch(string $className, string $fieldName, $key, string $expectedType, string $actualType)
{
return new self(sprintf(
'Expected association item with key "%s" for field "%s" in document of type "%s" to be of type "%s", "%s" received.',
$key,
$fieldName,
$className,
$expectedType,
$actualType
));
}
}
25 changes: 22 additions & 3 deletions lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
/** @ReferenceOne */
if (isset(\$data['%1\$s'])) {
\$reference = \$data['%1\$s'];
if (\$this->class->fieldMappings['%2\$s']['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID && ! is_array(\$reference)) {
throw HydratorException::associationTypeMismatch('%3\$s', '%1\$s', 'array', gettype(\$reference));
}
\$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$reference);
\$identifier = ClassMetadata::getReferenceId(\$reference, \$this->class->fieldMappings['%2\$s']['storeAs']);
\$targetMetadata = \$this->dm->getClassMetadata(\$className);
Expand All @@ -257,7 +262,8 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
EOF
,
$mapping['name'],
$mapping['fieldName']
$mapping['fieldName'],
$class->getName()
);
} elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isInverseSide']) {
if (isset($mapping['repositoryMethod']) && $mapping['repositoryMethod']) {
Expand Down Expand Up @@ -305,6 +311,11 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
/** @Many */
\$mongoData = isset(\$data['%1\$s']) ? \$data['%1\$s'] : null;
if (\$mongoData !== null && ! is_array(\$mongoData)) {
throw HydratorException::associationTypeMismatch('%3\$s', '%1\$s', 'array', gettype(\$mongoData));
}
\$return = \$this->dm->getConfiguration()->getPersistentCollectionFactory()->create(\$this->dm, \$this->class->fieldMappings['%2\$s']);
\$return->setHints(\$hints);
\$return->setOwner(\$document, \$this->class->fieldMappings['%2\$s']);
Expand All @@ -318,7 +329,8 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
EOF
,
$mapping['name'],
$mapping['fieldName']
$mapping['fieldName'],
$class->getName()
);
} elseif ($mapping['association'] === ClassMetadata::EMBED_ONE) {
$code .= sprintf(
Expand All @@ -327,6 +339,11 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
/** @EmbedOne */
if (isset(\$data['%1\$s'])) {
\$embeddedDocument = \$data['%1\$s'];
if (! is_array(\$embeddedDocument)) {
throw HydratorException::associationTypeMismatch('%3\$s', '%1\$s', 'array', gettype(\$embeddedDocument));
}
\$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$embeddedDocument);
\$embeddedMetadata = \$this->dm->getClassMetadata(\$className);
\$return = \$embeddedMetadata->newInstance();
Expand All @@ -347,7 +364,8 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
EOF
,
$mapping['name'],
$mapping['fieldName']
$mapping['fieldName'],
$class->getName()
);
}
}
Expand All @@ -360,6 +378,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
namespace $namespace;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Hydrator\HydratorException;
use Doctrine\ODM\MongoDB\Hydrator\HydratorInterface;
use Doctrine\ODM\MongoDB\Query\Query;
use Doctrine\ODM\MongoDB\UnitOfWork;
Expand Down
23 changes: 22 additions & 1 deletion lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use DateTime;
use Doctrine\Common\Persistence\Mapping\MappingException;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Hydrator\HydratorException;
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
use Doctrine\ODM\MongoDB\Iterator\CachingIterator;
use Doctrine\ODM\MongoDB\Iterator\HydratingIterator;
Expand Down Expand Up @@ -48,6 +49,7 @@
use function explode;
use function get_class;
use function get_object_vars;
use function gettype;
use function implode;
use function in_array;
use function is_array;
Expand Down Expand Up @@ -664,15 +666,24 @@ private function loadEmbedManyCollection(PersistentCollectionInterface $collecti
$embeddedDocuments = $collection->getMongoData();
$mapping = $collection->getMapping();
$owner = $collection->getOwner();

if (! $embeddedDocuments) {
return;
}

if ($owner === null) {
throw PersistentCollectionException::ownerRequiredToLoadCollection();
}

foreach ($embeddedDocuments as $key => $embeddedDocument) {
$className = $this->uow->getClassNameForAssociation($mapping, $embeddedDocument);
$embeddedMetadata = $this->dm->getClassMetadata($className);
$embeddedDocumentObject = $embeddedMetadata->newInstance();

if (! is_array($embeddedDocument)) {
throw HydratorException::associationItemTypeMismatch(get_class($owner), $mapping['name'], $key, 'array', gettype($embeddedDocument));
}

$this->uow->setParentAssociation($embeddedDocumentObject, $mapping, $owner, $mapping['name'] . '.' . $key);

$data = $this->hydratorFactory->hydrate($embeddedDocumentObject, $embeddedDocument, $collection->getHints());
Expand All @@ -693,12 +704,22 @@ private function loadReferenceManyCollectionOwningSide(PersistentCollectionInter
{
$hints = $collection->getHints();
$mapping = $collection->getMapping();
$owner = $collection->getOwner();
$groupedIds = [];

if ($owner === null) {
throw PersistentCollectionException::ownerRequiredToLoadCollection();
}

$sorted = isset($mapping['sort']) && $mapping['sort'];

foreach ($collection->getMongoData() as $key => $reference) {
$className = $this->uow->getClassNameForAssociation($mapping, $reference);
$className = $this->uow->getClassNameForAssociation($mapping, $reference);

if ($mapping['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID && ! is_array($reference)) {
throw HydratorException::associationItemTypeMismatch(get_class($owner), $mapping['name'], $key, 'array', gettype($reference));
}

$identifier = ClassMetadata::getReferenceId($reference, $mapping['storeAs']);
$id = $this->dm->getClassMetadata($className)->getPHPIdentifierValue($identifier);

Expand Down
88 changes: 88 additions & 0 deletions tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
namespace Doctrine\ODM\MongoDB\Tests;

use DateTime;
use Doctrine\ODM\MongoDB\Hydrator\HydratorException;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\PersistentCollection;
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface;
use Doctrine\ODM\MongoDB\Query\Query;
use ProxyManager\Proxy\GhostObjectInterface;

Expand Down Expand Up @@ -65,6 +67,92 @@ public function testReadOnly()
$this->assertFalse($this->uow->isInIdentityMap($user->embedOne));
$this->assertFalse($this->uow->isInIdentityMap($user->embedMany[0]));
}

public function testEmbedOneWithWrongType()
{
$user = new HydrationClosureUser();

$this->expectException(HydratorException::class);
$this->expectExceptionMessage('Expected association for field "embedOne" in document of type "' . HydrationClosureUser::class . '" to be of type "array", "string" received.');

$this->dm->getHydratorFactory()->hydrate($user, [
'_id' => 1,
'embedOne' => 'jon',
]);
}

public function testEmbedManyWithWrongType()
{
$user = new HydrationClosureUser();

$this->expectException(HydratorException::class);
$this->expectExceptionMessage('Expected association for field "embedMany" in document of type "' . HydrationClosureUser::class . '" to be of type "array", "string" received.');

$this->dm->getHydratorFactory()->hydrate($user, [
'_id' => 1,
'embedMany' => 'jon',
]);
}

public function testEmbedManyWithWrongElementType()
{
$user = new HydrationClosureUser();

$this->dm->getHydratorFactory()->hydrate($user, [
'_id' => 1,
'embedMany' => ['jon'],
]);

$this->assertInstanceOf(PersistentCollectionInterface::class, $user->embedMany);

$this->expectException(HydratorException::class);
$this->expectExceptionMessage('Expected association item with key "0" for field "embedMany" in document of type "' . HydrationClosureUser::class . '" to be of type "array", "string" received.');

$user->embedMany->initialize();
}

public function testReferenceOneWithWrongType()
{
$user = new HydrationClosureUser();

$this->expectException(HydratorException::class);
$this->expectExceptionMessage('Expected association for field "referenceOne" in document of type "' . HydrationClosureUser::class . '" to be of type "array", "string" received.');

$this->dm->getHydratorFactory()->hydrate($user, [
'_id' => 1,
'referenceOne' => 'jon',
]);
}

public function testReferenceManyWithWrongType()
{
$user = new HydrationClosureUser();

$this->expectException(HydratorException::class);
$this->expectExceptionMessage('Expected association for field "referenceMany" in document of type "' . HydrationClosureUser::class . '" to be of type "array", "string" received.');

$this->dm->getHydratorFactory()->hydrate($user, [
'_id' => 1,
'referenceMany' => 'jon',
]);
}

public function testReferenceManyWithWrongElementType()
{
$user = new HydrationClosureUser();

$this->dm->getHydratorFactory()->hydrate($user, [
'_id' => 1,
'referenceMany' => ['jon'],
]);

$this->assertInstanceOf(PersistentCollectionInterface::class, $user->referenceMany);

$this->expectException(HydratorException::class);
$this->expectExceptionMessage('Expected association item with key "0" for field "referenceMany" in document of type "' . HydrationClosureUser::class . '" to be of type "array", "string" received.');

$user->referenceMany->initialize();
}
}

/** @ODM\Document */
Expand Down

0 comments on commit 5d767f7

Please sign in to comment.