Skip to content

Commit

Permalink
AttributeDriver no longer extends from AnnotationDriver
Browse files Browse the repository at this point in the history
  • Loading branch information
franmomu committed Feb 5, 2023
1 parent 6bd72f6 commit 73611f3
Show file tree
Hide file tree
Showing 3 changed files with 390 additions and 19 deletions.
5 changes: 5 additions & 0 deletions UPGRADE-2.5.md
@@ -1,5 +1,10 @@
# UPGRADE FROM 2.4 to 2.5

## Backward compatibility breaks

* `Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver` no longer extends from
`Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver`.

## PHP requirements

* MongoDB ODM 2.5 requires PHP 7.4 or newer. If you're not running PHP 7.4 yet,
Expand Down
360 changes: 356 additions & 4 deletions lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php
Expand Up @@ -5,16 +5,368 @@
namespace Doctrine\ODM\MongoDB\Mapping\Driver;

use Doctrine\Common\Annotations\Reader;
use Doctrine\ODM\MongoDB\Events;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Mapping\Annotations\AbstractIndex;
use Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
use MongoDB\Driver\Exception\UnexpectedValueException;
use ReflectionClass;
use ReflectionMethod;

use function array_merge;
use function array_replace;
use function assert;
use function class_exists;
use function constant;
use function count;
use function get_class;
use function is_array;
use function MongoDB\BSON\fromJSON;
use function MongoDB\BSON\toPHP;
use function trigger_deprecation;

/**
* The AnnotationDriver reads the mapping metadata from docblock annotations.
* The AtttributeDriver reads the mapping metadata from attributes.
*/
class AttributeDriver extends AnnotationDriver
class AttributeDriver extends CompatibilityAnnotationDriver
{
use ColocatedMappingDriver;

/**
* The annotation reader.
*
* @internal this property will be private in 3.0
*
* @var Reader
*/
protected $reader;

/** @param string|string[]|null $paths */
public function __construct($paths = null, ?Reader $reader = null)
{
parent::__construct($reader ?? new AttributeReader(), $paths);
$this->reader = $reader ?? new AttributeReader();

$this->addPaths((array) $paths);
}

public function isTransient($className)
{
$classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));

foreach ($classAnnotations as $annot) {
if ($annot instanceof ODM\AbstractDocument) {
return false;
}
}

return true;
}

public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\ClassMetadata $metadata): void
{
assert($metadata instanceof ClassMetadata);
$reflClass = $metadata->getReflectionClass();

$classAnnotations = $this->reader->getClassAnnotations($reflClass);

$documentAnnot = null;
foreach ($classAnnotations as $annot) {
$classAnnotations[get_class($annot)] = $annot;

if ($annot instanceof ODM\AbstractDocument) {
if ($documentAnnot !== null) {
throw MappingException::classCanOnlyBeMappedByOneAbstractDocument($className, $documentAnnot, $annot);
}

$documentAnnot = $annot;
}

// non-document class annotations
if ($annot instanceof ODM\AbstractIndex) {
$this->addIndex($metadata, $annot);
}

if ($annot instanceof ODM\Indexes) {
trigger_deprecation(
'doctrine/mongodb-odm',
'2.2',
'The "@Indexes" annotation used in class "%s" is deprecated. Specify all "@Index" and "@UniqueIndex" annotations on the class.',
$className
);
$value = $annot->value;
foreach (is_array($value) ? $value : [$value] as $index) {
$this->addIndex($metadata, $index);
}
} elseif ($annot instanceof ODM\InheritanceType) {
$metadata->setInheritanceType(constant(ClassMetadata::class . '::INHERITANCE_TYPE_' . $annot->value));
} elseif ($annot instanceof ODM\DiscriminatorField) {
$metadata->setDiscriminatorField($annot->value);
} elseif ($annot instanceof ODM\DiscriminatorMap) {
$value = $annot->value;
assert(is_array($value));
$metadata->setDiscriminatorMap($value);
} elseif ($annot instanceof ODM\DiscriminatorValue) {
$metadata->setDiscriminatorValue($annot->value);
} elseif ($annot instanceof ODM\ChangeTrackingPolicy) {
$metadata->setChangeTrackingPolicy(constant(ClassMetadata::class . '::CHANGETRACKING_' . $annot->value));
} elseif ($annot instanceof ODM\DefaultDiscriminatorValue) {
$metadata->setDefaultDiscriminatorValue($annot->value);
} elseif ($annot instanceof ODM\ReadPreference) {
$metadata->setReadPreference($annot->value, $annot->tags ?? []);
} elseif ($annot instanceof ODM\Validation) {
if (isset($annot->validator)) {
try {
$validatorBson = fromJSON($annot->validator);
} catch (UnexpectedValueException $e) {
throw MappingException::schemaValidationError($e->getCode(), $e->getMessage(), $className, 'validator');
}

$validator = toPHP($validatorBson, []);
$metadata->setValidator($validator);
}

if (isset($annot->action)) {
$metadata->setValidationAction($annot->action);
}

if (isset($annot->level)) {
$metadata->setValidationLevel($annot->level);
}
}
}

if ($documentAnnot === null) {
throw MappingException::classIsNotAValidDocument($className);
}

if ($documentAnnot instanceof ODM\MappedSuperclass) {
$metadata->isMappedSuperclass = true;
} elseif ($documentAnnot instanceof ODM\EmbeddedDocument) {
$metadata->isEmbeddedDocument = true;
} elseif ($documentAnnot instanceof ODM\QueryResultDocument) {
$metadata->isQueryResultDocument = true;
} elseif ($documentAnnot instanceof ODM\View) {
if (! $documentAnnot->rootClass) {
throw MappingException::viewWithoutRootClass($className);
}

if (! class_exists($documentAnnot->rootClass)) {
throw MappingException::viewRootClassNotFound($className, $documentAnnot->rootClass);
}

$metadata->markViewOf($documentAnnot->rootClass);
} elseif ($documentAnnot instanceof ODM\File) {
$metadata->isFile = true;

if ($documentAnnot->chunkSizeBytes !== null) {
$metadata->setChunkSizeBytes($documentAnnot->chunkSizeBytes);
}
}

if (isset($documentAnnot->db)) {
$metadata->setDatabase($documentAnnot->db);
}

if (isset($documentAnnot->collection)) {
$metadata->setCollection($documentAnnot->collection);
}

if (isset($documentAnnot->view)) {
$metadata->setCollection($documentAnnot->view);
}

// Store bucketName as collection name for GridFS files
if (isset($documentAnnot->bucketName)) {
$metadata->setBucketName($documentAnnot->bucketName);
}

if (isset($documentAnnot->repositoryClass)) {
$metadata->setCustomRepositoryClass($documentAnnot->repositoryClass);
}

if (isset($documentAnnot->writeConcern)) {
$metadata->setWriteConcern($documentAnnot->writeConcern);
}

if (isset($documentAnnot->indexes) && count($documentAnnot->indexes)) {
trigger_deprecation(
'doctrine/mongodb-odm',
'2.2',
'The "indexes" parameter in the "%s" annotation for class "%s" is deprecated. Specify all "@Index" and "@UniqueIndex" annotations on the class.',
$className,
get_class($documentAnnot)
);

foreach ($documentAnnot->indexes as $index) {
$this->addIndex($metadata, $index);
}
}

if (! empty($documentAnnot->readOnly)) {
$metadata->markReadOnly();
}

foreach ($reflClass->getProperties() as $property) {
if (
($metadata->isMappedSuperclass && ! $property->isPrivate())
||
($metadata->isInheritedField($property->name) && $property->getDeclaringClass()->name !== $metadata->name)
) {
continue;
}

$indexes = [];
$mapping = ['fieldName' => $property->getName()];
$fieldAnnot = null;

foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof ODM\AbstractField) {
$fieldAnnot = $annot;
}

if ($annot instanceof ODM\AbstractIndex) {
$indexes[] = $annot;
}

if ($annot instanceof ODM\Indexes) {
$value = $annot->value;
foreach (is_array($value) ? $value : [$value] as $index) {
$indexes[] = $index;
}
} elseif ($annot instanceof ODM\AlsoLoad) {
$mapping['alsoLoadFields'] = (array) $annot->value;
} elseif ($annot instanceof ODM\Version) {
$mapping['version'] = true;
} elseif ($annot instanceof ODM\Lock) {
$mapping['lock'] = true;
}
}

if ($fieldAnnot) {
$mapping = array_replace($mapping, (array) $fieldAnnot);
$metadata->mapField($mapping);
}

if (! $indexes) {
continue;
}

foreach ($indexes as $index) {
$name = $mapping['name'] ?? $mapping['fieldName'];
$keys = [$name => $index->order ?: 'asc'];
$this->addIndex($metadata, $index, $keys);
}
}

// Set shard key after all fields to ensure we mapped all its keys
if (isset($classAnnotations[ShardKey::class])) {
assert($classAnnotations[ShardKey::class] instanceof ShardKey);
$this->setShardKey($metadata, $classAnnotations[ShardKey::class]);
}

foreach ($reflClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
/* Filter for the declaring class only. Callbacks from parent
* classes will already be registered.
*/
if ($method->getDeclaringClass()->name !== $reflClass->name) {
continue;
}

foreach ($this->reader->getMethodAnnotations($method) as $annot) {
if ($annot instanceof ODM\AlsoLoad) {
$metadata->registerAlsoLoadMethod($method->getName(), $annot->value);
}

if (! isset($classAnnotations[ODM\HasLifecycleCallbacks::class])) {
continue;
}

if ($annot instanceof ODM\PrePersist) {
$metadata->addLifecycleCallback($method->getName(), Events::prePersist);
} elseif ($annot instanceof ODM\PostPersist) {
$metadata->addLifecycleCallback($method->getName(), Events::postPersist);
} elseif ($annot instanceof ODM\PreUpdate) {
$metadata->addLifecycleCallback($method->getName(), Events::preUpdate);
} elseif ($annot instanceof ODM\PostUpdate) {
$metadata->addLifecycleCallback($method->getName(), Events::postUpdate);
} elseif ($annot instanceof ODM\PreRemove) {
$metadata->addLifecycleCallback($method->getName(), Events::preRemove);
} elseif ($annot instanceof ODM\PostRemove) {
$metadata->addLifecycleCallback($method->getName(), Events::postRemove);
} elseif ($annot instanceof ODM\PreLoad) {
$metadata->addLifecycleCallback($method->getName(), Events::preLoad);
} elseif ($annot instanceof ODM\PostLoad) {
$metadata->addLifecycleCallback($method->getName(), Events::postLoad);
} elseif ($annot instanceof ODM\PreFlush) {
$metadata->addLifecycleCallback($method->getName(), Events::preFlush);
}
}
}
}

/**
* @param ClassMetadata<object> $class
* @param array<string, int|string> $keys
*/
private function addIndex(ClassMetadata $class, AbstractIndex $index, array $keys = []): void
{
$keys = array_merge($keys, $index->keys);
$options = [];
$allowed = ['name', 'background', 'unique', 'sparse', 'expireAfterSeconds'];
foreach ($allowed as $name) {
if (! isset($index->$name)) {
continue;
}

$options[$name] = $index->$name;
}

if (! empty($index->partialFilterExpression)) {
$options['partialFilterExpression'] = $index->partialFilterExpression;
}

$options = array_merge($options, $index->options);
$class->addIndex($keys, $options);
}

/**
* @param ClassMetadata<object> $class
*
* @throws MappingException
*/
private function setShardKey(ClassMetadata $class, ODM\ShardKey $shardKey): void
{
$options = [];
$allowed = ['unique', 'numInitialChunks'];
foreach ($allowed as $name) {
if (! isset($shardKey->$name)) {
continue;
}

$options[$name] = $shardKey->$name;
}

$class->setShardKey($shardKey->keys, $options);
}

/**
* Retrieve the current annotation reader
*
* @return Reader
*/
public function getReader()
{
trigger_deprecation(
'doctrine/mongodb-odm',
'2.4',
'%s is deprecated with no replacement',
__METHOD__
);

return $this->reader;
}

/**
Expand All @@ -24,7 +376,7 @@ public function __construct($paths = null, ?Reader $reader = null)
*
* @return AttributeDriver
*/
public static function create($paths = [], ?Reader $reader = null): AnnotationDriver
public static function create($paths = [], ?Reader $reader = null)
{
return new self($paths, $reader);
}
Expand Down

0 comments on commit 73611f3

Please sign in to comment.