vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php line 2378
<?phpdeclare(strict_types=1);namespace Doctrine\ODM\MongoDB\Mapping;use BackedEnum;use BadMethodCallException;use DateTime;use DateTimeImmutable;use Doctrine\Instantiator\Instantiator;use Doctrine\Instantiator\InstantiatorInterface;use Doctrine\ODM\MongoDB\Id\IdGenerator;use Doctrine\ODM\MongoDB\LockException;use Doctrine\ODM\MongoDB\Types\Incrementable;use Doctrine\ODM\MongoDB\Types\Type;use Doctrine\ODM\MongoDB\Types\Versionable;use Doctrine\ODM\MongoDB\Utility\CollectionHelper;use Doctrine\Persistence\Mapping\ClassMetadata as BaseClassMetadata;use Doctrine\Persistence\Mapping\ReflectionService;use Doctrine\Persistence\Mapping\RuntimeReflectionService;use Doctrine\Persistence\Reflection\EnumReflectionProperty;use InvalidArgumentException;use LogicException;use ProxyManager\Proxy\GhostObjectInterface;use ReflectionClass;use ReflectionEnum;use ReflectionNamedType;use ReflectionProperty;use function array_filter;use function array_key_exists;use function array_keys;use function array_map;use function array_pop;use function assert;use function class_exists;use function constant;use function count;use function enum_exists;use function extension_loaded;use function get_class;use function in_array;use function is_array;use function is_string;use function is_subclass_of;use function ltrim;use function sprintf;use function strtolower;use function strtoupper;use function trigger_deprecation;use const PHP_VERSION_ID;/*** A <tt>ClassMetadata</tt> instance holds all the object-document mapping metadata* of a document and it's references.** Once populated, ClassMetadata instances are usually cached in a serialized form.** <b>IMPORTANT NOTE:</b>** The fields of this class are only public for 2 reasons:* 1) To allow fast READ access.* 2) To drastically reduce the size of a serialized instance (private/protected members* get the whole class name, namespace inclusive, prepended to every property in* the serialized representation).** @psalm-type FieldMappingConfig = array{* type?: string,* fieldName?: string,* name?: string,* strategy?: string,* association?: int,* id?: bool,* isOwningSide?: bool,* collectionClass?: class-string,* cascade?: list<string>|string,* embedded?: bool,* orphanRemoval?: bool,* options?: array<string, mixed>,* nullable?: bool,* reference?: bool,* storeAs?: string,* targetDocument?: class-string|null,* mappedBy?: string|null,* inversedBy?: string|null,* discriminatorField?: string,* defaultDiscriminatorValue?: string,* discriminatorMap?: array<string, class-string>,* repositoryMethod?: string|null,* sort?: array<string, string|int>,* limit?: int|null,* skip?: int|null,* version?: bool,* lock?: bool,* inherited?: string,* declared?: class-string,* prime?: list<string>,* sparse?: bool,* unique?: bool,* index?: bool,* index-name?: string,* criteria?: array<string, string>,* alsoLoadFields?: list<string>,* order?: int|string,* background?: bool,* enumType?: class-string<BackedEnum>,* }* @psalm-type FieldMapping = array{* type: string,* fieldName: string,* name: string,* isCascadeRemove: bool,* isCascadePersist: bool,* isCascadeRefresh: bool,* isCascadeMerge: bool,* isCascadeDetach: bool,* isOwningSide: bool,* isInverseSide: bool,* strategy?: string,* association?: int,* id?: bool,* collectionClass?: class-string,* cascade?: list<string>|string,* embedded?: bool,* orphanRemoval?: bool,* options?: array<string, mixed>,* nullable?: bool,* reference?: bool,* storeAs?: string,* targetDocument?: class-string|null,* mappedBy?: string|null,* inversedBy?: string|null,* discriminatorField?: string,* defaultDiscriminatorValue?: string,* discriminatorMap?: array<string, class-string>,* repositoryMethod?: string|null,* sort?: array<string, string|int>,* limit?: int|null,* skip?: int|null,* version?: bool,* lock?: bool,* notSaved?: bool,* inherited?: string,* declared?: class-string,* prime?: list<string>,* sparse?: bool,* unique?: bool,* index?: bool,* criteria?: array<string, string>,* alsoLoadFields?: list<string>,* enumType?: class-string<BackedEnum>,* }* @psalm-type AssociationFieldMapping = array{* type?: string,* fieldName: string,* name: string,* isCascadeRemove: bool,* isCascadePersist: bool,* isCascadeRefresh: bool,* isCascadeMerge: bool,* isCascadeDetach: bool,* isOwningSide: bool,* isInverseSide: bool,* targetDocument: class-string|null,* association: int,* strategy?: string,* id?: bool,* collectionClass?: class-string,* cascade?: list<string>|string,* embedded?: bool,* orphanRemoval?: bool,* options?: array<string, mixed>,* nullable?: bool,* reference?: bool,* storeAs?: string,* mappedBy?: string|null,* inversedBy?: string|null,* discriminatorField?: string,* defaultDiscriminatorValue?: string,* discriminatorMap?: array<string, class-string>,* repositoryMethod?: string|null,* sort?: array<string, string|int>,* limit?: int|null,* skip?: int|null,* version?: bool,* lock?: bool,* notSaved?: bool,* inherited?: string,* declared?: class-string,* prime?: list<string>,* sparse?: bool,* unique?: bool,* index?: bool,* criteria?: array<string, string>,* alsoLoadFields?: list<string>,* }* @psalm-type IndexKeys = array<string, mixed>* @psalm-type IndexOptions = array<string, mixed>* @psalm-type IndexMapping = array{* keys: IndexKeys,* options: IndexOptions* }* @psalm-type ShardKeys = array<string, mixed>* @psalm-type ShardOptions = array<string, mixed>* @psalm-type ShardKey = array{* keys?: ShardKeys,* options?: ShardOptions* }* @final* @template-covariant T of object* @template-implements BaseClassMetadata<T>*//* final */ class ClassMetadata implements BaseClassMetadata{/* The Id generator types. *//*** AUTO means Doctrine will automatically create a new \MongoDB\BSON\ObjectId instance for us.*/public const GENERATOR_TYPE_AUTO = 1;/*** INCREMENT means a separate collection is used for maintaining and incrementing id generation.* Offers full portability.*/public const GENERATOR_TYPE_INCREMENT = 2;/*** UUID means Doctrine will generate a uuid for us.*/public const GENERATOR_TYPE_UUID = 3;/*** ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT* generator to ensure identifier uniqueness*/public const GENERATOR_TYPE_ALNUM = 4;/*** CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class* and pass other options to the generator. It will throw an Exception if the class* does not exist or if an option was passed for that there is not setter in the new* generator class.** The class will have to implement IdGenerator.*/public const GENERATOR_TYPE_CUSTOM = 5;/*** NONE means Doctrine will not generate any id for us and you are responsible for manually* assigning an id.*/public const GENERATOR_TYPE_NONE = 6;/*** Default discriminator field name.** This is used for associations value for associations where a that do not define a "targetDocument" or* "discriminatorField" option in their mapping.*/public const DEFAULT_DISCRIMINATOR_FIELD = '_doctrine_class_name';/*** Association types*/public const REFERENCE_ONE = 1;public const REFERENCE_MANY = 2;public const EMBED_ONE = 3;public const EMBED_MANY = 4;/*** Mapping types*/public const MANY = 'many';public const ONE = 'one';/*** The types of storeAs references*/public const REFERENCE_STORE_AS_ID = 'id';public const REFERENCE_STORE_AS_DB_REF = 'dbRef';public const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';public const REFERENCE_STORE_AS_REF = 'ref';/*** The collection schema validationAction values** @see https://docs.mongodb.com/manual/core/schema-validation/#accept-or-reject-invalid-documents*/public const SCHEMA_VALIDATION_ACTION_ERROR = 'error';public const SCHEMA_VALIDATION_ACTION_WARN = 'warn';/*** The collection schema validationLevel values** @see https://docs.mongodb.com/manual/core/schema-validation/#existing-documents*/public const SCHEMA_VALIDATION_LEVEL_OFF = 'off';public const SCHEMA_VALIDATION_LEVEL_STRICT = 'strict';public const SCHEMA_VALIDATION_LEVEL_MODERATE = 'moderate';/* The inheritance mapping types *//*** NONE means the class does not participate in an inheritance hierarchy* and therefore does not need an inheritance mapping type.*/public const INHERITANCE_TYPE_NONE = 1;/*** SINGLE_COLLECTION means the class will be persisted according to the rules of* <tt>Single Collection Inheritance</tt>.*/public const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;/*** COLLECTION_PER_CLASS means the class will be persisted according to the rules* of <tt>Concrete Collection Inheritance</tt>.*/public const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;/*** DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time* by doing a property-by-property comparison with the original data. This will* be done for all entities that are in MANAGED state at commit-time.** This is the default change tracking policy.*/public const CHANGETRACKING_DEFERRED_IMPLICIT = 1;/*** DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time* by doing a property-by-property comparison with the original data. This will* be done only for entities that were explicitly saved (through persist() or a cascade).*/public const CHANGETRACKING_DEFERRED_EXPLICIT = 2;/*** NOTIFY means that Doctrine relies on the entities sending out notifications* when their properties change. Such entity classes must implement* the <tt>NotifyPropertyChanged</tt> interface.** @deprecated*/public const CHANGETRACKING_NOTIFY = 3;/*** SET means that fields will be written to the database using a $set operator*/public const STORAGE_STRATEGY_SET = 'set';/*** INCREMENT means that fields will be written to the database by calculating* the difference and using the $inc operator*/public const STORAGE_STRATEGY_INCREMENT = 'increment';public const STORAGE_STRATEGY_PUSH_ALL = 'pushAll';public const STORAGE_STRATEGY_ADD_TO_SET = 'addToSet';public const STORAGE_STRATEGY_ATOMIC_SET = 'atomicSet';public const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';public const STORAGE_STRATEGY_SET_ARRAY = 'setArray';private const ALLOWED_GRIDFS_FIELDS = ['_id', 'chunkSize', 'filename', 'length', 'metadata', 'uploadDate'];/*** READ-ONLY: The name of the mongo database the document is mapped to.** @var string|null*/public $db;/*** READ-ONLY: The name of the mongo collection the document is mapped to.** @var string*/public $collection;/*** READ-ONLY: The name of the GridFS bucket the document is mapped to.** @var string*/public $bucketName = 'fs';/*** READ-ONLY: If the collection should be a fixed size.** @var bool*/public $collectionCapped = false;/*** READ-ONLY: If the collection is fixed size, its size in bytes.** @var int|null*/public $collectionSize;/*** READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.** @var int|null*/public $collectionMax;/*** READ-ONLY Describes how MongoDB clients route read operations to the members of a replica set.** @var string|null*/public $readPreference;/*** READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read* operations to specific members, based on custom parameters.** @var array<array<string, string>>*/public $readPreferenceTags = [];/*** READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.** @var string|int|null*/public $writeConcern;/*** READ-ONLY: The field name of the document identifier.** @var string|null*/public $identifier;/*** READ-ONLY: The array of indexes for the document collection.** @var array<array<string, mixed>>* @psalm-var array<IndexMapping>*/public $indexes = [];/*** READ-ONLY: Keys and options describing shard key. Only for sharded collections.** @var array<string, array>* @psalm-var ShardKey*/public $shardKey = [];/*** Allows users to specify a validation schema for the collection.** @var array|object|null* @psalm-var array<string, mixed>|object|null*/private $validator;/*** Determines whether to error on invalid documents or just warn about the violations but allow invalid documents to be inserted.** @var string*/private $validationAction = self::SCHEMA_VALIDATION_ACTION_ERROR;/*** Determines how strictly MongoDB applies the validation rules to existing documents during an update.** @var string*/private $validationLevel = self::SCHEMA_VALIDATION_LEVEL_STRICT;/*** READ-ONLY: The name of the document class.** @var string* @psalm-var class-string<T>*/public $name;/*** READ-ONLY: The name of the document class that is at the root of the mapped document inheritance* hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same* as {@link $documentName}.** @var string* @psalm-var class-string*/public $rootDocumentName;/*** The name of the custom repository class used for the document class.* (Optional).** @var string|null* @psalm-var class-string|null*/public $customRepositoryClassName;/*** READ-ONLY: The names of the parent classes (ancestors).** @var array* @psalm-var list<class-string>*/public $parentClasses = [];/*** READ-ONLY: The names of all subclasses (descendants).** @var array* @psalm-var list<class-string>*/public $subClasses = [];/*** The ReflectionProperty instances of the mapped class.** @var ReflectionProperty[]*/public $reflFields = [];/*** READ-ONLY: The inheritance mapping type used by the class.** @var int*/public $inheritanceType = self::INHERITANCE_TYPE_NONE;/*** READ-ONLY: The Id generator type used by the class.** @var int*/public $generatorType = self::GENERATOR_TYPE_AUTO;/*** READ-ONLY: The Id generator options.** @var array<string, mixed>*/public $generatorOptions = [];/*** READ-ONLY: The ID generator used for generating IDs for this class.** @var IdGenerator|null*/public $idGenerator;/*** READ-ONLY: The field mappings of the class.* Keys are field names and values are mapping definitions.** The mapping definition array has the following values:** - <b>fieldName</b> (string)* The name of the field in the Document.** - <b>id</b> (boolean, optional)* Marks the field as the primary key of the document. Multiple fields of an* document can have the id attribute, forming a composite key.** @var array<string, mixed>* @psalm-var array<string, FieldMapping>*/public $fieldMappings = [];/*** READ-ONLY: The association mappings of the class.* Keys are field names and values are mapping definitions.** @var array<string, mixed>* @psalm-var array<string, AssociationFieldMapping>*/public $associationMappings = [];/*** READ-ONLY: Array of fields to also load with a given method.** @var array<string, mixed[]>*/public $alsoLoadMethods = [];/*** READ-ONLY: The registered lifecycle callbacks for documents of this class.** @var array<string, list<string>>*/public $lifecycleCallbacks = [];/*** READ-ONLY: The discriminator value of this class.** <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies* where a discriminator field is used.</b>** @see discriminatorField** @var string|null* @psalm-var class-string|null*/public $discriminatorValue;/*** READ-ONLY: The discriminator map of all mapped classes in the hierarchy.** <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy* where a discriminator field is used.</b>** @see discriminatorField** @psalm-var array<string, class-string>*/public $discriminatorMap = [];/*** READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION* inheritance mapping.** @var string|null*/public $discriminatorField;/*** READ-ONLY: The default value for discriminatorField in case it's not set in the document** @see discriminatorField** @var string|null*/public $defaultDiscriminatorValue;/*** READ-ONLY: Whether this class describes the mapping of a mapped superclass.** @var bool*/public $isMappedSuperclass = false;/*** READ-ONLY: Whether this class describes the mapping of a embedded document.** @var bool*/public $isEmbeddedDocument = false;/*** READ-ONLY: Whether this class describes the mapping of an aggregation result document.** @var bool*/public $isQueryResultDocument = false;/*** READ-ONLY: Whether this class describes the mapping of a database view.** @var bool*/private $isView = false;/*** READ-ONLY: Whether this class describes the mapping of a gridFS file** @var bool*/public $isFile = false;/*** READ-ONLY: The default chunk size in bytes for the file** @var int|null*/public $chunkSizeBytes;/*** READ-ONLY: The policy used for change-tracking on entities of this class.** @var int*/public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;/*** READ-ONLY: A flag for whether or not instances of this class are to be versioned* with optimistic locking.** @var bool $isVersioned*/public $isVersioned = false;/*** READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).** @var string|null $versionField*/public $versionField;/*** READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic* locking.** @var bool $isLockable*/public $isLockable = false;/*** READ-ONLY: The name of the field which is used for locking a document.** @var mixed $lockField*/public $lockField;/*** The ReflectionClass instance of the mapped class.** @var ReflectionClass* @psalm-var ReflectionClass<T>*/public $reflClass;/*** READ_ONLY: A flag for whether or not this document is read-only.** @var bool*/public $isReadOnly;/** @var InstantiatorInterface */private $instantiator;/** @var ReflectionService */private $reflectionService;/*** @var string|null* @psalm-var class-string|null*/private $rootClass;/*** Initializes a new ClassMetadata instance that will hold the object-document mapping* metadata of the class with the given name.** @psalm-param class-string<T> $documentName*/public function __construct(string $documentName){$this->name = $documentName;$this->rootDocumentName = $documentName;$this->reflectionService = new RuntimeReflectionService();$this->reflClass = new ReflectionClass($documentName);$this->setCollection($this->reflClass->getShortName());$this->instantiator = new Instantiator();}/*** Helper method to get reference id of ref* type references** @internal** @param mixed $reference** @return mixed*/public static function getReferenceId($reference, string $storeAs){return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];}/*** Returns the reference prefix used for a reference*/private static function getReferencePrefix(string $storeAs): string{if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {throw new LogicException('Can only get a reference prefix for DBRef and reference arrays');}return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';}/*** Returns a fully qualified field name for a given reference** @internal** @param string $pathPrefix The field path prefix*/public static function getReferenceFieldName(string $storeAs, string $pathPrefix = ''): string{if ($storeAs === self::REFERENCE_STORE_AS_ID) {return $pathPrefix;}return ($pathPrefix ? $pathPrefix . '.' : '') . static::getReferencePrefix($storeAs) . 'id';}public function getReflectionClass(): ReflectionClass{return $this->reflClass;}/*** @param string $fieldName*/public function isIdentifier($fieldName): bool{return $this->identifier === $fieldName;}/*** Sets the mapped identifier field of this class.** @internal*/public function setIdentifier(?string $identifier): void{$this->identifier = $identifier;}/*** Since MongoDB only allows exactly one identifier field* this will always return an array with only one value** @return array<string|null>*/public function getIdentifier(): array{return [$this->identifier];}/*** Since MongoDB only allows exactly one identifier field* this will always return an array with only one value** @return array<string|null>*/public function getIdentifierFieldNames(): array{return [$this->identifier];}/*** @param string $fieldName*/public function hasField($fieldName): bool{return isset($this->fieldMappings[$fieldName]);}/*** Sets the inheritance type used by the class and it's subclasses.*/public function setInheritanceType(int $type): void{$this->inheritanceType = $type;}/*** Checks whether a mapped field is inherited from an entity superclass.*/public function isInheritedField(string $fieldName): bool{return isset($this->fieldMappings[$fieldName]['inherited']);}/*** Registers a custom repository class for the document class.** @psalm-param class-string|null $repositoryClassName*/public function setCustomRepositoryClass(?string $repositoryClassName): void{if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {return;}$this->customRepositoryClassName = $repositoryClassName;}/*** Dispatches the lifecycle event of the given document by invoking all* registered callbacks.** @param mixed[]|null $arguments** @throws InvalidArgumentException If document class is not this class or* a Proxy of this class.*/public function invokeLifecycleCallbacks(string $event, object $document, ?array $arguments = null): void{if ($this->isView()) {return;}if (! $document instanceof $this->name) {throw new InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));}if (empty($this->lifecycleCallbacks[$event])) {return;}foreach ($this->lifecycleCallbacks[$event] as $callback) {if ($arguments !== null) {$document->$callback(...$arguments);} else {$document->$callback();}}}/*** Checks whether the class has callbacks registered for a lifecycle event.*/public function hasLifecycleCallbacks(string $event): bool{return ! empty($this->lifecycleCallbacks[$event]);}/*** Gets the registered lifecycle callbacks for an event.** @return list<string>*/public function getLifecycleCallbacks(string $event): array{return $this->lifecycleCallbacks[$event] ?? [];}/*** Adds a lifecycle callback for documents of this class.** If the callback is already registered, this is a NOOP.*/public function addLifecycleCallback(string $callback, string $event): void{if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {return;}$this->lifecycleCallbacks[$event][] = $callback;}/*** Sets the lifecycle callbacks for documents of this class.** Any previously registered callbacks are overwritten.** @param array<string, list<string>> $callbacks*/public function setLifecycleCallbacks(array $callbacks): void{$this->lifecycleCallbacks = $callbacks;}/*** Registers a method for loading document data before field hydration.** Note: A method may be registered multiple times for different fields.* it will be invoked only once for the first field found.** @param array<string, mixed>|string $fields Database field name(s)*/public function registerAlsoLoadMethod(string $method, $fields): void{$this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];}/*** Sets the AlsoLoad methods for documents of this class.** Any previously registered methods are overwritten.** @param array<string, mixed[]> $methods*/public function setAlsoLoadMethods(array $methods): void{$this->alsoLoadMethods = $methods;}/*** Sets the discriminator field.** The field name is the the unmapped database field. Discriminator values* are only used to discern the hydration class and are not mapped to class* properties.** @param array<string, mixed>|string|null $discriminatorField* @psalm-param array{name?: string, fieldName?: string}|string|null $discriminatorField** @throws MappingException If the discriminator field conflicts with the* "name" attribute of a mapped field.*/public function setDiscriminatorField($discriminatorField): void{if ($this->isFile) {throw MappingException::discriminatorNotAllowedForGridFS($this->name);}if ($discriminatorField === null) {$this->discriminatorField = null;return;}// @todo: deprecate, document and remove this:// Handle array argument with name/fieldName keys for BCif (is_array($discriminatorField)) {if (isset($discriminatorField['name'])) {$discriminatorField = $discriminatorField['name'];} elseif (isset($discriminatorField['fieldName'])) {$discriminatorField = $discriminatorField['fieldName'];}}foreach ($this->fieldMappings as $fieldMapping) {if ($discriminatorField === $fieldMapping['name']) {throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);}}$this->discriminatorField = $discriminatorField;}/*** Sets the discriminator values used by this class.* Used for JOINED and SINGLE_TABLE inheritance mapping strategies.** @param array<string, class-string> $map** @throws MappingException*/public function setDiscriminatorMap(array $map): void{if ($this->isFile) {throw MappingException::discriminatorNotAllowedForGridFS($this->name);}$this->subClasses = [];$this->discriminatorMap = [];$this->discriminatorValue = null;foreach ($map as $value => $className) {$this->discriminatorMap[$value] = $className;if ($this->name === $className) {$this->discriminatorValue = $value;} else {if (! class_exists($className)) {throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);}if (is_subclass_of($className, $this->name)) {$this->subClasses[] = $className;}}}}/*** Sets the default discriminator value to be used for this class* Used for SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value** @throws MappingException*/public function setDefaultDiscriminatorValue(?string $defaultDiscriminatorValue): void{if ($this->isFile) {throw MappingException::discriminatorNotAllowedForGridFS($this->name);}if ($defaultDiscriminatorValue === null) {$this->defaultDiscriminatorValue = null;return;}if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);}$this->defaultDiscriminatorValue = $defaultDiscriminatorValue;}/*** Sets the discriminator value for this class.* Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single* collection.** @throws MappingException*/public function setDiscriminatorValue(string $value): void{if ($this->isFile) {throw MappingException::discriminatorNotAllowedForGridFS($this->name);}$this->discriminatorMap[$value] = $this->name;$this->discriminatorValue = $value;}/*** Add a index for this Document.** @param array<string, int|string> $keys* @psalm-param IndexKeys $keys* @psalm-param IndexOptions $options*/public function addIndex(array $keys, array $options = []): void{$this->indexes[] = ['keys' => array_map(static function ($value) {if ($value === 1 || $value === -1) {return $value;}if (is_string($value)) {$lower = strtolower($value);if ($lower === 'asc') {return 1;}if ($lower === 'desc') {return -1;}}return $value;}, $keys),'options' => $options,];}/*** Returns the array of indexes for this Document.** @psalm-return array<IndexMapping>*/public function getIndexes(): array{return $this->indexes;}/*** Checks whether this document has indexes or not.*/public function hasIndexes(): bool{return $this->indexes !== [];}/*** Set shard key for this Document.** @param array<string, string|int> $keys* @param array<string, mixed> $options* @psalm-param ShardKeys $keys* @psalm-param ShardOptions $options** @throws MappingException*/public function setShardKey(array $keys, array $options = []): void{if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== []) {throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());}if ($this->isEmbeddedDocument) {throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());}foreach (array_keys($keys) as $field) {if (! isset($this->fieldMappings[$field])) {continue;}if (in_array($this->fieldMappings[$field]['type'], [self::MANY, Type::COLLECTION])) {throw MappingException::noMultiKeyShardKeys($this->getName(), $field);}if ($this->fieldMappings[$field]['strategy'] !== self::STORAGE_STRATEGY_SET) {throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);}}$this->shardKey = ['keys' => array_map(static function ($value) {if ($value === 1 || $value === -1) {return $value;}if (is_string($value)) {$lower = strtolower($value);if ($lower === 'asc') {return 1;}if ($lower === 'desc') {return -1;}}return $value;}, $keys),'options' => $options,];}/*** @psalm-return ShardKey*/public function getShardKey(): array{return $this->shardKey;}/*** Checks whether this document has shard key or not.*/public function isSharded(): bool{return $this->shardKey !== [];}/*** @return array|object|null* @psalm-return array<string, mixed>|object|null*/public function getValidator(){return $this->validator;}/*** @param array|object|null $validator* @psalm-param array<string, mixed>|object|null $validator*/public function setValidator($validator): void{$this->validator = $validator;}public function getValidationAction(): string{return $this->validationAction;}public function setValidationAction(string $validationAction): void{$this->validationAction = $validationAction;}public function getValidationLevel(): string{return $this->validationLevel;}public function setValidationLevel(string $validationLevel): void{$this->validationLevel = $validationLevel;}/*** Sets the read preference used by this class.** @param array<array<string, string>> $tags*/public function setReadPreference(?string $readPreference, array $tags): void{$this->readPreference = $readPreference;$this->readPreferenceTags = $tags;}/*** Sets the write concern used by this class.** @param string|int|null $writeConcern*/public function setWriteConcern($writeConcern): void{$this->writeConcern = $writeConcern;}/*** @return int|string|null*/public function getWriteConcern(){return $this->writeConcern;}/*** Whether there is a write concern configured for this class.*/public function hasWriteConcern(): bool{return $this->writeConcern !== null;}/*** Sets the change tracking policy used by this class.*/public function setChangeTrackingPolicy(int $policy): void{$this->changeTrackingPolicy = $policy;}/*** Whether the change tracking policy of this class is "deferred explicit".*/public function isChangeTrackingDeferredExplicit(): bool{return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;}/*** Whether the change tracking policy of this class is "deferred implicit".*/public function isChangeTrackingDeferredImplicit(): bool{return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;}/*** Whether the change tracking policy of this class is "notify".** @deprecated This method was deprecated in doctrine/mongodb-odm 2.4. Please use DEFERRED_EXPLICIT tracking* policy and isChangeTrackingDeferredImplicit method to detect it.*/public function isChangeTrackingNotify(): bool{return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;}/*** Gets the ReflectionProperties of the mapped class.** @return ReflectionProperty[]*/public function getReflectionProperties(): array{return $this->reflFields;}/*** Gets a ReflectionProperty for a specific field of the mapped class.*/public function getReflectionProperty(string $name): ReflectionProperty{return $this->reflFields[$name];}/*** @psalm-return class-string<T>*/public function getName(): string{return $this->name;}/*** Returns the database this Document is mapped to.*/public function getDatabase(): ?string{return $this->db;}/*** Set the database this Document is mapped to.*/public function setDatabase(?string $db): void{$this->db = $db;}/*** Get the collection this Document is mapped to.*/public function getCollection(): string{return $this->collection;}/*** Sets the collection this Document is mapped to.** @param array|string $name* @psalm-param array{name: string, capped?: bool, size?: int, max?: int}|string $name** @throws InvalidArgumentException*/public function setCollection($name): void{if (is_array($name)) {if (! isset($name['name'])) {throw new InvalidArgumentException('A name key is required when passing an array to setCollection()');}$this->collectionCapped = $name['capped'] ?? false;$this->collectionSize = $name['size'] ?? 0;$this->collectionMax = $name['max'] ?? 0;$this->collection = $name['name'];} else {$this->collection = $name;}}public function getBucketName(): ?string{return $this->bucketName;}public function setBucketName(string $bucketName): void{$this->bucketName = $bucketName;$this->setCollection($bucketName . '.files');}public function getChunkSizeBytes(): ?int{return $this->chunkSizeBytes;}public function setChunkSizeBytes(int $chunkSizeBytes): void{$this->chunkSizeBytes = $chunkSizeBytes;}/*** Get whether or not the documents collection is capped.*/public function getCollectionCapped(): bool{return $this->collectionCapped;}/*** Set whether or not the documents collection is capped.*/public function setCollectionCapped(bool $bool): void{$this->collectionCapped = $bool;}/*** Get the collection size*/public function getCollectionSize(): ?int{return $this->collectionSize;}/*** Set the collection size.*/public function setCollectionSize(int $size): void{$this->collectionSize = $size;}/*** Get the collection max.*/public function getCollectionMax(): ?int{return $this->collectionMax;}/*** Set the collection max.*/public function setCollectionMax(int $max): void{$this->collectionMax = $max;}/*** Returns TRUE if this Document is mapped to a collection FALSE otherwise.*/public function isMappedToCollection(): bool{return $this->collection !== '' && $this->collection !== null;}/*** Validates the storage strategy of a mapping for consistency** @psalm-param FieldMappingConfig $mapping** @throws MappingException*/private function applyStorageStrategy(array &$mapping): void{if (! isset($mapping['type']) || isset($mapping['id'])) {return;}switch (true) {case $mapping['type'] === self::MANY:$defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;$allowedStrategies = [self::STORAGE_STRATEGY_PUSH_ALL,self::STORAGE_STRATEGY_ADD_TO_SET,self::STORAGE_STRATEGY_SET,self::STORAGE_STRATEGY_SET_ARRAY,self::STORAGE_STRATEGY_ATOMIC_SET,self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,];break;case $mapping['type'] === self::ONE:$defaultStrategy = self::STORAGE_STRATEGY_SET;$allowedStrategies = [self::STORAGE_STRATEGY_SET];break;default:$defaultStrategy = self::STORAGE_STRATEGY_SET;$allowedStrategies = [self::STORAGE_STRATEGY_SET];$type = Type::getType($mapping['type']);if ($type instanceof Incrementable) {$allowedStrategies[] = self::STORAGE_STRATEGY_INCREMENT;}}if (! isset($mapping['strategy'])) {$mapping['strategy'] = $defaultStrategy;}if (! in_array($mapping['strategy'], $allowedStrategies)) {throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);}if (isset($mapping['reference']) && $mapping['type'] === self::MANY && $mapping['isOwningSide']&& ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);}}/*** Map a single embedded document.** @psalm-param FieldMappingConfig $mapping*/public function mapOneEmbedded(array $mapping): void{$mapping['embedded'] = true;$mapping['type'] = self::ONE;$this->mapField($mapping);}/*** Map a collection of embedded documents.** @psalm-param FieldMappingConfig $mapping*/public function mapManyEmbedded(array $mapping): void{$mapping['embedded'] = true;$mapping['type'] = self::MANY;$this->mapField($mapping);}/*** Map a single document reference.** @psalm-param FieldMappingConfig $mapping*/public function mapOneReference(array $mapping): void{$mapping['reference'] = true;$mapping['type'] = self::ONE;$this->mapField($mapping);}/*** Map a collection of document references.** @psalm-param FieldMappingConfig $mapping*/public function mapManyReference(array $mapping): void{$mapping['reference'] = true;$mapping['type'] = self::MANY;$this->mapField($mapping);}/*** Adds a field mapping without completing/validating it.* This is mainly used to add inherited field mappings to derived classes.** @internal** @psalm-param FieldMapping $fieldMapping*/public function addInheritedFieldMapping(array $fieldMapping): void{$this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;if (! isset($fieldMapping['association'])) {return;}$this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;}/*** Adds an association mapping without completing/validating it.* This is mainly used to add inherited association mappings to derived classes.** @internal** @psalm-param AssociationFieldMapping $mapping** @throws MappingException*/public function addInheritedAssociationMapping(array $mapping): void{$this->associationMappings[$mapping['fieldName']] = $mapping;}/*** Checks whether the class has a mapped association with the given field name.*/public function hasReference(string $fieldName): bool{return isset($this->fieldMappings[$fieldName]['reference']);}/*** Checks whether the class has a mapped embed with the given field name.*/public function hasEmbed(string $fieldName): bool{return isset($this->fieldMappings[$fieldName]['embedded']);}/*** Checks whether the class has a mapped association (embed or reference) with the given field name.** @param string $fieldName*/public function hasAssociation($fieldName): bool{return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);}/*** Checks whether the class has a mapped reference or embed for the specified field and* is a single valued association.** @param string $fieldName*/public function isSingleValuedAssociation($fieldName): bool{return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);}/*** Checks whether the class has a mapped reference or embed for the specified field and* is a collection valued association.** @param string $fieldName*/public function isCollectionValuedAssociation($fieldName): bool{return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);}/*** Checks whether the class has a mapped association for the specified field* and if yes, checks whether it is a single-valued association (to-one).*/public function isSingleValuedReference(string $fieldName): bool{return isset($this->fieldMappings[$fieldName]['association']) &&$this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;}/*** Checks whether the class has a mapped association for the specified field* and if yes, checks whether it is a collection-valued association (to-many).*/public function isCollectionValuedReference(string $fieldName): bool{return isset($this->fieldMappings[$fieldName]['association']) &&$this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;}/*** Checks whether the class has a mapped embedded document for the specified field* and if yes, checks whether it is a single-valued association (to-one).*/public function isSingleValuedEmbed(string $fieldName): bool{return isset($this->fieldMappings[$fieldName]['association']) &&$this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;}/*** Checks whether the class has a mapped embedded document for the specified field* and if yes, checks whether it is a collection-valued association (to-many).*/public function isCollectionValuedEmbed(string $fieldName): bool{return isset($this->fieldMappings[$fieldName]['association']) &&$this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;}/*** Sets the ID generator used to generate IDs for instances of this class.*/public function setIdGenerator(IdGenerator $generator): void{$this->idGenerator = $generator;}/*** Casts the identifier to its portable PHP type.** @param mixed $id** @return mixed $id*/public function getPHPIdentifierValue($id){$idType = $this->fieldMappings[$this->identifier]['type'];return Type::getType($idType)->convertToPHPValue($id);}/*** Casts the identifier to its database type.** @param mixed $id** @return mixed $id*/public function getDatabaseIdentifierValue($id){$idType = $this->fieldMappings[$this->identifier]['type'];return Type::getType($idType)->convertToDatabaseValue($id);}/*** Sets the document identifier of a document.** The value will be converted to a PHP type before being set.** @param mixed $id*/public function setIdentifierValue(object $document, $id): void{$id = $this->getPHPIdentifierValue($id);$this->reflFields[$this->identifier]->setValue($document, $id);}/*** Gets the document identifier as a PHP type.** @return mixed $id*/public function getIdentifierValue(object $document){return $this->reflFields[$this->identifier]->getValue($document);}/*** Since MongoDB only allows exactly one identifier field this is a proxy* to {@see getIdentifierValue()} and returns an array with the identifier* field as a key.** @param object $object*/public function getIdentifierValues($object): array{return [$this->identifier => $this->getIdentifierValue($object)];}/*** Get the document identifier object as a database type.** @return mixed $id*/public function getIdentifierObject(object $document){return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));}/*** Sets the specified field to the specified value on the given document.** @param mixed $value*/public function setFieldValue(object $document, string $field, $value): void{if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {//property changes to an uninitialized proxy will not be tracked or persisted,//so the proxy needs to be loaded first.$document->initializeProxy();}$this->reflFields[$field]->setValue($document, $value);}/*** Gets the specified field's value off the given document.** @return mixed*/public function getFieldValue(object $document, string $field){if ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) {$document->initializeProxy();}return $this->reflFields[$field]->getValue($document);}/*** Gets the mapping of a field.** @psalm-return FieldMapping** @throws MappingException If the $fieldName is not found in the fieldMappings array.*/public function getFieldMapping(string $fieldName): array{if (! isset($this->fieldMappings[$fieldName])) {throw MappingException::mappingNotFound($this->name, $fieldName);}return $this->fieldMappings[$fieldName];}/*** Gets mappings of fields holding embedded document(s).** @psalm-return array<string, FieldMapping>*/public function getEmbeddedFieldsMappings(): array{return array_filter($this->associationMappings,static function ($assoc) {return ! empty($assoc['embedded']);});}/*** Gets the field mapping by its DB name.* E.g. it returns identifier's mapping when called with _id.** @psalm-return FieldMapping** @throws MappingException*/public function getFieldMappingByDbFieldName(string $dbFieldName): array{foreach ($this->fieldMappings as $mapping) {if ($mapping['name'] === $dbFieldName) {return $mapping;}}throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);}/*** Check if the field is not null.*/public function isNullable(string $fieldName): bool{$mapping = $this->getFieldMapping($fieldName);return isset($mapping['nullable']) && $mapping['nullable'] === true;}/*** Checks whether the document has a discriminator field and value configured.*/public function hasDiscriminator(): bool{return isset($this->discriminatorField, $this->discriminatorValue);}/*** Sets the type of Id generator to use for the mapped class.*/public function setIdGeneratorType(int $generatorType): void{$this->generatorType = $generatorType;}/*** Sets the Id generator options.** @param array<string, mixed> $generatorOptions*/public function setIdGeneratorOptions(array $generatorOptions): void{$this->generatorOptions = $generatorOptions;}public function isInheritanceTypeNone(): bool{return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;}/*** Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.*/public function isInheritanceTypeSingleCollection(): bool{return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;}/*** Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.*/public function isInheritanceTypeCollectionPerClass(): bool{return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;}/*** Sets the mapped subclasses of this class.** @param string[] $subclasses The names of all mapped subclasses.* @psalm-param class-string[] $subclasses*/public function setSubclasses(array $subclasses): void{foreach ($subclasses as $subclass) {$this->subClasses[] = $subclass;}}/*** Sets the parent class names.* Assumes that the class names in the passed array are in the order:* directParent -> directParentParent -> directParentParentParent ... -> root.** @param string[] $classNames* @psalm-param list<class-string> $classNames*/public function setParentClasses(array $classNames): void{$this->parentClasses = $classNames;if (count($classNames) <= 0) {return;}$this->rootDocumentName = (string) array_pop($classNames);}/*** Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.*/public function isIdGeneratorAuto(): bool{return $this->generatorType === self::GENERATOR_TYPE_AUTO;}/*** Checks whether the class will use a collection to generate incremented identifiers.*/public function isIdGeneratorIncrement(): bool{return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;}/*** Checks whether the class will generate a uuid id.*/public function isIdGeneratorUuid(): bool{return $this->generatorType === self::GENERATOR_TYPE_UUID;}/*** Checks whether the class uses no id generator.*/public function isIdGeneratorNone(): bool{return $this->generatorType === self::GENERATOR_TYPE_NONE;}/*** Sets the version field mapping used for versioning. Sets the default* value to use depending on the column type.** @psalm-param FieldMapping $mapping** @throws LockException*/public function setVersionMapping(array &$mapping): void{if (! Type::getType($mapping['type']) instanceof Versionable) {throw LockException::invalidVersionFieldType($mapping['type']);}$this->isVersioned = true;$this->versionField = $mapping['fieldName'];}/*** Sets whether this class is to be versioned for optimistic locking.*/public function setVersioned(bool $bool): void{$this->isVersioned = $bool;}/*** Sets the name of the field that is to be used for versioning if this class is* versioned for optimistic locking.*/public function setVersionField(?string $versionField): void{$this->versionField = $versionField;}/*** Sets the version field mapping used for versioning. Sets the default* value to use depending on the column type.** @psalm-param FieldMapping $mapping** @throws LockException*/public function setLockMapping(array &$mapping): void{if ($mapping['type'] !== 'int') {throw LockException::invalidLockFieldType($mapping['type']);}$this->isLockable = true;$this->lockField = $mapping['fieldName'];}/*** Sets whether this class is to allow pessimistic locking.*/public function setLockable(bool $bool): void{$this->isLockable = $bool;}/*** Sets the name of the field that is to be used for storing whether a document* is currently locked or not.*/public function setLockField(string $lockField): void{$this->lockField = $lockField;}/*** Marks this class as read only, no change tracking is applied to it.*/public function markReadOnly(): void{$this->isReadOnly = true;}public function getRootClass(): ?string{return $this->rootClass;}public function isView(): bool{return $this->isView;}/*** @psalm-param class-string $rootClass*/public function markViewOf(string $rootClass): void{$this->isView = true;$this->rootClass = $rootClass;}public function getFieldNames(): array{return array_keys($this->fieldMappings);}public function getAssociationNames(): array{return array_keys($this->associationMappings);}/*** @param string $fieldName*/public function getTypeOfField($fieldName): ?string{return isset($this->fieldMappings[$fieldName]) ?$this->fieldMappings[$fieldName]['type'] : null;}/*** @param string $assocName** @psalm-return class-string|null*/public function getAssociationTargetClass($assocName): ?string{if (! isset($this->associationMappings[$assocName])) {throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");}return $this->associationMappings[$assocName]['targetDocument'] ?? null;}/*** Retrieve the collectionClass associated with an association** @psalm-return class-string*/public function getAssociationCollectionClass(string $assocName): string{if (! isset($this->associationMappings[$assocName])) {throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");}if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");}return $this->associationMappings[$assocName]['collectionClass'];}/*** @param string $assocName*/public function isAssociationInverseSide($assocName): bool{throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');}/*** @param string $assocName*/public function getAssociationMappedByTargetField($assocName){throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');}/*** Map a field.** @psalm-param FieldMappingConfig $mapping** @psalm-return FieldMapping** @throws MappingException*/public function mapField(array $mapping): array{if (! isset($mapping['fieldName']) && isset($mapping['name'])) {$mapping['fieldName'] = $mapping['name'];}if ($this->isTypedProperty($mapping['fieldName'])) {$mapping = $this->validateAndCompleteTypedFieldMapping($mapping);if (isset($mapping['type']) && $mapping['type'] === self::MANY) {$mapping = $this->validateAndCompleteTypedManyAssociationMapping($mapping);}}if (! isset($mapping['fieldName']) || ! is_string($mapping['fieldName'])) {throw MappingException::missingFieldName($this->name);}if (! isset($mapping['name'])) {$mapping['name'] = $mapping['fieldName'];}if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {throw MappingException::mustNotChangeIdentifierFieldsType($this->name, (string) $mapping['name']);}if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);}if (isset($mapping['collectionClass'])) {$mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');}if (! empty($mapping['collectionClass'])) {$rColl = new ReflectionClass($mapping['collectionClass']);if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);}}if (isset($mapping['cascade']) && isset($mapping['embedded'])) {throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);}$cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];if (in_array('all', $cascades) || isset($mapping['embedded'])) {$cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];}if (isset($mapping['embedded'])) {unset($mapping['cascade']);} elseif (isset($mapping['cascade'])) {$mapping['cascade'] = $cascades;}$mapping['isCascadeRemove'] = in_array('remove', $cascades);$mapping['isCascadePersist'] = in_array('persist', $cascades);$mapping['isCascadeRefresh'] = in_array('refresh', $cascades);$mapping['isCascadeMerge'] = in_array('merge', $cascades);$mapping['isCascadeDetach'] = in_array('detach', $cascades);if (isset($mapping['id']) && $mapping['id'] === true) {$mapping['name'] = '_id';$this->identifier = $mapping['fieldName'];if (isset($mapping['strategy'])) {$this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));}$this->generatorOptions = $mapping['options'] ?? [];switch ($this->generatorType) {case self::GENERATOR_TYPE_AUTO:$mapping['type'] = 'id';break;default:if (! empty($this->generatorOptions['type'])) {$mapping['type'] = (string) $this->generatorOptions['type'];} elseif (empty($mapping['type'])) {$mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? Type::INT : Type::CUSTOMID;}}unset($this->generatorOptions['type']);}if (! isset($mapping['type'])) {// Default to string$mapping['type'] = Type::STRING;}if (! isset($mapping['nullable'])) {$mapping['nullable'] = false;}if (isset($mapping['reference'])&& isset($mapping['storeAs'])&& $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID&& ! isset($mapping['targetDocument'])) {throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);}if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&(isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);}if ($this->isEmbeddedDocument && $mapping['type'] === self::MANY && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);}if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);}if (isset($mapping['targetDocument']) && isset($mapping['discriminatorMap'])) {trigger_deprecation('doctrine/mongodb-odm','2.2','Mapping both "targetDocument" and "discriminatorMap" on field "%s" in class "%s" is deprecated. Only one of them can be used at a time',$mapping['fieldName'],$this->name);}if (isset($mapping['reference']) && $mapping['type'] === self::ONE) {$mapping['association'] = self::REFERENCE_ONE;}if (isset($mapping['reference']) && $mapping['type'] === self::MANY) {$mapping['association'] = self::REFERENCE_MANY;}if (isset($mapping['embedded']) && $mapping['type'] === self::ONE) {$mapping['association'] = self::EMBED_ONE;}if (isset($mapping['embedded']) && $mapping['type'] === self::MANY) {$mapping['association'] = self::EMBED_MANY;}if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {$mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;}if (isset($mapping['version'])) {$mapping['notSaved'] = true;$this->setVersionMapping($mapping);}if (isset($mapping['lock'])) {$mapping['notSaved'] = true;$this->setLockMapping($mapping);}$mapping['isOwningSide'] = true;$mapping['isInverseSide'] = false;if (isset($mapping['reference'])) {if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {$mapping['isOwningSide'] = true;$mapping['isInverseSide'] = false;}if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {$mapping['isInverseSide'] = true;$mapping['isOwningSide'] = false;}if (isset($mapping['repositoryMethod'])) {$mapping['isInverseSide'] = true;$mapping['isOwningSide'] = false;}if (! isset($mapping['orphanRemoval'])) {$mapping['orphanRemoval'] = false;}}if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);}if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);}$this->applyStorageStrategy($mapping);$this->checkDuplicateMapping($mapping);$this->typeRequirementsAreMet($mapping);$deprecatedTypes = [Type::BOOLEAN => Type::BOOL,Type::INTEGER => Type::INT,Type::INTID => Type::INT,];if (isset($deprecatedTypes[$mapping['type']])) {trigger_deprecation('doctrine/mongodb-odm','2.1','The "%s" mapping type is deprecated. Use "%s" instead.',$mapping['type'],$deprecatedTypes[$mapping['type']]);}$this->fieldMappings[$mapping['fieldName']] = $mapping;if (isset($mapping['association'])) {$this->associationMappings[$mapping['fieldName']] = $mapping;}$reflProp = $this->reflectionService->getAccessibleProperty($this->name, $mapping['fieldName']);assert($reflProp instanceof ReflectionProperty);if (isset($mapping['enumType'])) {if (PHP_VERSION_ID < 80100) {throw MappingException::enumsRequirePhp81($this->name, $mapping['fieldName']);}if (! enum_exists($mapping['enumType'])) {throw MappingException::nonEnumTypeMapped($this->name, $mapping['fieldName'], $mapping['enumType']);}$reflectionEnum = new ReflectionEnum($mapping['enumType']);if (! $reflectionEnum->isBacked()) {throw MappingException::nonBackedEnumMapped($this->name, $mapping['fieldName'], $mapping['enumType']);}$reflProp = new EnumReflectionProperty($reflProp, $mapping['enumType']);}$this->reflFields[$mapping['fieldName']] = $reflProp;return $mapping;}/*** Determines which fields get serialized.** It is only serialized what is necessary for best unserialization performance.* That means any metadata properties that are not set or empty or simply have* their default value are NOT serialized.** Parts that are also NOT serialized because they can not be properly unserialized:* - reflClass (ReflectionClass)* - reflFields (ReflectionProperty array)** @return array The names of all the fields that should be serialized.*/public function __sleep(){// This metadata is always serialized/cached.$serialized = ['fieldMappings','associationMappings','identifier','name','db','collection','readPreference','readPreferenceTags','writeConcern','rootDocumentName','generatorType','generatorOptions','idGenerator','indexes','shardKey',];// The rest of the metadata is only serialized if necessary.if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {$serialized[] = 'changeTrackingPolicy';}if ($this->customRepositoryClassName) {$serialized[] = 'customRepositoryClassName';}if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {$serialized[] = 'inheritanceType';$serialized[] = 'discriminatorField';$serialized[] = 'discriminatorValue';$serialized[] = 'discriminatorMap';$serialized[] = 'defaultDiscriminatorValue';$serialized[] = 'parentClasses';$serialized[] = 'subClasses';}if ($this->isMappedSuperclass) {$serialized[] = 'isMappedSuperclass';}if ($this->isEmbeddedDocument) {$serialized[] = 'isEmbeddedDocument';}if ($this->isQueryResultDocument) {$serialized[] = 'isQueryResultDocument';}if ($this->isView()) {$serialized[] = 'isView';$serialized[] = 'rootClass';}if ($this->isFile) {$serialized[] = 'isFile';$serialized[] = 'bucketName';$serialized[] = 'chunkSizeBytes';}if ($this->isVersioned) {$serialized[] = 'isVersioned';$serialized[] = 'versionField';}if ($this->isLockable) {$serialized[] = 'isLockable';$serialized[] = 'lockField';}if ($this->lifecycleCallbacks) {$serialized[] = 'lifecycleCallbacks';}if ($this->collectionCapped) {$serialized[] = 'collectionCapped';$serialized[] = 'collectionSize';$serialized[] = 'collectionMax';}if ($this->isReadOnly) {$serialized[] = 'isReadOnly';}if ($this->validator !== null) {$serialized[] = 'validator';$serialized[] = 'validationAction';$serialized[] = 'validationLevel';}return $serialized;}/*** Restores some state that can not be serialized/unserialized.*/public function __wakeup(){// Restore ReflectionClass and properties$this->reflectionService = new RuntimeReflectionService();$this->reflClass = new ReflectionClass($this->name);$this->instantiator = new Instantiator();foreach ($this->fieldMappings as $field => $mapping) {$prop = $this->reflectionService->getAccessibleProperty($mapping['declared'] ?? $this->name, $field);assert($prop instanceof ReflectionProperty);if (isset($mapping['enumType'])) {$prop = new EnumReflectionProperty($prop, $mapping['enumType']);}$this->reflFields[$field] = $prop;}}/*** Creates a new instance of the mapped class, without invoking the constructor.** @psalm-return T*/public function newInstance(): object{/** @psalm-var T */return $this->instantiator->instantiate($this->name);}private function isAllowedGridFSField(string $name): bool{return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);}/*** @psalm-param FieldMapping $mapping*/private function typeRequirementsAreMet(array $mapping): void{if ($mapping['type'] === Type::DECIMAL128 && ! extension_loaded('bcmath')) {throw MappingException::typeRequirementsNotFulfilled($this->name, $mapping['fieldName'], Type::DECIMAL128, 'ext-bcmath is missing');}}/*** @psalm-param FieldMapping $mapping*/private function checkDuplicateMapping(array $mapping): void{if ($mapping['notSaved'] ?? false) {return;}foreach ($this->fieldMappings as $fieldName => $otherMapping) {// Ignore fields with the same name - we can safely override their mappingif ($mapping['fieldName'] === $fieldName) {continue;}// Ignore fields with a different name in the databaseif ($mapping['name'] !== $otherMapping['name']) {continue;}// If the other field is not saved, ignore it as wellif ($otherMapping['notSaved'] ?? false) {continue;}throw MappingException::duplicateDatabaseFieldName($this->getName(), $mapping['fieldName'], $mapping['name'], $fieldName);}}private function isTypedProperty(string $name): bool{return PHP_VERSION_ID >= 70400&& $this->reflClass->hasProperty($name)&& $this->reflClass->getProperty($name)->hasType();}/*** Validates & completes the given field mapping based on typed property.** @psalm-param FieldMappingConfig $mapping** @return FieldMappingConfig*/private function validateAndCompleteTypedFieldMapping(array $mapping): array{$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();if (! $type instanceof ReflectionNamedType || isset($mapping['type'])) {return $mapping;}if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {$mapping['enumType'] = $type->getName();$reflection = new ReflectionEnum($type->getName());$type = $reflection->getBackingType();if ($type === null) {throw MappingException::nonBackedEnumMapped($this->name, $mapping['fieldName'], $mapping['enumType']);}assert($type instanceof ReflectionNamedType);}switch ($type->getName()) {case DateTime::class:$mapping['type'] = Type::DATE;break;case DateTimeImmutable::class:$mapping['type'] = Type::DATE_IMMUTABLE;break;case 'array':$mapping['type'] = Type::HASH;break;case 'bool':$mapping['type'] = Type::BOOL;break;case 'float':$mapping['type'] = Type::FLOAT;break;case 'int':$mapping['type'] = Type::INT;break;case 'string':$mapping['type'] = Type::STRING;break;}return $mapping;}/*** Validates & completes the basic mapping information based on typed property.** @psalm-param FieldMappingConfig $mapping** @return FieldMappingConfig*/private function validateAndCompleteTypedManyAssociationMapping(array $mapping): array{$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();if (! $type instanceof ReflectionNamedType) {return $mapping;}if (! isset($mapping['collectionClass']) && class_exists($type->getName())) {$mapping['collectionClass'] = $type->getName();}return $mapping;}}