Skip to content

Commit

Permalink
Validate that targetDocument can be resolved (#2577)
Browse files Browse the repository at this point in the history
* fix: Validate that targetDocument can be resolved

* refactor: Use exception factory

* fix: Validate that all values in a discriminatorMap can be resolved

* fix: PHPStan errors for intentionally missing class

* fix: psalm/phpunit CI tests
  • Loading branch information
buffcode committed Nov 23, 2023
1 parent d6f1686 commit 92b0d2f
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 55 deletions.
17 changes: 17 additions & 0 deletions lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use function extension_loaded;
use function get_class;
use function in_array;
use function interface_exists;
use function is_array;
use function is_string;
use function is_subclass_of;
Expand Down Expand Up @@ -2309,6 +2310,22 @@ public function mapField(array $mapping): array
$mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
}

if (isset($mapping['targetDocument']) && ! class_exists($mapping['targetDocument']) && ! interface_exists($mapping['targetDocument'])) {
throw MappingException::invalidTargetDocument(
$mapping['targetDocument'],
$this->name,
$mapping['fieldName'],
);
}

if (isset($mapping['discriminatorMap'])) {
foreach ($mapping['discriminatorMap'] as $value => $class) {
if (! class_exists($class) && ! interface_exists($class)) {
throw MappingException::invalidClassInReferenceDiscriminatorMap($class, $this->name, $mapping['fieldName']);
}
}
}

if (isset($mapping['version'])) {
$mapping['notSaved'] = true;
$this->setVersionMapping($mapping);
Expand Down
10 changes: 10 additions & 0 deletions lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public static function invalidClassInDiscriminatorMap(string $className, string
return new self(sprintf("Document class '%s' used in the discriminator map of class '%s' does not exist.", $className, $owningClass));
}

public static function invalidClassInReferenceDiscriminatorMap(string $className, string $owningClass, string $fieldName): self
{
return new self(sprintf("Document class '%s' used in the discriminator map of field '%s' in class '%s' does not exist.", $className, $fieldName, $owningClass));
}

public static function unlistedClassInDiscriminatorMap(string $className): self
{
return new self(sprintf('Document class "%s" is unlisted in the discriminator map.', $className));
Expand All @@ -87,6 +92,11 @@ public static function invalidDiscriminatorValue(string $value, string $owningCl
return new self(sprintf("Discriminator value '%s' used in the declaration of class '%s' does not exist.", $value, $owningClass));
}

public static function invalidTargetDocument(string $targetDocument, string $owningClass, string $owningField): self
{
return new self(sprintf("Target document class '%s' used in field '%s' of class '%s' does not exist.", $targetDocument, $owningField, $owningClass));
}

public static function missingFieldName(string $className): self
{
return new self(sprintf("The Document class '%s' field mapping misses the 'fieldName' attribute.", $className));
Expand Down
124 changes: 124 additions & 0 deletions tests/Doctrine/ODM/MongoDB/Tests/Functional/TargetDocumentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

declare(strict_types=1);

namespace Doctrine\ODM\MongoDB\Tests\Functional;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
use Doctrine\ODM\MongoDB\Tests\BaseTestCase;
use stdClass;

class TargetDocumentTest extends BaseTestCase
{
/** @doesNotPerformAssertions */
public function testMappedSuperClassAsTargetDocument(): void
{
$test = new TargetDocumentTestDocument();
$test->reference = new TargetDocumentTestReference();
$this->dm->persist($test);
$this->dm->persist($test->reference);
$this->dm->flush();
}

public function testTargetDocumentIsResolvable(): void
{
self::expectExceptionObject(
MappingException::invalidTargetDocument(
'Doctrine\ODM\MongoDB\Tests\Functional\SomeInvalidClass',
InvalidTargetDocumentTestDocument::class,
'reference',
),
);

$test = new InvalidTargetDocumentTestDocument();
$test->reference = new stdClass();
$this->dm->persist($test);
}

public function testDiscriminatorTargetIsResolvable(): void
{
self::expectExceptionObject(
MappingException::invalidClassInReferenceDiscriminatorMap(
'Doctrine\ODM\MongoDB\Tests\Functional\SomeInvalidClass',
InvalidDiscriminatorTargetsTestDocument::class,
'reference',
),
);

$test = new InvalidDiscriminatorTargetsTestDocument();
$test->reference = new stdClass();
$this->dm->persist($test);
}
}

/** @ODM\Document */
class TargetDocumentTestDocument
{
/**
* @ODM\Id
*
* @var string|null
*/
public $id;

/**
* @ODM\ReferenceOne(targetDocument=Doctrine\ODM\MongoDB\Tests\Functional\TargetDocumentTestReference::class)
*
* @var TargetDocumentTestReference|null
*/
public $reference;
}

/** @ODM\MappedSuperclass */
abstract class AbstractTargetDocumentTestReference
{
/**
* @ODM\Id
*
* @var string|null
*/
public $id;
}

/** @ODM\Document */
class TargetDocumentTestReference extends AbstractTargetDocumentTestReference
{
}

/** @ODM\Document */
class InvalidTargetDocumentTestDocument
{
/**
* @ODM\Id
*
* @var string|null
*/
public $id;

/**
* @ODM\ReferenceOne(targetDocument="Doctrine\ODM\MongoDB\Tests\Functional\SomeInvalidClass")
*
* @var object|null
*/
public $reference;
}


/** @ODM\Document */
class InvalidDiscriminatorTargetsTestDocument
{
/**
* @ODM\Id
*
* @var string|null
*/
public $id;

/**
* @ODM\ReferenceOne(discriminatorField="referencedClass", discriminatorMap={"Foo"="Doctrine\ODM\MongoDB\Tests\Functional\SomeInvalidClass"})
*
* @var object|null
*/
public $reference;
}
55 changes: 0 additions & 55 deletions tests/Doctrine/ODM/MongoDB/Tests/Functional/TestTargetDocument.php

This file was deleted.

0 comments on commit 92b0d2f

Please sign in to comment.