Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple AttributeDriver from AnnotationDriver #2502

Merged
merged 2 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions UPGRADE-2.7.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# UPGRADE FROM 2.6 to 2.7

## Backward compatibility breaks

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

## doctrine/persistence

* MongoDB ODM 2.7 requires `doctrine/persistence` 3.2 or newer.
346 changes: 1 addition & 345 deletions lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,12 @@

use Doctrine\Common\Annotations\AnnotationReader;
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 Doctrine\Persistence\Mapping\Driver\MappingDriver;
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 interface_exists;
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.
*/
class AnnotationDriver implements MappingDriver
class AnnotationDriver extends AttributeDriver
{
use ColocatedMappingDriver;

/**
* The annotation reader.
*
Expand All @@ -60,323 +35,6 @@ public function __construct($reader, $paths = null)
$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[$annot::class] = $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,
$documentAnnot::class,
);

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;
}

/**
* Factory method for the Annotation Driver
*
Expand All @@ -387,5 +45,3 @@ public static function create($paths = [], ?Reader $reader = null): AnnotationDr
return new self($reader ?? new AnnotationReader(), $paths);
}
}

interface_exists(\Doctrine\Persistence\Mapping\ClassMetadata::class);