vendor/doctrine/mongodb-odm/src/Mapping/ClassMetadata.php line 2593

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ODM\MongoDB\Mapping;
  4. use BackedEnum;
  5. use BadMethodCallException;
  6. use DateTime;
  7. use DateTimeImmutable;
  8. use Doctrine\Common\Collections\Collection;
  9. use Doctrine\Instantiator\Instantiator;
  10. use Doctrine\Instantiator\InstantiatorInterface;
  11. use Doctrine\ODM\MongoDB\Id\IdGenerator;
  12. use Doctrine\ODM\MongoDB\LockException;
  13. use Doctrine\ODM\MongoDB\Mapping\Annotations\EncryptQuery;
  14. use Doctrine\ODM\MongoDB\Mapping\Annotations\TimeSeries;
  15. use Doctrine\ODM\MongoDB\Mapping\PropertyAccessors\EnumPropertyAccessor;
  16. use Doctrine\ODM\MongoDB\Mapping\PropertyAccessors\PropertyAccessor;
  17. use Doctrine\ODM\MongoDB\Mapping\PropertyAccessors\PropertyAccessorFactory;
  18. use Doctrine\ODM\MongoDB\Proxy\InternalProxy;
  19. use Doctrine\ODM\MongoDB\Types\Incrementable;
  20. use Doctrine\ODM\MongoDB\Types\Type;
  21. use Doctrine\ODM\MongoDB\Types\Versionable;
  22. use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
  23. use Doctrine\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
  24. use Doctrine\Persistence\Mapping\ReflectionService;
  25. use Doctrine\Persistence\Mapping\RuntimeReflectionService;
  26. use InvalidArgumentException;
  27. use LogicException;
  28. use MongoDB\BSON\Decimal128;
  29. use MongoDB\BSON\Int64;
  30. use MongoDB\BSON\UTCDateTime;
  31. use ProxyManager\Proxy\GhostObjectInterface;
  32. use ReflectionClass;
  33. use ReflectionEnum;
  34. use ReflectionNamedType;
  35. use ReflectionProperty;
  36. use Symfony\Component\Uid\UuidV1;
  37. use Symfony\Component\Uid\UuidV4;
  38. use Symfony\Component\Uid\UuidV7;
  39. use function array_column;
  40. use function array_filter;
  41. use function array_key_exists;
  42. use function array_keys;
  43. use function array_map;
  44. use function array_pop;
  45. use function assert;
  46. use function class_exists;
  47. use function constant;
  48. use function count;
  49. use function enum_exists;
  50. use function extension_loaded;
  51. use function in_array;
  52. use function interface_exists;
  53. use function is_array;
  54. use function is_string;
  55. use function is_subclass_of;
  56. use function ltrim;
  57. use function sprintf;
  58. use function strtolower;
  59. use function strtoupper;
  60. use function trigger_deprecation;
  61. use const PHP_VERSION_ID;
  62. /**
  63.  * A <tt>ClassMetadata</tt> instance holds all the object-document mapping metadata
  64.  * of a document and it's references.
  65.  *
  66.  * Once populated, ClassMetadata instances are usually cached in a serialized form.
  67.  *
  68.  * <b>IMPORTANT NOTE:</b>
  69.  *
  70.  * The fields of this class are only public for 2 reasons:
  71.  * 1) To allow fast READ access.
  72.  * 2) To drastically reduce the size of a serialized instance (private/protected members
  73.  *    get the whole class name, namespace inclusive, prepended to every property in
  74.  *    the serialized representation).
  75.  *
  76.  * The EncryptConfig type is identical to the {@see Encrypt} attribute.
  77.  *
  78.  * @phpstan-type EncryptConfig array{
  79.  *     queryType?: ?EncryptQuery,
  80.  *     min?: float|int|Decimal128|Int64|UTCDateTime|null,
  81.  *     max?: float|int|Decimal128|Int64|UTCDateTime|null,
  82.  *     sparsity?: int<1, 4>,
  83.  *     precision?: positive-int,
  84.  *     trimFactor?: positive-int,
  85.  *     contention?: positive-int,
  86.  * }
  87.  * @phpstan-type FieldMappingConfig array{
  88.  *      type?: string,
  89.  *      fieldName?: string,
  90.  *      name?: string,
  91.  *      strategy?: string,
  92.  *      association?: int,
  93.  *      id?: bool,
  94.  *      isOwningSide?: bool,
  95.  *      collectionClass?: class-string,
  96.  *      cascade?: list<string>|string,
  97.  *      embedded?: bool,
  98.  *      orphanRemoval?: bool,
  99.  *      options?: array<string, mixed>,
  100.  *      nullable?: bool,
  101.  *      reference?: bool,
  102.  *      storeAs?: string,
  103.  *      targetDocument?: class-string|null,
  104.  *      mappedBy?: string|null,
  105.  *      inversedBy?: string|null,
  106.  *      discriminatorField?: string,
  107.  *      defaultDiscriminatorValue?: string,
  108.  *      discriminatorMap?: array<string, class-string>,
  109.  *      repositoryMethod?: string|null,
  110.  *      sort?: array<string, string|int>,
  111.  *      limit?: int|null,
  112.  *      skip?: int|null,
  113.  *      version?: bool,
  114.  *      lock?: bool,
  115.  *      inherited?: string,
  116.  *      declared?: class-string,
  117.  *      prime?: list<string>,
  118.  *      sparse?: bool,
  119.  *      unique?: bool,
  120.  *      index?: bool,
  121.  *      index-name?: string,
  122.  *      criteria?: array<string, mixed>,
  123.  *      alsoLoadFields?: list<string>,
  124.  *      order?: int|string,
  125.  *      background?: bool,
  126.  *      enumType?: class-string<BackedEnum>,
  127.  *      encrypt?: EncryptConfig,
  128.  * }
  129.  * @phpstan-type FieldMapping array{
  130.  *      type: string,
  131.  *      fieldName: string,
  132.  *      name: string,
  133.  *      isCascadeRemove: bool,
  134.  *      isCascadePersist: bool,
  135.  *      isCascadeRefresh: bool,
  136.  *      isCascadeMerge: bool,
  137.  *      isCascadeDetach: bool,
  138.  *      isOwningSide: bool,
  139.  *      isInverseSide: bool,
  140.  *      strategy?: string,
  141.  *      association?: int,
  142.  *      id?: bool,
  143.  *      collectionClass?: class-string,
  144.  *      cascade?: list<string>|string,
  145.  *      embedded?: bool,
  146.  *      orphanRemoval?: bool,
  147.  *      options?: array<string, mixed>,
  148.  *      nullable?: bool,
  149.  *      reference?: bool,
  150.  *      storeAs?: string,
  151.  *      targetDocument?: class-string|null,
  152.  *      mappedBy?: string|null,
  153.  *      inversedBy?: string|null,
  154.  *      discriminatorField?: string,
  155.  *      defaultDiscriminatorValue?: string,
  156.  *      discriminatorMap?: array<string, class-string>,
  157.  *      repositoryMethod?: string|null,
  158.  *      sort?: array<string, string|int>,
  159.  *      limit?: int|null,
  160.  *      skip?: int|null,
  161.  *      version?: bool,
  162.  *      lock?: bool,
  163.  *      notSaved?: bool,
  164.  *      inherited?: string,
  165.  *      declared?: class-string,
  166.  *      prime?: list<string>,
  167.  *      sparse?: bool,
  168.  *      unique?: bool,
  169.  *      index?: bool,
  170.  *      criteria?: array<string, mixed>,
  171.  *      alsoLoadFields?: list<string>,
  172.  *      enumType?: class-string<BackedEnum>,
  173.  *      storeEmptyArray?: bool,
  174.  *      encrypt?: EncryptConfig,
  175.  * }
  176.  * @phpstan-type AssociationFieldMapping array{
  177.  *      type?: string,
  178.  *      fieldName: string,
  179.  *      name: string,
  180.  *      isCascadeRemove: bool,
  181.  *      isCascadePersist: bool,
  182.  *      isCascadeRefresh: bool,
  183.  *      isCascadeMerge: bool,
  184.  *      isCascadeDetach: bool,
  185.  *      isOwningSide: bool,
  186.  *      isInverseSide: bool,
  187.  *      targetDocument: class-string|null,
  188.  *      association: int,
  189.  *      strategy?: string,
  190.  *      id?: bool,
  191.  *      collectionClass?: class-string,
  192.  *      cascade?: list<string>|string,
  193.  *      embedded?: bool,
  194.  *      orphanRemoval?: bool,
  195.  *      options?: array<string, mixed>,
  196.  *      nullable?: bool,
  197.  *      reference?: bool,
  198.  *      storeAs?: string,
  199.  *      mappedBy?: string|null,
  200.  *      inversedBy?: string|null,
  201.  *      discriminatorField?: string,
  202.  *      defaultDiscriminatorValue?: string,
  203.  *      discriminatorMap?: array<string, class-string>,
  204.  *      repositoryMethod?: string|null,
  205.  *      sort?: array<string, string|int>,
  206.  *      limit?: int|null,
  207.  *      skip?: int|null,
  208.  *      version?: bool,
  209.  *      lock?: bool,
  210.  *      notSaved?: bool,
  211.  *      inherited?: string,
  212.  *      declared?: class-string,
  213.  *      prime?: list<string>,
  214.  *      sparse?: bool,
  215.  *      unique?: bool,
  216.  *      index?: bool,
  217.  *      criteria?: array<string, mixed>,
  218.  *      alsoLoadFields?: list<string>,
  219.  *      storeEmptyArray?: bool,
  220.  * }
  221.  * @phpstan-type IndexKeys array<string, mixed>
  222.  * @phpstan-type IndexOptions array{
  223.  *      background?: bool,
  224.  *      bits?: int,
  225.  *      default_language?: string,
  226.  *      expireAfterSeconds?: int,
  227.  *      language_override?: string,
  228.  *      min?: float,
  229.  *      max?: float,
  230.  *      name?: string,
  231.  *      partialFilterExpression?: mixed[],
  232.  *      sparse?: bool,
  233.  *      storageEngine?: mixed[],
  234.  *      textIndexVersion?: int,
  235.  *      unique?: bool,
  236.  *      weights?: array{string, int},
  237.  * }
  238.  * @phpstan-type IndexMapping array{
  239.  *      keys: IndexKeys,
  240.  *      options: IndexOptions
  241.  * }
  242.  * @phpstan-type SearchIndexStoredSourceInclude array{include: list<string>}
  243.  * @phpstan-type SearchIndexStoredSourceExclude array{exclude: list<string>}
  244.  * @phpstan-type SearchIndexStoredSource bool|SearchIndexStoredSourceInclude|SearchIndexStoredSourceExclude
  245.  * @phpstan-type SearchIndexSynonym array{
  246.  *      analyzer: string,
  247.  *      name: string,
  248.  *      source: array{
  249.  *          collection: string,
  250.  *      },
  251.  * }
  252.  * @phpstan-type SearchIndexDefinition array{
  253.  *      mappings: array{
  254.  *          dynamic?: bool,
  255.  *          fields?: array<string, array<string, mixed>>,
  256.  *      },
  257.  *      analyzer?: string,
  258.  *      searchAnalyzer?: string,
  259.  *      analyzers?: list<array<string, mixed>>,
  260.  *      storedSource?: SearchIndexStoredSource,
  261.  *      synonyms?: list<SearchIndexSynonym>,
  262.  * }
  263.  * @phpstan-type SearchIndexMapping array{
  264.  *      type: "search"|"vectorSearch",
  265.  *      name: string,
  266.  *      definition: SearchIndexDefinition
  267.  * }
  268.  * @phpstan-type VectorSearchIndexField array{
  269.  *     type: "vector"|"filter",
  270.  *     path: string,
  271.  *     numDimensions?: int,
  272.  *     similarity?: self::VECTOR_SIMILARITY_*,
  273.  *     quantization?: self::VECTOR_QUANTIZATION_*,
  274.  *     hnswOptions?: array{maxEdges?: int, numEdgeCandidates?: int}
  275.  * }
  276.  * @phpstan-type VectorSearchIndexDefinition array{
  277.  *     fields: list<VectorSearchIndexField>
  278.  * }
  279.  * @phpstan-type ShardKeys array<string, mixed>
  280.  * @phpstan-type ShardOptions array<string, mixed>
  281.  * @phpstan-type ShardKey array{
  282.  *      keys?: ShardKeys,
  283.  *      options?: ShardOptions
  284.  * }
  285.  * @final
  286.  * @template-covariant T of object
  287.  * @template-implements BaseClassMetadata<T>
  288.  */
  289. /* final */ class ClassMetadata implements BaseClassMetadata
  290. {
  291.     /* The Id generator types. */
  292.     /**
  293.      * AUTO means Doctrine will automatically create a new \MongoDB\BSON\ObjectId instance for us.
  294.      */
  295.     public const GENERATOR_TYPE_AUTO 1;
  296.     /**
  297.      * INCREMENT means a separate collection is used for maintaining and incrementing id generation.
  298.      * Offers full portability.
  299.      */
  300.     public const GENERATOR_TYPE_INCREMENT 2;
  301.     /**
  302.      * UUID means Doctrine will generate a uuid for us.
  303.      *
  304.      * @deprecated without replacement. Use a custom generator or switch to binary UUIDs.
  305.      */
  306.     public const GENERATOR_TYPE_UUID 3;
  307.     /**
  308.      * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
  309.      * generator to ensure identifier uniqueness
  310.      */
  311.     public const GENERATOR_TYPE_ALNUM 4;
  312.     /**
  313.      * CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class
  314.      * and pass other options to the generator. It will throw an Exception if the class
  315.      * does not exist or if an option was passed for that there is not setter in the new
  316.      * generator class.
  317.      *
  318.      * The class will have to implement IdGenerator.
  319.      */
  320.     public const GENERATOR_TYPE_CUSTOM 5;
  321.     /**
  322.      * NONE means Doctrine will not generate any id for us and you are responsible for manually
  323.      * assigning an id.
  324.      */
  325.     public const GENERATOR_TYPE_NONE 6;
  326.     /**
  327.      * Default discriminator field name.
  328.      *
  329.      * This is used for associations value for associations where a that do not define a "targetDocument" or
  330.      * "discriminatorField" option in their mapping.
  331.      */
  332.     public const DEFAULT_DISCRIMINATOR_FIELD '_doctrine_class_name';
  333.     /**
  334.      * Association types
  335.      */
  336.     public const REFERENCE_ONE  1;
  337.     public const REFERENCE_MANY 2;
  338.     public const EMBED_ONE      3;
  339.     public const EMBED_MANY     4;
  340.     /**
  341.      * Mapping types
  342.      */
  343.     public const MANY 'many';
  344.     public const ONE  'one';
  345.     /**
  346.      * The types of storeAs references
  347.      */
  348.     public const REFERENCE_STORE_AS_ID             'id';
  349.     public const REFERENCE_STORE_AS_DB_REF         'dbRef';
  350.     public const REFERENCE_STORE_AS_DB_REF_WITH_DB 'dbRefWithDb';
  351.     public const REFERENCE_STORE_AS_REF            'ref';
  352.     /**
  353.      * Rejects any insert or update that violates the validation criteria.
  354.      *
  355.      * Value for collection schema validationAction.
  356.      *
  357.      * @see https://www.mongodb.com/docs/manual/core/schema-validation/handle-invalid-documents/#option-1--reject-invalid-documents
  358.      */
  359.     public const SCHEMA_VALIDATION_ACTION_ERROR 'error';
  360.     /**
  361.      * MongoDB allows the operation to proceed, but records the violation in the MongoDB log.
  362.      *
  363.      * Value for collection schema validationAction.
  364.      *
  365.      * @see https://www.mongodb.com/docs/manual/core/schema-validation/handle-invalid-documents/#option-2--allow-invalid-documents--but-record-them-in-the-log
  366.      */
  367.     public const SCHEMA_VALIDATION_ACTION_WARN 'warn';
  368.     /**
  369.      * Disable schema validation for the collection.
  370.      *
  371.      * Value of validationLevel.
  372.      *
  373.      * @see https://www.mongodb.com/docs/manual/core/schema-validation/specify-validation-level/
  374.      */
  375.     public const SCHEMA_VALIDATION_LEVEL_OFF 'off';
  376.     /**
  377.      * MongoDB applies the same validation rules to all document inserts and updates.
  378.      *
  379.      * Value of validationLevel.
  380.      *
  381.      * @see https://www.mongodb.com/docs/manual/core/schema-validation/specify-validation-level/#steps--use-strict-validation
  382.      */
  383.     public const SCHEMA_VALIDATION_LEVEL_STRICT 'strict';
  384.     /**
  385.      * MongoDB applies the same validation rules to document inserts and updates
  386.      * to existing valid documents that match the validation rules. Updates to
  387.      * existing documents in the collection that don't match the validation rules
  388.      * aren't checked for validity.
  389.      *
  390.      * Value of validationLevel.
  391.      *
  392.      * @see https://www.mongodb.com/docs/manual/core/schema-validation/specify-validation-level/#steps--use-moderate-validation
  393.      */
  394.     public const SCHEMA_VALIDATION_LEVEL_MODERATE 'moderate';
  395.     /* The inheritance mapping types */
  396.     /**
  397.      * NONE means the class does not participate in an inheritance hierarchy
  398.      * and therefore does not need an inheritance mapping type.
  399.      */
  400.     public const INHERITANCE_TYPE_NONE 1;
  401.     /**
  402.      * SINGLE_COLLECTION means the class will be persisted according to the rules of
  403.      * <tt>Single Collection Inheritance</tt>.
  404.      */
  405.     public const INHERITANCE_TYPE_SINGLE_COLLECTION 2;
  406.     /**
  407.      * COLLECTION_PER_CLASS means the class will be persisted according to the rules
  408.      * of <tt>Concrete Collection Inheritance</tt>.
  409.      */
  410.     public const INHERITANCE_TYPE_COLLECTION_PER_CLASS 3;
  411.     /**
  412.      * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
  413.      * by doing a property-by-property comparison with the original data. This will
  414.      * be done for all entities that are in MANAGED state at commit-time.
  415.      *
  416.      * This is the default change tracking policy.
  417.      */
  418.     public const CHANGETRACKING_DEFERRED_IMPLICIT 1;
  419.     /**
  420.      * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
  421.      * by doing a property-by-property comparison with the original data. This will
  422.      * be done only for entities that were explicitly saved (through persist() or a cascade).
  423.      */
  424.     public const CHANGETRACKING_DEFERRED_EXPLICIT 2;
  425.     /**
  426.      * NOTIFY means that Doctrine relies on the entities sending out notifications
  427.      * when their properties change. Such entity classes must implement
  428.      * the <tt>NotifyPropertyChanged</tt> interface.
  429.      *
  430.      * @deprecated
  431.      */
  432.     public const CHANGETRACKING_NOTIFY 3;
  433.     /**
  434.      * SET means that fields will be written to the database using a $set operator
  435.      */
  436.     public const STORAGE_STRATEGY_SET 'set';
  437.     /**
  438.      * INCREMENT means that fields will be written to the database by calculating
  439.      * the difference and using the $inc operator
  440.      */
  441.     public const STORAGE_STRATEGY_INCREMENT 'increment';
  442.     public const STORAGE_STRATEGY_PUSH_ALL         'pushAll';
  443.     public const STORAGE_STRATEGY_ADD_TO_SET       'addToSet';
  444.     public const STORAGE_STRATEGY_ATOMIC_SET       'atomicSet';
  445.     public const STORAGE_STRATEGY_ATOMIC_SET_ARRAY 'atomicSetArray';
  446.     public const STORAGE_STRATEGY_SET_ARRAY        'setArray';
  447.     /**
  448.      * Default search index name.
  449.      *
  450.      * @see https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/
  451.      */
  452.     public const DEFAULT_SEARCH_INDEX_NAME 'default';
  453.     public const VECTOR_SIMILARITY_EUCLIDEAN   'euclidean';
  454.     public const VECTOR_SIMILARITY_COSINE      'cosine';
  455.     public const VECTOR_SIMILARITY_DOT_PRODUCT 'dotProduct';
  456.     public const VECTOR_QUANTIZATION_NONE      'none';
  457.     public const VECTOR_QUANTIZATION_SCALAR    'scalar';
  458.     public const VECTOR_QUANTIZATION_BINARY    'binary';
  459.     private const ALLOWED_GRIDFS_FIELDS = ['_id''chunkSize''filename''length''metadata''uploadDate'];
  460.     /**
  461.      * READ-ONLY: The name of the mongo database the document is mapped to.
  462.      *
  463.      * @var string|null
  464.      */
  465.     public $db;
  466.     /**
  467.      * READ-ONLY: The name of the mongo collection the document is mapped to.
  468.      *
  469.      * @var string
  470.      */
  471.     public $collection;
  472.     /**
  473.      * READ-ONLY: The name of the GridFS bucket the document is mapped to.
  474.      *
  475.      * @var string
  476.      */
  477.     public $bucketName 'fs';
  478.     /**
  479.      * READ-ONLY: If the collection should be a fixed size.
  480.      *
  481.      * @var bool
  482.      */
  483.     public $collectionCapped false;
  484.     /**
  485.      * READ-ONLY: If the collection is fixed size, its size in bytes.
  486.      *
  487.      * @var int|null
  488.      */
  489.     public $collectionSize;
  490.     /**
  491.      * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
  492.      *
  493.      * @var int|null
  494.      */
  495.     public $collectionMax;
  496.     /**
  497.      * READ-ONLY Describes how MongoDB clients route read operations to the members of a replica set.
  498.      *
  499.      * @var string|null
  500.      */
  501.     public $readPreference;
  502.     /**
  503.      * READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read
  504.      * operations to specific members, based on custom parameters.
  505.      *
  506.      * @var array<array<string, string>>
  507.      */
  508.     public $readPreferenceTags = [];
  509.     /**
  510.      * READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.
  511.      *
  512.      * @var string|int|null
  513.      */
  514.     public $writeConcern;
  515.     /**
  516.      * READ-ONLY: The field name of the document identifier.
  517.      *
  518.      * @var string|null
  519.      */
  520.     public $identifier;
  521.     /**
  522.      * READ-ONLY: The array of indexes for the document collection.
  523.      *
  524.      * @var array<array<string, mixed>>
  525.      * @phpstan-var array<IndexMapping>
  526.      */
  527.     public $indexes = [];
  528.     /**
  529.      * READ-ONLY: The array of search indexes for the document collection.
  530.      *
  531.      * @var list<SearchIndexMapping>
  532.      */
  533.     public $searchIndexes = [];
  534.     /**
  535.      * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
  536.      *
  537.      * @var array<string, array>
  538.      * @phpstan-var ShardKey
  539.      */
  540.     public $shardKey = [];
  541.     /**
  542.      * Allows users to specify a validation schema for the collection.
  543.      *
  544.      * @phpstan-var array<string, mixed>|object|null
  545.      */
  546.     private array|object|null $validator null;
  547.     /**
  548.      * Determines whether to error on invalid documents or just warn about the violations but allow invalid documents to be inserted.
  549.      */
  550.     private string $validationAction self::SCHEMA_VALIDATION_ACTION_ERROR;
  551.     /**
  552.      * Determines how strictly MongoDB applies the validation rules to existing documents during an update.
  553.      */
  554.     private string $validationLevel self::SCHEMA_VALIDATION_LEVEL_STRICT;
  555.     /**
  556.      * READ-ONLY: The name of the document class.
  557.      *
  558.      * @var class-string<T>
  559.      */
  560.     public $name;
  561.     /**
  562.      * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
  563.      * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
  564.      * as {@link $documentName}.
  565.      *
  566.      * @var class-string
  567.      */
  568.     public $rootDocumentName;
  569.     /**
  570.      * The name of the custom repository class used for the document class.
  571.      * (Optional).
  572.      *
  573.      * @var class-string|null
  574.      */
  575.     public $customRepositoryClassName;
  576.     /**
  577.      * READ-ONLY: The names of the parent classes (ancestors).
  578.      *
  579.      * @var list<class-string>
  580.      */
  581.     public $parentClasses = [];
  582.     /**
  583.      * READ-ONLY: The names of all subclasses (descendants).
  584.      *
  585.      * @var list<class-string>
  586.      */
  587.     public $subClasses = [];
  588.     /**
  589.      * The ReflectionProperty instances of the mapped class.
  590.      *
  591.      * @deprecated Since 2.13, use $propertyAccessors instead.
  592.      *
  593.      * @var LegacyReflectionFields|array<ReflectionProperty>
  594.      */
  595.     public $reflFields = [];
  596.     /** @var array<string, PropertyAccessors\PropertyAccessor> */
  597.     public array $propertyAccessors = [];
  598.     /**
  599.      * READ-ONLY: The inheritance mapping type used by the class.
  600.      *
  601.      * @var int
  602.      */
  603.     public $inheritanceType self::INHERITANCE_TYPE_NONE;
  604.     /**
  605.      * READ-ONLY: The Id generator type used by the class.
  606.      *
  607.      * @var int
  608.      */
  609.     public $generatorType self::GENERATOR_TYPE_AUTO;
  610.     /**
  611.      * READ-ONLY: The Id generator options.
  612.      *
  613.      * @var array<string, mixed>
  614.      */
  615.     public $generatorOptions = [];
  616.     /**
  617.      * READ-ONLY: The ID generator used for generating IDs for this class.
  618.      *
  619.      * @var IdGenerator|null
  620.      */
  621.     public $idGenerator;
  622.     /**
  623.      * READ-ONLY: The field mappings of the class.
  624.      * Keys are field names and values are mapping definitions.
  625.      *
  626.      * The mapping definition array has the following values:
  627.      *
  628.      * - <b>fieldName</b> (string)
  629.      * The name of the field in the Document.
  630.      *
  631.      * - <b>id</b> (boolean, optional)
  632.      * Marks the field as the primary key of the document. Multiple fields of an
  633.      * document can have the id attribute, forming a composite key.
  634.      *
  635.      * @var array<string, mixed>
  636.      * @phpstan-var array<string, FieldMapping>
  637.      */
  638.     public $fieldMappings = [];
  639.     /**
  640.      * READ-ONLY: The association mappings of the class.
  641.      * Keys are field names and values are mapping definitions.
  642.      *
  643.      * @var array<string, mixed>
  644.      * @phpstan-var array<string, AssociationFieldMapping>
  645.      */
  646.     public $associationMappings = [];
  647.     /**
  648.      * READ-ONLY: Array of fields to also load with a given method.
  649.      *
  650.      * @var array<string, mixed[]>
  651.      */
  652.     public $alsoLoadMethods = [];
  653.     /**
  654.      * READ-ONLY: The registered lifecycle callbacks for documents of this class.
  655.      *
  656.      * @var array<string, list<string>>
  657.      */
  658.     public $lifecycleCallbacks = [];
  659.     /**
  660.      * READ-ONLY: The discriminator value of this class.
  661.      *
  662.      * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
  663.      * where a discriminator field is used.</b>
  664.      *
  665.      * @see discriminatorField
  666.      *
  667.      * @var class-string|null
  668.      */
  669.     public $discriminatorValue;
  670.     /**
  671.      * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
  672.      *
  673.      * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
  674.      * where a discriminator field is used.</b>
  675.      *
  676.      * @see discriminatorField
  677.      *
  678.      * @var array<string, class-string>
  679.      */
  680.     public $discriminatorMap = [];
  681.     /**
  682.      * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
  683.      * inheritance mapping.
  684.      *
  685.      * @var string|null
  686.      */
  687.     public $discriminatorField;
  688.     /**
  689.      * READ-ONLY: The default value for discriminatorField in case it's not set in the document
  690.      *
  691.      * @see discriminatorField
  692.      *
  693.      * @var string|null
  694.      */
  695.     public $defaultDiscriminatorValue;
  696.     /**
  697.      * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
  698.      *
  699.      * @var bool
  700.      */
  701.     public $isMappedSuperclass false;
  702.     /**
  703.      * READ-ONLY: Whether this class describes the mapping of a embedded document.
  704.      *
  705.      * @var bool
  706.      */
  707.     public $isEmbeddedDocument false;
  708.     /**
  709.      * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
  710.      *
  711.      * @var bool
  712.      */
  713.     public $isQueryResultDocument false;
  714.     /**
  715.      * READ-ONLY: Whether this class describes the mapping of a database view.
  716.      */
  717.     private bool $isView false;
  718.     /**
  719.      * READ-ONLY: Whether this class describes the mapping of a gridFS file
  720.      *
  721.      * @var bool
  722.      */
  723.     public $isFile false;
  724.     /**
  725.      * READ-ONLY: The default chunk size in bytes for the file
  726.      *
  727.      * @var int|null
  728.      */
  729.     public $chunkSizeBytes;
  730.     /**
  731.      * READ-ONLY: The policy used for change-tracking on entities of this class.
  732.      *
  733.      * @var int
  734.      */
  735.     public $changeTrackingPolicy self::CHANGETRACKING_DEFERRED_IMPLICIT;
  736.     /**
  737.      * READ-ONLY: A flag for whether or not instances of this class are to be versioned
  738.      * with optimistic locking.
  739.      *
  740.      * @var bool $isVersioned
  741.      */
  742.     public $isVersioned false;
  743.     /**
  744.      * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
  745.      *
  746.      * @var string|null $versionField
  747.      */
  748.     public $versionField;
  749.     /**
  750.      * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
  751.      * locking.
  752.      *
  753.      * @var bool $isLockable
  754.      */
  755.     public $isLockable false;
  756.     /**
  757.      * READ-ONLY: The name of the field which is used for locking a document.
  758.      *
  759.      * @var mixed $lockField
  760.      */
  761.     public $lockField;
  762.     /**
  763.      * The ReflectionClass instance of the mapped class.
  764.      *
  765.      * @var ReflectionClass<T>
  766.      */
  767.     public $reflClass;
  768.     /**
  769.      * READ_ONLY: A flag for whether or not this document is read-only.
  770.      *
  771.      * @var bool
  772.      */
  773.     public $isReadOnly;
  774.     /**
  775.      * READ-ONLY: A flag for whether or not this document has encrypted fields.
  776.      */
  777.     public bool $isEncrypted false;
  778.     /** READ ONLY: stores metadata about the time series collection */
  779.     public ?TimeSeries $timeSeriesOptions null;
  780.     private InstantiatorInterface $instantiator;
  781.     private ReflectionService $reflectionService;
  782.     /** @var class-string|null */
  783.     private ?string $rootClass;
  784.     /**
  785.      * Initializes a new ClassMetadata instance that will hold the object-document mapping
  786.      * metadata of the class with the given name.
  787.      *
  788.      * @param class-string<T> $documentName
  789.      */
  790.     public function __construct(string $documentName)
  791.     {
  792.         $this->name              $documentName;
  793.         $this->rootDocumentName  $documentName;
  794.         $this->reflectionService = new RuntimeReflectionService();
  795.         $this->reflClass         = new ReflectionClass($documentName);
  796.         $this->reflFields        = new LegacyReflectionFields($this$this->reflectionService);
  797.         $this->setCollection($this->reflClass->getShortName());
  798.         $this->instantiator = new Instantiator();
  799.     }
  800.     /**
  801.      * Helper method to get reference id of ref* type references
  802.      *
  803.      * @internal
  804.      *
  805.      * @param mixed $reference
  806.      *
  807.      * @return mixed
  808.      */
  809.     public static function getReferenceId($referencestring $storeAs)
  810.     {
  811.         return $storeAs === self::REFERENCE_STORE_AS_ID $reference $reference[self::getReferencePrefix($storeAs) . 'id'];
  812.     }
  813.     /**
  814.      * Returns the reference prefix used for a reference
  815.      */
  816.     private static function getReferencePrefix(string $storeAs): string
  817.     {
  818.         if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REFself::REFERENCE_STORE_AS_DB_REFself::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
  819.             throw new LogicException('Can only get a reference prefix for DBRef and reference arrays');
  820.         }
  821.         return $storeAs === self::REFERENCE_STORE_AS_REF '' '$';
  822.     }
  823.     /**
  824.      * Returns a fully qualified field name for a given reference
  825.      *
  826.      * @internal
  827.      *
  828.      * @param string $pathPrefix The field path prefix
  829.      */
  830.     public static function getReferenceFieldName(string $storeAsstring $pathPrefix ''): string
  831.     {
  832.         if ($storeAs === self::REFERENCE_STORE_AS_ID) {
  833.             return $pathPrefix;
  834.         }
  835.         return ($pathPrefix $pathPrefix '.' '') . static::getReferencePrefix($storeAs) . 'id';
  836.     }
  837.     public function getReflectionClass(): ReflectionClass
  838.     {
  839.         return $this->reflClass;
  840.     }
  841.     /** @param string $fieldName */
  842.     public function isIdentifier($fieldName): bool
  843.     {
  844.         return $this->identifier === $fieldName;
  845.     }
  846.     /**
  847.      * Sets the mapped identifier field of this class.
  848.      *
  849.      * @internal
  850.      */
  851.     public function setIdentifier(?string $identifier): void
  852.     {
  853.         $this->identifier $identifier;
  854.     }
  855.     /**
  856.      * Since MongoDB only allows exactly one identifier field
  857.      * this will always return an array with only one value
  858.      *
  859.      * @return array<string|null>
  860.      */
  861.     public function getIdentifier(): array
  862.     {
  863.         return [$this->identifier];
  864.     }
  865.     /**
  866.      * Gets the mapping of the identifier field
  867.      *
  868.      * @phpstan-return FieldMapping
  869.      */
  870.     public function getIdentifierMapping(): array
  871.     {
  872.         return $this->fieldMappings[$this->identifier];
  873.     }
  874.     /**
  875.      * Since MongoDB only allows exactly one identifier field
  876.      * this will always return an array with only one value
  877.      *
  878.      * @return array<string|null>
  879.      */
  880.     public function getIdentifierFieldNames(): array
  881.     {
  882.         return [$this->identifier];
  883.     }
  884.     /** @param string $fieldName */
  885.     public function hasField($fieldName): bool
  886.     {
  887.         return isset($this->fieldMappings[$fieldName]);
  888.     }
  889.     /**
  890.      * Sets the inheritance type used by the class and it's subclasses.
  891.      */
  892.     public function setInheritanceType(int $type): void
  893.     {
  894.         $this->inheritanceType $type;
  895.     }
  896.     /**
  897.      * Checks whether a mapped field is inherited from an entity superclass.
  898.      */
  899.     public function isInheritedField(string $fieldName): bool
  900.     {
  901.         return isset($this->fieldMappings[$fieldName]['inherited']);
  902.     }
  903.     /**
  904.      * Registers a custom repository class for the document class.
  905.      *
  906.      * @param class-string|null $repositoryClassName
  907.      */
  908.     public function setCustomRepositoryClass(?string $repositoryClassName): void
  909.     {
  910.         if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
  911.             return;
  912.         }
  913.         $this->customRepositoryClassName $repositoryClassName;
  914.     }
  915.     /**
  916.      * Dispatches the lifecycle event of the given document by invoking all
  917.      * registered callbacks.
  918.      *
  919.      * @param mixed[]|null $arguments
  920.      *
  921.      * @throws InvalidArgumentException If document class is not this class or
  922.      *                                   a Proxy of this class.
  923.      */
  924.     public function invokeLifecycleCallbacks(string $eventobject $document, ?array $arguments null): void
  925.     {
  926.         if ($this->isView()) {
  927.             return;
  928.         }
  929.         if (! $document instanceof $this->name) {
  930.             throw new InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"'$this->name$document::class));
  931.         }
  932.         if (empty($this->lifecycleCallbacks[$event])) {
  933.             return;
  934.         }
  935.         foreach ($this->lifecycleCallbacks[$event] as $callback) {
  936.             if ($arguments !== null) {
  937.                 $document->$callback(...$arguments);
  938.             } else {
  939.                 $document->$callback();
  940.             }
  941.         }
  942.     }
  943.     /**
  944.      * Checks whether the class has callbacks registered for a lifecycle event.
  945.      */
  946.     public function hasLifecycleCallbacks(string $event): bool
  947.     {
  948.         return ! empty($this->lifecycleCallbacks[$event]);
  949.     }
  950.     /**
  951.      * Gets the registered lifecycle callbacks for an event.
  952.      *
  953.      * @return list<string>
  954.      */
  955.     public function getLifecycleCallbacks(string $event): array
  956.     {
  957.         return $this->lifecycleCallbacks[$event] ?? [];
  958.     }
  959.     /**
  960.      * Adds a lifecycle callback for documents of this class.
  961.      *
  962.      * If the callback is already registered, this is a NOOP.
  963.      */
  964.     public function addLifecycleCallback(string $callbackstring $event): void
  965.     {
  966.         if (isset($this->lifecycleCallbacks[$event]) && in_array($callback$this->lifecycleCallbacks[$event])) {
  967.             return;
  968.         }
  969.         $this->lifecycleCallbacks[$event][] = $callback;
  970.     }
  971.     /**
  972.      * Sets the lifecycle callbacks for documents of this class.
  973.      *
  974.      * Any previously registered callbacks are overwritten.
  975.      *
  976.      * @param array<string, list<string>> $callbacks
  977.      */
  978.     public function setLifecycleCallbacks(array $callbacks): void
  979.     {
  980.         $this->lifecycleCallbacks $callbacks;
  981.     }
  982.     /**
  983.      * Registers a method for loading document data before field hydration.
  984.      *
  985.      * Note: A method may be registered multiple times for different fields.
  986.      * it will be invoked only once for the first field found.
  987.      *
  988.      * @param array<string, mixed>|string $fields Database field name(s)
  989.      */
  990.     public function registerAlsoLoadMethod(string $method$fields): void
  991.     {
  992.         $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
  993.     }
  994.     /**
  995.      * Sets the AlsoLoad methods for documents of this class.
  996.      *
  997.      * Any previously registered methods are overwritten.
  998.      *
  999.      * @param array<string, mixed[]> $methods
  1000.      */
  1001.     public function setAlsoLoadMethods(array $methods): void
  1002.     {
  1003.         $this->alsoLoadMethods $methods;
  1004.     }
  1005.     /**
  1006.      * Sets the discriminator field.
  1007.      *
  1008.      * The field name is the the unmapped database field. Discriminator values
  1009.      * are only used to discern the hydration class and are not mapped to class
  1010.      * properties.
  1011.      *
  1012.      * @param array{name?: string, fieldName?: string}|string|null $discriminatorField
  1013.      *
  1014.      * @throws MappingException If the discriminator field conflicts with the
  1015.      *                          "name" attribute of a mapped field.
  1016.      */
  1017.     public function setDiscriminatorField($discriminatorField): void
  1018.     {
  1019.         if ($this->isFile) {
  1020.             throw MappingException::discriminatorNotAllowedForGridFS($this->name);
  1021.         }
  1022.         if ($discriminatorField === null) {
  1023.             $this->discriminatorField null;
  1024.             return;
  1025.         }
  1026.         // @todo: deprecate, document and remove this:
  1027.         // Handle array argument with name/fieldName keys for BC
  1028.         if (is_array($discriminatorField)) {
  1029.             if (isset($discriminatorField['name'])) {
  1030.                 $discriminatorField $discriminatorField['name'];
  1031.             } elseif (isset($discriminatorField['fieldName'])) {
  1032.                 $discriminatorField $discriminatorField['fieldName'];
  1033.             }
  1034.         }
  1035.         foreach ($this->fieldMappings as $fieldMapping) {
  1036.             if ($discriminatorField === $fieldMapping['name']) {
  1037.                 throw MappingException::discriminatorFieldConflict($this->name$discriminatorField);
  1038.             }
  1039.         }
  1040.         $this->discriminatorField $discriminatorField;
  1041.     }
  1042.     /**
  1043.      * Sets the discriminator values used by this class.
  1044.      * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
  1045.      *
  1046.      * @param array<string, class-string> $map
  1047.      *
  1048.      * @throws MappingException
  1049.      */
  1050.     public function setDiscriminatorMap(array $map): void
  1051.     {
  1052.         if ($this->isFile) {
  1053.             throw MappingException::discriminatorNotAllowedForGridFS($this->name);
  1054.         }
  1055.         $this->subClasses         = [];
  1056.         $this->discriminatorMap   = [];
  1057.         $this->discriminatorValue null;
  1058.         foreach ($map as $value => $className) {
  1059.             $this->discriminatorMap[$value] = $className;
  1060.             if ($this->name === $className) {
  1061.                 $this->discriminatorValue $value;
  1062.             } else {
  1063.                 if (! class_exists($className)) {
  1064.                     throw MappingException::invalidClassInDiscriminatorMap($className$this->name);
  1065.                 }
  1066.                 if (is_subclass_of($className$this->name)) {
  1067.                     $this->subClasses[] = $className;
  1068.                 }
  1069.             }
  1070.         }
  1071.     }
  1072.     /**
  1073.      * Sets the default discriminator value to be used for this class
  1074.      * Used for SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
  1075.      *
  1076.      * @throws MappingException
  1077.      */
  1078.     public function setDefaultDiscriminatorValue(?string $defaultDiscriminatorValue): void
  1079.     {
  1080.         if ($this->isFile) {
  1081.             throw MappingException::discriminatorNotAllowedForGridFS($this->name);
  1082.         }
  1083.         if ($defaultDiscriminatorValue === null) {
  1084.             $this->defaultDiscriminatorValue null;
  1085.             return;
  1086.         }
  1087.         if (! array_key_exists($defaultDiscriminatorValue$this->discriminatorMap)) {
  1088.             throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue$this->name);
  1089.         }
  1090.         $this->defaultDiscriminatorValue $defaultDiscriminatorValue;
  1091.     }
  1092.     /**
  1093.      * Sets the discriminator value for this class.
  1094.      * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
  1095.      * collection.
  1096.      *
  1097.      * @throws MappingException
  1098.      */
  1099.     public function setDiscriminatorValue(string $value): void
  1100.     {
  1101.         if ($this->isFile) {
  1102.             throw MappingException::discriminatorNotAllowedForGridFS($this->name);
  1103.         }
  1104.         $this->discriminatorMap[$value] = $this->name;
  1105.         $this->discriminatorValue       $value;
  1106.     }
  1107.     /**
  1108.      * Add a index for this Document.
  1109.      *
  1110.      * @param array<string, int|string> $keys
  1111.      * @phpstan-param IndexKeys $keys
  1112.      * @phpstan-param IndexOptions $options
  1113.      */
  1114.     public function addIndex(array $keys, array $options = []): void
  1115.     {
  1116.         $this->indexes[] = [
  1117.             'keys' => array_map(static function ($value) {
  1118.                 if ($value === || $value === -1) {
  1119.                     return $value;
  1120.                 }
  1121.                 if (is_string($value)) {
  1122.                     $lower strtolower($value);
  1123.                     if ($lower === 'asc') {
  1124.                         return 1;
  1125.                     }
  1126.                     if ($lower === 'desc') {
  1127.                         return -1;
  1128.                     }
  1129.                 }
  1130.                 return $value;
  1131.             }, $keys),
  1132.             'options' => $options,
  1133.         ];
  1134.     }
  1135.     /**
  1136.      * Returns the array of indexes for this Document.
  1137.      *
  1138.      * @phpstan-return array<IndexMapping>
  1139.      */
  1140.     public function getIndexes(): array
  1141.     {
  1142.         return $this->indexes;
  1143.     }
  1144.     /**
  1145.      * Checks whether this document has indexes or not.
  1146.      */
  1147.     public function hasIndexes(): bool
  1148.     {
  1149.         return $this->indexes !== [];
  1150.     }
  1151.     /**
  1152.      * Add a search index for this Document.
  1153.      *
  1154.      * @phpstan-param SearchIndexDefinition|VectorSearchIndexDefinition $definition
  1155.      * @phpstan-param "search"|"vectorSearch" $type
  1156.      */
  1157.     public function addSearchIndex(array $definition, ?string $name nullstring $type 'search'): void
  1158.     {
  1159.         $name ??= self::DEFAULT_SEARCH_INDEX_NAME;
  1160.         if ($type !== 'search' && $type !== 'vectorSearch') {
  1161.             throw new InvalidArgumentException(sprintf('Search index type must be either "search" or "vectorSearch", "%s" given.'$type));
  1162.         }
  1163.         if ($type === 'search' && empty($definition['mappings']['dynamic']) && empty($definition['mappings']['fields'])) {
  1164.             throw MappingException::emptySearchIndexDefinition($this->name$name);
  1165.         }
  1166.         if ($type === 'vectorSearch' && ! in_array('vector'array_column($definition['fields'] ?? [], 'type'), true)) {
  1167.             throw MappingException::emptyVectorSearchIndexDefinition($this->name$name);
  1168.         }
  1169.         $this->searchIndexes[] = [
  1170.             'definition' => $definition,
  1171.             'name' => $name,
  1172.             'type' => $type,
  1173.         ];
  1174.     }
  1175.     /**
  1176.      * Returns the array of search indexes for this Document.
  1177.      *
  1178.      * @phpstan-return list<SearchIndexMapping>
  1179.      */
  1180.     public function getSearchIndexes(): array
  1181.     {
  1182.         return $this->searchIndexes;
  1183.     }
  1184.     /**
  1185.      * Checks whether this document has search indexes or not.
  1186.      */
  1187.     public function hasSearchIndexes(): bool
  1188.     {
  1189.         return $this->searchIndexes !== [];
  1190.     }
  1191.     /**
  1192.      * Set shard key for this Document.
  1193.      *
  1194.      * @param array<string, string|int> $keys
  1195.      * @param array<string, mixed>      $options
  1196.      * @phpstan-param ShardKeys $keys
  1197.      * @phpstan-param ShardOptions      $options
  1198.      *
  1199.      * @throws MappingException
  1200.      */
  1201.     public function setShardKey(array $keys, array $options = []): void
  1202.     {
  1203.         if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== []) {
  1204.             throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
  1205.         }
  1206.         if ($this->isEmbeddedDocument) {
  1207.             throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
  1208.         }
  1209.         foreach (array_keys($keys) as $field) {
  1210.             if (! isset($this->fieldMappings[$field])) {
  1211.                 continue;
  1212.             }
  1213.             if (in_array($this->fieldMappings[$field]['type'], [self::MANYType::COLLECTION])) {
  1214.                 throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
  1215.             }
  1216.             if ($this->fieldMappings[$field]['strategy'] !== self::STORAGE_STRATEGY_SET) {
  1217.                 throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
  1218.             }
  1219.         }
  1220.         $this->shardKey = [
  1221.             'keys' => array_map(static function ($value) {
  1222.                 if ($value === || $value === -1) {
  1223.                     return $value;
  1224.                 }
  1225.                 if (is_string($value)) {
  1226.                     $lower strtolower($value);
  1227.                     if ($lower === 'asc') {
  1228.                         return 1;
  1229.                     }
  1230.                     if ($lower === 'desc') {
  1231.                         return -1;
  1232.                     }
  1233.                 }
  1234.                 return $value;
  1235.             }, $keys),
  1236.             'options' => $options,
  1237.         ];
  1238.     }
  1239.     /** @phpstan-return ShardKey */
  1240.     public function getShardKey(): array
  1241.     {
  1242.         return $this->shardKey;
  1243.     }
  1244.     /**
  1245.      * Checks whether this document has shard key or not.
  1246.      */
  1247.     public function isSharded(): bool
  1248.     {
  1249.         return $this->shardKey !== [];
  1250.     }
  1251.     /** @return array<string, mixed>|object|null */
  1252.     public function getValidator()
  1253.     {
  1254.         return $this->validator;
  1255.     }
  1256.     /** @param array<string, mixed>|object|null $validator */
  1257.     public function setValidator($validator): void
  1258.     {
  1259.         $this->validator $validator;
  1260.     }
  1261.     public function getValidationAction(): string
  1262.     {
  1263.         return $this->validationAction;
  1264.     }
  1265.     public function setValidationAction(string $validationAction): void
  1266.     {
  1267.         $this->validationAction $validationAction;
  1268.     }
  1269.     public function getValidationLevel(): string
  1270.     {
  1271.         return $this->validationLevel;
  1272.     }
  1273.     public function setValidationLevel(string $validationLevel): void
  1274.     {
  1275.         $this->validationLevel $validationLevel;
  1276.     }
  1277.     /**
  1278.      * Sets the read preference used by this class.
  1279.      *
  1280.      * @param array<array<string, string>> $tags
  1281.      */
  1282.     public function setReadPreference(?string $readPreference, array $tags): void
  1283.     {
  1284.         $this->readPreference     $readPreference;
  1285.         $this->readPreferenceTags $tags;
  1286.     }
  1287.     /**
  1288.      * Sets the write concern used by this class.
  1289.      *
  1290.      * @param string|int|null $writeConcern
  1291.      */
  1292.     public function setWriteConcern($writeConcern): void
  1293.     {
  1294.         $this->writeConcern $writeConcern;
  1295.     }
  1296.     /** @return int|string|null */
  1297.     public function getWriteConcern()
  1298.     {
  1299.         return $this->writeConcern;
  1300.     }
  1301.     /**
  1302.      * Whether there is a write concern configured for this class.
  1303.      */
  1304.     public function hasWriteConcern(): bool
  1305.     {
  1306.         return $this->writeConcern !== null;
  1307.     }
  1308.     /**
  1309.      * Sets the change tracking policy used by this class.
  1310.      */
  1311.     public function setChangeTrackingPolicy(int $policy): void
  1312.     {
  1313.         $this->changeTrackingPolicy $policy;
  1314.     }
  1315.     /**
  1316.      * Whether the change tracking policy of this class is "deferred explicit".
  1317.      */
  1318.     public function isChangeTrackingDeferredExplicit(): bool
  1319.     {
  1320.         return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
  1321.     }
  1322.     /**
  1323.      * Whether the change tracking policy of this class is "deferred implicit".
  1324.      */
  1325.     public function isChangeTrackingDeferredImplicit(): bool
  1326.     {
  1327.         return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
  1328.     }
  1329.     /**
  1330.      * Whether the change tracking policy of this class is "notify".
  1331.      *
  1332.      * @deprecated This method was deprecated in doctrine/mongodb-odm 2.4. Please use DEFERRED_EXPLICIT tracking
  1333.      * policy and isChangeTrackingDeferredImplicit method to detect it.
  1334.      */
  1335.     public function isChangeTrackingNotify(): bool
  1336.     {
  1337.         return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
  1338.     }
  1339.     /**
  1340.      * Gets the ReflectionProperties of the mapped class.
  1341.      *
  1342.      * @deprecated Since 2.13, use getPropertyAccessors() instead.
  1343.      *
  1344.      * @return array<ReflectionProperty>|LegacyReflectionFields
  1345.      */
  1346.     public function getReflectionProperties(): array|LegacyReflectionFields
  1347.     {
  1348.         return $this->reflFields;
  1349.     }
  1350.     /**
  1351.      * Gets the ReflectionProperties of the mapped class.
  1352.      *
  1353.      * @return PropertyAccessor[] An array of PropertyAccessor instances.
  1354.      */
  1355.     public function getPropertyAccessors(): array
  1356.     {
  1357.         return $this->propertyAccessors;
  1358.     }
  1359.     /**
  1360.      * Gets a ReflectionProperty for a specific field of the mapped class.
  1361.      *
  1362.      * @deprecated Since 2.13, use getPropertyAccessor() instead.
  1363.      */
  1364.     public function getReflectionProperty(string $name): ReflectionProperty
  1365.     {
  1366.         return $this->reflFields[$name];
  1367.     }
  1368.     public function getPropertyAccessor(string $name): PropertyAccessor|null
  1369.     {
  1370.         return $this->propertyAccessors[$name] ?? null;
  1371.     }
  1372.     /** @return class-string<T> */
  1373.     public function getName(): string
  1374.     {
  1375.         return $this->name;
  1376.     }
  1377.     /**
  1378.      * Returns the database this Document is mapped to.
  1379.      */
  1380.     public function getDatabase(): ?string
  1381.     {
  1382.         return $this->db;
  1383.     }
  1384.     /**
  1385.      * Set the database this Document is mapped to.
  1386.      */
  1387.     public function setDatabase(?string $db): void
  1388.     {
  1389.         $this->db $db;
  1390.     }
  1391.     /**
  1392.      * Get the collection this Document is mapped to.
  1393.      */
  1394.     public function getCollection(): string
  1395.     {
  1396.         return $this->collection;
  1397.     }
  1398.     /**
  1399.      * Sets the collection this Document is mapped to.
  1400.      *
  1401.      * @param array{name: string, capped?: bool, size?: int, max?: int}|string $name
  1402.      *
  1403.      * @throws InvalidArgumentException
  1404.      */
  1405.     public function setCollection($name): void
  1406.     {
  1407.         if (is_array($name)) {
  1408.             if (! isset($name['name'])) {
  1409.                 throw new InvalidArgumentException('A name key is required when passing an array to setCollection()');
  1410.             }
  1411.             $this->collectionCapped $name['capped'] ?? false;
  1412.             $this->collectionSize   $name['size'] ?? 0;
  1413.             $this->collectionMax    $name['max'] ?? 0;
  1414.             $this->collection       $name['name'];
  1415.         } else {
  1416.             $this->collection $name;
  1417.         }
  1418.     }
  1419.     public function getBucketName(): ?string
  1420.     {
  1421.         return $this->bucketName;
  1422.     }
  1423.     public function setBucketName(string $bucketName): void
  1424.     {
  1425.         $this->bucketName $bucketName;
  1426.         $this->setCollection($bucketName '.files');
  1427.     }
  1428.     public function getChunkSizeBytes(): ?int
  1429.     {
  1430.         return $this->chunkSizeBytes;
  1431.     }
  1432.     public function setChunkSizeBytes(int $chunkSizeBytes): void
  1433.     {
  1434.         $this->chunkSizeBytes $chunkSizeBytes;
  1435.     }
  1436.     /**
  1437.      * Get whether or not the documents collection is capped.
  1438.      */
  1439.     public function getCollectionCapped(): bool
  1440.     {
  1441.         return $this->collectionCapped;
  1442.     }
  1443.     /**
  1444.      * Set whether or not the documents collection is capped.
  1445.      */
  1446.     public function setCollectionCapped(bool $bool): void
  1447.     {
  1448.         $this->collectionCapped $bool;
  1449.     }
  1450.     /**
  1451.      * Get the collection size
  1452.      */
  1453.     public function getCollectionSize(): ?int
  1454.     {
  1455.         return $this->collectionSize;
  1456.     }
  1457.     /**
  1458.      * Set the collection size.
  1459.      */
  1460.     public function setCollectionSize(int $size): void
  1461.     {
  1462.         $this->collectionSize $size;
  1463.     }
  1464.     /**
  1465.      * Get the collection max.
  1466.      */
  1467.     public function getCollectionMax(): ?int
  1468.     {
  1469.         return $this->collectionMax;
  1470.     }
  1471.     /**
  1472.      * Set the collection max.
  1473.      */
  1474.     public function setCollectionMax(int $max): void
  1475.     {
  1476.         $this->collectionMax $max;
  1477.     }
  1478.     /**
  1479.      * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
  1480.      */
  1481.     public function isMappedToCollection(): bool
  1482.     {
  1483.         return $this->collection !== '' && $this->collection !== null;
  1484.     }
  1485.     /**
  1486.      * Validates the storage strategy of a mapping for consistency
  1487.      *
  1488.      * @phpstan-param FieldMappingConfig $mapping
  1489.      *
  1490.      * @throws MappingException
  1491.      */
  1492.     private function applyStorageStrategy(array &$mapping): void
  1493.     {
  1494.         if (! isset($mapping['type']) || isset($mapping['id'])) {
  1495.             return;
  1496.         }
  1497.         switch (true) {
  1498.             case $mapping['type'] === self::MANY:
  1499.                 $defaultStrategy   CollectionHelper::DEFAULT_STRATEGY;
  1500.                 $allowedStrategies = [
  1501.                     self::STORAGE_STRATEGY_PUSH_ALL,
  1502.                     self::STORAGE_STRATEGY_ADD_TO_SET,
  1503.                     self::STORAGE_STRATEGY_SET,
  1504.                     self::STORAGE_STRATEGY_SET_ARRAY,
  1505.                     self::STORAGE_STRATEGY_ATOMIC_SET,
  1506.                     self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
  1507.                 ];
  1508.                 break;
  1509.             case $mapping['type'] === self::ONE:
  1510.                 $defaultStrategy   self::STORAGE_STRATEGY_SET;
  1511.                 $allowedStrategies = [self::STORAGE_STRATEGY_SET];
  1512.                 break;
  1513.             default:
  1514.                 $defaultStrategy   self::STORAGE_STRATEGY_SET;
  1515.                 $allowedStrategies = [self::STORAGE_STRATEGY_SET];
  1516.                 $type              Type::getType($mapping['type']);
  1517.                 if ($type instanceof Incrementable) {
  1518.                     $allowedStrategies[] = self::STORAGE_STRATEGY_INCREMENT;
  1519.                 }
  1520.         }
  1521.         if (! isset($mapping['strategy'])) {
  1522.             $mapping['strategy'] = $defaultStrategy;
  1523.         }
  1524.         if (! in_array($mapping['strategy'], $allowedStrategies)) {
  1525.             throw MappingException::invalidStorageStrategy($this->name$mapping['fieldName'], $mapping['type'], $mapping['strategy']);
  1526.         }
  1527.         if (
  1528.             isset($mapping['reference']) && $mapping['type'] === self::MANY && $mapping['isOwningSide']
  1529.             && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])
  1530.         ) {
  1531.             throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name$mapping['fieldName'], $mapping['strategy']);
  1532.         }
  1533.     }
  1534.     /**
  1535.      * Map a single embedded document.
  1536.      *
  1537.      * @phpstan-param FieldMappingConfig $mapping
  1538.      */
  1539.     public function mapOneEmbedded(array $mapping): void
  1540.     {
  1541.         $mapping['embedded'] = true;
  1542.         $mapping['type']     = self::ONE;
  1543.         $this->mapField($mapping);
  1544.     }
  1545.     /**
  1546.      * Map a collection of embedded documents.
  1547.      *
  1548.      * @phpstan-param FieldMappingConfig $mapping
  1549.      */
  1550.     public function mapManyEmbedded(array $mapping): void
  1551.     {
  1552.         $mapping['embedded'] = true;
  1553.         $mapping['type']     = self::MANY;
  1554.         $this->mapField($mapping);
  1555.     }
  1556.     /**
  1557.      * Map a single document reference.
  1558.      *
  1559.      * @phpstan-param FieldMappingConfig $mapping
  1560.      */
  1561.     public function mapOneReference(array $mapping): void
  1562.     {
  1563.         $mapping['reference'] = true;
  1564.         $mapping['type']      = self::ONE;
  1565.         $this->mapField($mapping);
  1566.     }
  1567.     /**
  1568.      * Map a collection of document references.
  1569.      *
  1570.      * @phpstan-param FieldMappingConfig $mapping
  1571.      */
  1572.     public function mapManyReference(array $mapping): void
  1573.     {
  1574.         $mapping['reference'] = true;
  1575.         $mapping['type']      = self::MANY;
  1576.         $this->mapField($mapping);
  1577.     }
  1578.     /**
  1579.      * Adds a field mapping without completing/validating it.
  1580.      * This is mainly used to add inherited field mappings to derived classes.
  1581.      *
  1582.      * @internal
  1583.      *
  1584.      * @phpstan-param FieldMapping $fieldMapping
  1585.      */
  1586.     public function addInheritedFieldMapping(array $fieldMapping): void
  1587.     {
  1588.         $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
  1589.         if (! isset($fieldMapping['association'])) {
  1590.             return;
  1591.         }
  1592.         $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
  1593.     }
  1594.     /**
  1595.      * Adds an association mapping without completing/validating it.
  1596.      * This is mainly used to add inherited association mappings to derived classes.
  1597.      *
  1598.      * @internal
  1599.      *
  1600.      * @phpstan-param AssociationFieldMapping $mapping
  1601.      *
  1602.      * @throws MappingException
  1603.      */
  1604.     public function addInheritedAssociationMapping(array $mapping): void
  1605.     {
  1606.         $this->associationMappings[$mapping['fieldName']] = $mapping;
  1607.     }
  1608.     /**
  1609.      * Checks whether the class has a mapped association with the given field name.
  1610.      */
  1611.     public function hasReference(string $fieldName): bool
  1612.     {
  1613.         return isset($this->fieldMappings[$fieldName]['reference']);
  1614.     }
  1615.     /**
  1616.      * Checks whether the class has a mapped embed with the given field name.
  1617.      */
  1618.     public function hasEmbed(string $fieldName): bool
  1619.     {
  1620.         return isset($this->fieldMappings[$fieldName]['embedded']);
  1621.     }
  1622.     /**
  1623.      * Checks whether the class has a mapped association (embed or reference) with the given field name.
  1624.      *
  1625.      * @param string $fieldName
  1626.      */
  1627.     public function hasAssociation($fieldName): bool
  1628.     {
  1629.         return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
  1630.     }
  1631.     /**
  1632.      * Checks whether the class has a mapped reference or embed for the specified field and
  1633.      * is a single valued association.
  1634.      *
  1635.      * @param string $fieldName
  1636.      */
  1637.     public function isSingleValuedAssociation($fieldName): bool
  1638.     {
  1639.         return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
  1640.     }
  1641.     /**
  1642.      * Checks whether the class has a mapped reference or embed for the specified field and
  1643.      * is a collection valued association.
  1644.      *
  1645.      * @param string $fieldName
  1646.      */
  1647.     public function isCollectionValuedAssociation($fieldName): bool
  1648.     {
  1649.         return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
  1650.     }
  1651.     /**
  1652.      * Checks whether the class has a mapped association for the specified field
  1653.      * and if yes, checks whether it is a single-valued association (to-one).
  1654.      */
  1655.     public function isSingleValuedReference(string $fieldName): bool
  1656.     {
  1657.         return ($this->fieldMappings[$fieldName]['association'] ?? null) === self::REFERENCE_ONE;
  1658.     }
  1659.     /**
  1660.      * Checks whether the class has a mapped association for the specified field
  1661.      * and if yes, checks whether it is a collection-valued association (to-many).
  1662.      */
  1663.     public function isCollectionValuedReference(string $fieldName): bool
  1664.     {
  1665.         return ($this->fieldMappings[$fieldName]['association'] ?? null) === self::REFERENCE_MANY;
  1666.     }
  1667.     /**
  1668.      * Checks whether the class has a mapped embedded document for the specified field
  1669.      * and if yes, checks whether it is a single-valued association (to-one).
  1670.      */
  1671.     public function isSingleValuedEmbed(string $fieldName): bool
  1672.     {
  1673.         return ($this->fieldMappings[$fieldName]['association'] ?? null) === self::EMBED_ONE;
  1674.     }
  1675.     /**
  1676.      * Checks whether the class has a mapped embedded document for the specified field
  1677.      * and if yes, checks whether it is a collection-valued association (to-many).
  1678.      */
  1679.     public function isCollectionValuedEmbed(string $fieldName): bool
  1680.     {
  1681.         return ($this->fieldMappings[$fieldName]['association'] ?? null) === self::EMBED_MANY;
  1682.     }
  1683.     /**
  1684.      * Sets the ID generator used to generate IDs for instances of this class.
  1685.      */
  1686.     public function setIdGenerator(IdGenerator $generator): void
  1687.     {
  1688.         $this->idGenerator $generator;
  1689.     }
  1690.     /**
  1691.      * Casts the identifier to its portable PHP type.
  1692.      *
  1693.      * @param mixed $id
  1694.      *
  1695.      * @return mixed $id
  1696.      */
  1697.     public function getPHPIdentifierValue($id)
  1698.     {
  1699.         $idType $this->fieldMappings[$this->identifier]['type'];
  1700.         return Type::getType($idType)->convertToPHPValue($id);
  1701.     }
  1702.     /**
  1703.      * Casts the identifier to its database type.
  1704.      *
  1705.      * @param mixed $id
  1706.      *
  1707.      * @return mixed $id
  1708.      */
  1709.     public function getDatabaseIdentifierValue($id)
  1710.     {
  1711.         $idType $this->fieldMappings[$this->identifier]['type'];
  1712.         return Type::getType($idType)->convertToDatabaseValue($id);
  1713.     }
  1714.     /**
  1715.      * Sets the document identifier of a document.
  1716.      *
  1717.      * The value will be converted to a PHP type before being set.
  1718.      *
  1719.      * @param mixed $id
  1720.      */
  1721.     public function setIdentifierValue(object $document$id): void
  1722.     {
  1723.         $id $this->getPHPIdentifierValue($id);
  1724.         $this->propertyAccessors[$this->identifier]->setValue($document$id);
  1725.     }
  1726.     /**
  1727.      * Gets the document identifier as a PHP type.
  1728.      *
  1729.      * @return mixed $id
  1730.      */
  1731.     public function getIdentifierValue(object $document)
  1732.     {
  1733.         return $this->propertyAccessors[$this->identifier]->getValue($document);
  1734.     }
  1735.     /**
  1736.      * Since MongoDB only allows exactly one identifier field this is a proxy
  1737.      * to {@see getIdentifierValue()} and returns an array with the identifier
  1738.      * field as a key.
  1739.      *
  1740.      * @param object $object
  1741.      */
  1742.     public function getIdentifierValues($object): array
  1743.     {
  1744.         return [$this->identifier => $this->getIdentifierValue($object)];
  1745.     }
  1746.     /**
  1747.      * Get the document identifier object as a database type.
  1748.      *
  1749.      * @return mixed $id
  1750.      */
  1751.     public function getIdentifierObject(object $document)
  1752.     {
  1753.         return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
  1754.     }
  1755.     /**
  1756.      * Sets the specified field to the specified value on the given document.
  1757.      *
  1758.      * @param mixed $value
  1759.      */
  1760.     public function setFieldValue(object $documentstring $field$value): void
  1761.     {
  1762.         if ($document instanceof InternalProxy && ! $document->__isInitialized()) {
  1763.             //property changes to an uninitialized proxy will not be tracked or persisted,
  1764.             //so the proxy needs to be loaded first.
  1765.             $document->__load();
  1766.         } elseif ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
  1767.             $document->initializeProxy();
  1768.         } elseif (PHP_VERSION_ID >= 80400) {
  1769.             $this->reflClass->initializeLazyObject($document);
  1770.         }
  1771.         $this->propertyAccessors[$field]->setValue($document$value);
  1772.     }
  1773.     /**
  1774.      * Gets the specified field's value off the given document.
  1775.      *
  1776.      * @return mixed
  1777.      */
  1778.     public function getFieldValue(object $documentstring $field)
  1779.     {
  1780.         if ($document instanceof InternalProxy && $field !== $this->identifier && ! $document->__isInitialized()) {
  1781.             $document->__load();
  1782.         } elseif ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) {
  1783.             $document->initializeProxy();
  1784.         } elseif (PHP_VERSION_ID >= 80400 && $field !== $this->identifier && $this->reflClass->isUninitializedLazyObject($document)) {
  1785.             $this->reflClass->initializeLazyObject($document);
  1786.         }
  1787.         return $this->propertyAccessors[$field]->getValue($document);
  1788.     }
  1789.     /**
  1790.      * Gets the mapping of a field.
  1791.      *
  1792.      * @phpstan-return FieldMapping
  1793.      *
  1794.      * @throws MappingException If the $fieldName is not found in the fieldMappings array.
  1795.      */
  1796.     public function getFieldMapping(string $fieldName): array
  1797.     {
  1798.         if (! isset($this->fieldMappings[$fieldName])) {
  1799.             throw MappingException::mappingNotFound($this->name$fieldName);
  1800.         }
  1801.         return $this->fieldMappings[$fieldName];
  1802.     }
  1803.     /**
  1804.      * Gets mappings of fields holding embedded document(s).
  1805.      *
  1806.      * @return array<string, AssociationFieldMapping>
  1807.      */
  1808.     public function getEmbeddedFieldsMappings(): array
  1809.     {
  1810.         return array_filter(
  1811.             $this->associationMappings,
  1812.             static fn ($assoc) => ! empty($assoc['embedded']),
  1813.         );
  1814.     }
  1815.     /**
  1816.      * Gets the field mapping by its DB name.
  1817.      * E.g. it returns identifier's mapping when called with _id.
  1818.      *
  1819.      * @phpstan-return FieldMapping
  1820.      *
  1821.      * @throws MappingException
  1822.      */
  1823.     public function getFieldMappingByDbFieldName(string $dbFieldName): array
  1824.     {
  1825.         foreach ($this->fieldMappings as $mapping) {
  1826.             if ($mapping['name'] === $dbFieldName) {
  1827.                 return $mapping;
  1828.             }
  1829.         }
  1830.         throw MappingException::mappingNotFoundByDbName($this->name$dbFieldName);
  1831.     }
  1832.     /**
  1833.      * Check if the field is not null.
  1834.      */
  1835.     public function isNullable(string $fieldName): bool
  1836.     {
  1837.         $mapping $this->getFieldMapping($fieldName);
  1838.         return isset($mapping['nullable']) && $mapping['nullable'] === true;
  1839.     }
  1840.     /**
  1841.      * Checks whether the document has a discriminator field and value configured.
  1842.      */
  1843.     public function hasDiscriminator(): bool
  1844.     {
  1845.         return isset($this->discriminatorField$this->discriminatorValue);
  1846.     }
  1847.     /**
  1848.      * Sets the type of Id generator to use for the mapped class.
  1849.      */
  1850.     public function setIdGeneratorType(int $generatorType): void
  1851.     {
  1852.         $this->generatorType $generatorType;
  1853.     }
  1854.     /**
  1855.      * Sets the Id generator options.
  1856.      *
  1857.      * @param array<string, mixed> $generatorOptions
  1858.      */
  1859.     public function setIdGeneratorOptions(array $generatorOptions): void
  1860.     {
  1861.         $this->generatorOptions $generatorOptions;
  1862.     }
  1863.     public function isInheritanceTypeNone(): bool
  1864.     {
  1865.         return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
  1866.     }
  1867.     /**
  1868.      * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
  1869.      */
  1870.     public function isInheritanceTypeSingleCollection(): bool
  1871.     {
  1872.         return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
  1873.     }
  1874.     /**
  1875.      * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
  1876.      */
  1877.     public function isInheritanceTypeCollectionPerClass(): bool
  1878.     {
  1879.         return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
  1880.     }
  1881.     /**
  1882.      * Sets the mapped subclasses of this class.
  1883.      *
  1884.      * @param class-string[] $subclasses The names of all mapped subclasses.
  1885.      */
  1886.     public function setSubclasses(array $subclasses): void
  1887.     {
  1888.         foreach ($subclasses as $subclass) {
  1889.             $this->subClasses[] = $subclass;
  1890.         }
  1891.     }
  1892.     /**
  1893.      * Sets the parent class names.
  1894.      * Assumes that the class names in the passed array are in the order:
  1895.      * directParent -> directParentParent -> directParentParentParent ... -> root.
  1896.      *
  1897.      * @param list<class-string> $classNames
  1898.      */
  1899.     public function setParentClasses(array $classNames): void
  1900.     {
  1901.         $this->parentClasses $classNames;
  1902.         if (count($classNames) <= 0) {
  1903.             return;
  1904.         }
  1905.         $this->rootDocumentName = (string) array_pop($classNames);
  1906.     }
  1907.     /**
  1908.      * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
  1909.      */
  1910.     public function isIdGeneratorAuto(): bool
  1911.     {
  1912.         return $this->generatorType === self::GENERATOR_TYPE_AUTO;
  1913.     }
  1914.     /**
  1915.      * Checks whether the class will use a collection to generate incremented identifiers.
  1916.      */
  1917.     public function isIdGeneratorIncrement(): bool
  1918.     {
  1919.         return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
  1920.     }
  1921.     /**
  1922.      * Checks whether the class will generate a uuid id.
  1923.      *
  1924.      * @deprecated Since 2.15, the UUID id generator is deprecated. Use GENERATOR_TYPE_AUTO with the UUID type instead.
  1925.      */
  1926.     public function isIdGeneratorUuid(): bool
  1927.     {
  1928.         trigger_deprecation('doctrine/mongodb-odm''2.15''The method %s() is deprecated. Use GENERATOR_TYPE_AUTO with the UUID type instead.'__FUNCTION__);
  1929.         return $this->generatorType === self::GENERATOR_TYPE_UUID;
  1930.     }
  1931.     /**
  1932.      * Checks whether the class uses no id generator.
  1933.      */
  1934.     public function isIdGeneratorNone(): bool
  1935.     {
  1936.         return $this->generatorType === self::GENERATOR_TYPE_NONE;
  1937.     }
  1938.     /**
  1939.      * Sets the version field mapping used for versioning. Sets the default
  1940.      * value to use depending on the column type.
  1941.      *
  1942.      * @phpstan-param FieldMapping $mapping
  1943.      *
  1944.      * @throws LockException
  1945.      */
  1946.     public function setVersionMapping(array &$mapping): void
  1947.     {
  1948.         if (! Type::getType($mapping['type']) instanceof Versionable) {
  1949.             throw LockException::invalidVersionFieldType($mapping['type']);
  1950.         }
  1951.         $this->isVersioned  true;
  1952.         $this->versionField $mapping['fieldName'];
  1953.     }
  1954.     /**
  1955.      * Sets whether this class is to be versioned for optimistic locking.
  1956.      */
  1957.     public function setVersioned(bool $bool): void
  1958.     {
  1959.         $this->isVersioned $bool;
  1960.     }
  1961.     /**
  1962.      * Sets the name of the field that is to be used for versioning if this class is
  1963.      * versioned for optimistic locking.
  1964.      */
  1965.     public function setVersionField(?string $versionField): void
  1966.     {
  1967.         $this->versionField $versionField;
  1968.     }
  1969.     /**
  1970.      * Sets the version field mapping used for versioning. Sets the default
  1971.      * value to use depending on the column type.
  1972.      *
  1973.      * @phpstan-param FieldMapping $mapping
  1974.      *
  1975.      * @throws LockException
  1976.      */
  1977.     public function setLockMapping(array &$mapping): void
  1978.     {
  1979.         if ($mapping['type'] !== 'int') {
  1980.             throw LockException::invalidLockFieldType($mapping['type']);
  1981.         }
  1982.         $this->isLockable true;
  1983.         $this->lockField  $mapping['fieldName'];
  1984.     }
  1985.     /**
  1986.      * Sets whether this class is to allow pessimistic locking.
  1987.      */
  1988.     public function setLockable(bool $bool): void
  1989.     {
  1990.         $this->isLockable $bool;
  1991.     }
  1992.     /**
  1993.      * Sets the name of the field that is to be used for storing whether a document
  1994.      * is currently locked or not.
  1995.      */
  1996.     public function setLockField(string $lockField): void
  1997.     {
  1998.         $this->lockField $lockField;
  1999.     }
  2000.     /**
  2001.      * Marks this class as read only, no change tracking is applied to it.
  2002.      */
  2003.     public function markReadOnly(): void
  2004.     {
  2005.         $this->isReadOnly true;
  2006.     }
  2007.     public function getRootClass(): ?string
  2008.     {
  2009.         return $this->rootClass;
  2010.     }
  2011.     public function isView(): bool
  2012.     {
  2013.         return $this->isView;
  2014.     }
  2015.     public function isDocument(): bool
  2016.     {
  2017.         return ! $this->isView
  2018.             && ! $this->isEmbeddedDocument
  2019.             && ! $this->isFile
  2020.             && ! $this->isQueryResultDocument
  2021.             && ! $this->isMappedSuperclass;
  2022.     }
  2023.     /** @param class-string $rootClass */
  2024.     public function markViewOf(string $rootClass): void
  2025.     {
  2026.         $this->isView    true;
  2027.         $this->rootClass $rootClass;
  2028.     }
  2029.     public function markAsTimeSeries(TimeSeries $options): void
  2030.     {
  2031.         $this->validateTimeSeriesOptions($options);
  2032.         $this->timeSeriesOptions $options;
  2033.     }
  2034.     public function getFieldNames(): array
  2035.     {
  2036.         return array_keys($this->fieldMappings);
  2037.     }
  2038.     public function getAssociationNames(): array
  2039.     {
  2040.         return array_keys($this->associationMappings);
  2041.     }
  2042.     /** @param string $fieldName */
  2043.     public function getTypeOfField($fieldName): ?string
  2044.     {
  2045.         return $this->fieldMappings[$fieldName]['type'] ?? null;
  2046.     }
  2047.     /**
  2048.      * @param string $assocName
  2049.      *
  2050.      * @return class-string|null
  2051.      */
  2052.     public function getAssociationTargetClass($assocName): ?string
  2053.     {
  2054.         if (! isset($this->associationMappings[$assocName])) {
  2055.             throw new InvalidArgumentException("Association name expected, '" $assocName "' is not an association.");
  2056.         }
  2057.         return $this->associationMappings[$assocName]['targetDocument'] ?? null;
  2058.     }
  2059.     /**
  2060.      * Retrieve the collectionClass associated with an association
  2061.      *
  2062.      * @return class-string
  2063.      */
  2064.     public function getAssociationCollectionClass(string $assocName): string
  2065.     {
  2066.         if (! isset($this->associationMappings[$assocName])) {
  2067.             throw new InvalidArgumentException("Association name expected, '" $assocName "' is not an association.");
  2068.         }
  2069.         if (! array_key_exists('collectionClass'$this->associationMappings[$assocName])) {
  2070.             throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
  2071.         }
  2072.         return $this->associationMappings[$assocName]['collectionClass'];
  2073.     }
  2074.     /** @param string $assocName */
  2075.     public function isAssociationInverseSide($assocName): bool
  2076.     {
  2077.         throw new BadMethodCallException(__METHOD__ '() is not implemented yet.');
  2078.     }
  2079.     /** @param string $assocName */
  2080.     public function getAssociationMappedByTargetField($assocName): string
  2081.     {
  2082.         throw new BadMethodCallException(__METHOD__ '() is not implemented yet.');
  2083.     }
  2084.     /**
  2085.      * Map a field.
  2086.      *
  2087.      * @phpstan-param FieldMappingConfig $mapping
  2088.      *
  2089.      * @phpstan-return FieldMapping
  2090.      *
  2091.      * @throws MappingException
  2092.      */
  2093.     public function mapField(array $mapping): array
  2094.     {
  2095.         if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
  2096.             $mapping['fieldName'] = $mapping['name'];
  2097.         }
  2098.         if ($this->isTypedProperty($mapping['fieldName'])) {
  2099.             $mapping $this->validateAndCompleteTypedFieldMapping($mapping);
  2100.             if (isset($mapping['type']) && $mapping['type'] === self::MANY) {
  2101.                 $mapping $this->validateAndCompleteTypedManyAssociationMapping($mapping);
  2102.             }
  2103.         }
  2104.         if (! isset($mapping['fieldName']) || ! is_string($mapping['fieldName'])) {
  2105.             throw MappingException::missingFieldName($this->name);
  2106.         }
  2107.         if (! isset($mapping['name'])) {
  2108.             $mapping['name'] = $mapping['fieldName'];
  2109.         }
  2110.         if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
  2111.             throw MappingException::mustNotChangeIdentifierFieldsType($this->name, (string) $mapping['name']);
  2112.         }
  2113.         if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
  2114.             throw MappingException::discriminatorFieldConflict($this->name$this->discriminatorField);
  2115.         }
  2116.         if (isset($mapping['collectionClass'])) {
  2117.             $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
  2118.         }
  2119.         if (! empty($mapping['collectionClass'])) {
  2120.             $rColl = new ReflectionClass($mapping['collectionClass']);
  2121.             if (! $rColl->implementsInterface(Collection::class)) {
  2122.                 throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name$mapping['fieldName'], $mapping['collectionClass']);
  2123.             }
  2124.         }
  2125.         if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
  2126.             throw MappingException::cascadeOnEmbeddedNotAllowed($this->name$mapping['fieldName']);
  2127.         }
  2128.         $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
  2129.         if (in_array('all'$cascades) || isset($mapping['embedded'])) {
  2130.             $cascades = ['remove''persist''refresh''merge''detach'];
  2131.         }
  2132.         if (isset($mapping['embedded'])) {
  2133.             unset($mapping['cascade']);
  2134.         } elseif (isset($mapping['cascade'])) {
  2135.             $mapping['cascade'] = $cascades;
  2136.         }
  2137.         $mapping['isCascadeRemove']  = in_array('remove'$cascades);
  2138.         $mapping['isCascadePersist'] = in_array('persist'$cascades);
  2139.         $mapping['isCascadeRefresh'] = in_array('refresh'$cascades);
  2140.         $mapping['isCascadeMerge']   = in_array('merge'$cascades);
  2141.         $mapping['isCascadeDetach']  = in_array('detach'$cascades);
  2142.         if (isset($mapping['id']) && $mapping['id'] === true) {
  2143.             $mapping['name']  = '_id';
  2144.             $this->identifier $mapping['fieldName'];
  2145.             if (isset($mapping['strategy'])) {
  2146.                 $this->generatorType constant(self::class . '::GENERATOR_TYPE_' strtoupper($mapping['strategy']));
  2147.             }
  2148.             $this->generatorOptions $mapping['options'] ?? [];
  2149.             if ($this->generatorType !== self::GENERATOR_TYPE_AUTO) {
  2150.                 if (! empty($this->generatorOptions['type'])) {
  2151.                     $mapping['type'] = (string) $this->generatorOptions['type'];
  2152.                 } elseif (empty($mapping['type'])) {
  2153.                     $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT Type::INT Type::CUSTOMID;
  2154.                 }
  2155.             } elseif (empty($mapping['type']) || $mapping['type'] !== Type::UUID) {
  2156.                 $mapping['type'] = Type::ID;
  2157.             }
  2158.             unset($this->generatorOptions['type']);
  2159.         } elseif (! isset($mapping['type'])) {
  2160.             // Default to string
  2161.             $mapping['type'] = Type::STRING;
  2162.         }
  2163.         if (! isset($mapping['nullable'])) {
  2164.             $mapping['nullable'] = false;
  2165.         }
  2166.         if (isset($mapping['encrypt']['queryType'])) {
  2167.             // The encrypted range query options min and max must be converted to the database type
  2168.             $type Type::getType($mapping['type']);
  2169.             foreach (['min''max'] as $option) {
  2170.                 if (isset($mapping['encrypt'][$option])) {
  2171.                     $mapping['encrypt'][$option] = $type->convertToDatabaseValue($mapping['encrypt'][$option]);
  2172.                 }
  2173.             }
  2174.         }
  2175.         if (
  2176.             isset($mapping['reference'])
  2177.             && isset($mapping['storeAs'])
  2178.             && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
  2179.             && ! isset($mapping['targetDocument'])
  2180.         ) {
  2181.             throw MappingException::simpleReferenceRequiresTargetDocument($this->name$mapping['fieldName']);
  2182.         }
  2183.         if (
  2184.             isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
  2185.                 (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))
  2186.         ) {
  2187.             throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name$mapping['fieldName']);
  2188.         }
  2189.         if ($this->isEmbeddedDocument && $mapping['type'] === self::MANY && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
  2190.             throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name$mapping['fieldName']);
  2191.         }
  2192.         if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
  2193.             throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name$mapping['fieldName']);
  2194.         }
  2195.         if (isset($mapping['targetDocument']) && isset($mapping['discriminatorMap'])) {
  2196.             trigger_deprecation(
  2197.                 'doctrine/mongodb-odm',
  2198.                 '2.2',
  2199.                 'Mapping both "targetDocument" and "discriminatorMap" on field "%s" in class "%s" is deprecated. Only one of them can be used at a time',
  2200.                 $mapping['fieldName'],
  2201.                 $this->name,
  2202.             );
  2203.         }
  2204.         if (isset($mapping['reference']) && $mapping['type'] === self::ONE) {
  2205.             $mapping['association'] = self::REFERENCE_ONE;
  2206.         }
  2207.         if (isset($mapping['reference']) && $mapping['type'] === self::MANY) {
  2208.             $mapping['association'] = self::REFERENCE_MANY;
  2209.         }
  2210.         if (isset($mapping['embedded']) && $mapping['type'] === self::ONE) {
  2211.             $mapping['association'] = self::EMBED_ONE;
  2212.         }
  2213.         if (isset($mapping['embedded']) && $mapping['type'] === self::MANY) {
  2214.             $mapping['association'] = self::EMBED_MANY;
  2215.         }
  2216.         if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
  2217.             $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
  2218.         }
  2219.         if (isset($mapping['targetDocument']) && ! class_exists($mapping['targetDocument']) && ! interface_exists($mapping['targetDocument'])) {
  2220.             throw MappingException::invalidTargetDocument(
  2221.                 $mapping['targetDocument'],
  2222.                 $this->name,
  2223.                 $mapping['fieldName'],
  2224.             );
  2225.         }
  2226.         if (isset($mapping['discriminatorMap'])) {
  2227.             foreach ($mapping['discriminatorMap'] as $value => $class) {
  2228.                 if (! class_exists($class) && ! interface_exists($class)) {
  2229.                     throw MappingException::invalidClassInReferenceDiscriminatorMap($class$this->name$mapping['fieldName']);
  2230.                 }
  2231.             }
  2232.         }
  2233.         if (isset($mapping['version'])) {
  2234.             $mapping['notSaved'] = true;
  2235.             $this->setVersionMapping($mapping);
  2236.         }
  2237.         if (isset($mapping['lock'])) {
  2238.             $mapping['notSaved'] = true;
  2239.             $this->setLockMapping($mapping);
  2240.         }
  2241.         $mapping['isOwningSide']  = true;
  2242.         $mapping['isInverseSide'] = false;
  2243.         if (isset($mapping['reference'])) {
  2244.             if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
  2245.                 $mapping['isOwningSide']  = true;
  2246.                 $mapping['isInverseSide'] = false;
  2247.             }
  2248.             if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
  2249.                 $mapping['isInverseSide'] = true;
  2250.                 $mapping['isOwningSide']  = false;
  2251.             }
  2252.             if (isset($mapping['repositoryMethod'])) {
  2253.                 $mapping['isInverseSide'] = true;
  2254.                 $mapping['isOwningSide']  = false;
  2255.             }
  2256.             if (! isset($mapping['orphanRemoval'])) {
  2257.                 $mapping['orphanRemoval'] = false;
  2258.             }
  2259.         }
  2260.         if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
  2261.             throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name$mapping['fieldName']);
  2262.         }
  2263.         if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
  2264.             throw MappingException::fieldNotAllowedForGridFS($this->name$mapping['fieldName']);
  2265.         }
  2266.         $this->applyStorageStrategy($mapping);
  2267.         $this->checkDuplicateMapping($mapping);
  2268.         $this->typeRequirementsAreMet($mapping);
  2269.         $deprecatedTypes = [
  2270.             Type::BOOLEAN => Type::BOOL,
  2271.             Type::INTEGER => Type::INT,
  2272.             Type::INTID => Type::INT,
  2273.         ];
  2274.         if (isset($deprecatedTypes[$mapping['type']])) {
  2275.             trigger_deprecation(
  2276.                 'doctrine/mongodb-odm',
  2277.                 '2.1',
  2278.                 'The "%s" mapping type is deprecated. Use "%s" instead.',
  2279.                 $mapping['type'],
  2280.                 $deprecatedTypes[$mapping['type']],
  2281.             );
  2282.         }
  2283.         $this->fieldMappings[$mapping['fieldName']] = $mapping;
  2284.         if (isset($mapping['association'])) {
  2285.             $this->associationMappings[$mapping['fieldName']] = $mapping;
  2286.         }
  2287.         $accessor PropertyAccessorFactory::createPropertyAccessor($this->name$mapping['fieldName']);
  2288.         if (PHP_VERSION_ID >= 80400 && $accessor->getUnderlyingReflector()->isVirtual()) {
  2289.             throw MappingException::mappingVirtualPropertyNotAllowed($this->name$mapping['fieldName']);
  2290.         }
  2291.         if (isset($mapping['enumType'])) {
  2292.             if (! enum_exists($mapping['enumType'])) {
  2293.                 throw MappingException::nonEnumTypeMapped($this->name$mapping['fieldName'], $mapping['enumType']);
  2294.             }
  2295.             $reflectionEnum = new ReflectionEnum($mapping['enumType']);
  2296.             if (! $reflectionEnum->isBacked()) {
  2297.                 throw MappingException::nonBackedEnumMapped($this->name$mapping['fieldName'], $mapping['enumType']);
  2298.             }
  2299.             $accessor = new EnumPropertyAccessor($accessor$mapping['enumType']);
  2300.         }
  2301.         $this->propertyAccessors[$mapping['fieldName']] = $accessor;
  2302.         return $mapping;
  2303.     }
  2304.     /**
  2305.      * Determines which fields get serialized.
  2306.      *
  2307.      * It is only serialized what is necessary for best unserialization performance.
  2308.      * That means any metadata properties that are not set or empty or simply have
  2309.      * their default value are NOT serialized.
  2310.      *
  2311.      * Parts that are also NOT serialized because they cannot be properly unserialized:
  2312.      *      - reflClass (ReflectionClass)
  2313.      *      - reflFields (ReflectionProperty array)
  2314.      *      - propertyAccessors (ReflectionProperty array)
  2315.      *
  2316.      * @return array The names of all the fields that should be serialized.
  2317.      */
  2318.     public function __sleep()
  2319.     {
  2320.         // This metadata is always serialized/cached.
  2321.         $serialized = [
  2322.             'fieldMappings',
  2323.             'associationMappings',
  2324.             'identifier',
  2325.             'name',
  2326.             'db',
  2327.             'collection',
  2328.             'readPreference',
  2329.             'readPreferenceTags',
  2330.             'writeConcern',
  2331.             'rootDocumentName',
  2332.             'generatorType',
  2333.             'generatorOptions',
  2334.             'idGenerator',
  2335.             'indexes',
  2336.             'shardKey',
  2337.             'timeSeriesOptions',
  2338.         ];
  2339.         // The rest of the metadata is only serialized if necessary.
  2340.         if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
  2341.             $serialized[] = 'changeTrackingPolicy';
  2342.         }
  2343.         if ($this->customRepositoryClassName) {
  2344.             $serialized[] = 'customRepositoryClassName';
  2345.         }
  2346.         if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
  2347.             $serialized[] = 'inheritanceType';
  2348.             $serialized[] = 'discriminatorField';
  2349.             $serialized[] = 'discriminatorValue';
  2350.             $serialized[] = 'discriminatorMap';
  2351.             $serialized[] = 'defaultDiscriminatorValue';
  2352.             $serialized[] = 'parentClasses';
  2353.             $serialized[] = 'subClasses';
  2354.         }
  2355.         if ($this->isMappedSuperclass) {
  2356.             $serialized[] = 'isMappedSuperclass';
  2357.         }
  2358.         if ($this->isEmbeddedDocument) {
  2359.             $serialized[] = 'isEmbeddedDocument';
  2360.         }
  2361.         if ($this->isQueryResultDocument) {
  2362.             $serialized[] = 'isQueryResultDocument';
  2363.         }
  2364.         if ($this->isView()) {
  2365.             $serialized[] = 'isView';
  2366.             $serialized[] = 'rootClass';
  2367.         }
  2368.         if ($this->isFile) {
  2369.             $serialized[] = 'isFile';
  2370.             $serialized[] = 'bucketName';
  2371.             $serialized[] = 'chunkSizeBytes';
  2372.         }
  2373.         if ($this->isVersioned) {
  2374.             $serialized[] = 'isVersioned';
  2375.             $serialized[] = 'versionField';
  2376.         }
  2377.         if ($this->isLockable) {
  2378.             $serialized[] = 'isLockable';
  2379.             $serialized[] = 'lockField';
  2380.         }
  2381.         if ($this->lifecycleCallbacks) {
  2382.             $serialized[] = 'lifecycleCallbacks';
  2383.         }
  2384.         if ($this->collectionCapped) {
  2385.             $serialized[] = 'collectionCapped';
  2386.             $serialized[] = 'collectionSize';
  2387.             $serialized[] = 'collectionMax';
  2388.         }
  2389.         if ($this->isReadOnly) {
  2390.             $serialized[] = 'isReadOnly';
  2391.         }
  2392.         if ($this->validator !== null) {
  2393.             $serialized[] = 'validator';
  2394.             $serialized[] = 'validationAction';
  2395.             $serialized[] = 'validationLevel';
  2396.         }
  2397.         return $serialized;
  2398.     }
  2399.     /**
  2400.      * Restores some state that cannot be serialized/unserialized.
  2401.      */
  2402.     public function __wakeup(): void
  2403.     {
  2404.         // Restore ReflectionClass and properties
  2405.         $this->reflClass         = new ReflectionClass($this->name);
  2406.         $this->instantiator      = new Instantiator();
  2407.         $this->reflectionService = new RuntimeReflectionService();
  2408.         $this->reflFields        = new LegacyReflectionFields($this$this->reflectionService);
  2409.         foreach ($this->fieldMappings as $field => $mapping) {
  2410.             $accessor PropertyAccessorFactory::createPropertyAccessor($mapping['declared'] ?? $this->name$field);
  2411.             if (isset($mapping['enumType'])) {
  2412.                 $accessor = new EnumPropertyAccessor($accessor$mapping['enumType']);
  2413.             }
  2414.             $this->propertyAccessors[$field] = $accessor;
  2415.         }
  2416.     }
  2417.     /**
  2418.      * Creates a new instance of the mapped class, without invoking the constructor.
  2419.      *
  2420.      * @phpstan-return T
  2421.      */
  2422.     public function newInstance(): object
  2423.     {
  2424.         /** @phpstan-var T */
  2425.         return $this->instantiator->instantiate($this->name);
  2426.     }
  2427.     private function isAllowedGridFSField(string $name): bool
  2428.     {
  2429.         return in_array($nameself::ALLOWED_GRIDFS_FIELDStrue);
  2430.     }
  2431.     /** @phpstan-param FieldMapping $mapping */
  2432.     private function typeRequirementsAreMet(array $mapping): void
  2433.     {
  2434.         if ($mapping['type'] === Type::DECIMAL128 && ! extension_loaded('bcmath')) {
  2435.             throw MappingException::typeRequirementsNotFulfilled($this->name$mapping['fieldName'], Type::DECIMAL128'ext-bcmath is missing');
  2436.         }
  2437.     }
  2438.     /** @phpstan-param FieldMapping $mapping */
  2439.     private function checkDuplicateMapping(array $mapping): void
  2440.     {
  2441.         if ($mapping['notSaved'] ?? false) {
  2442.             return;
  2443.         }
  2444.         foreach ($this->fieldMappings as $fieldName => $otherMapping) {
  2445.             // Ignore fields with the same name - we can safely override their mapping
  2446.             if ($mapping['fieldName'] === $fieldName) {
  2447.                 continue;
  2448.             }
  2449.             // Ignore fields with a different name in the database
  2450.             if ($mapping['name'] !== $otherMapping['name']) {
  2451.                 continue;
  2452.             }
  2453.             // If the other field is not saved, ignore it as well
  2454.             if ($otherMapping['notSaved'] ?? false) {
  2455.                 continue;
  2456.             }
  2457.             throw MappingException::duplicateDatabaseFieldName($this->getName(), $mapping['fieldName'], $mapping['name'], $fieldName);
  2458.         }
  2459.     }
  2460.     private function isTypedProperty(string $name): bool
  2461.     {
  2462.         return $this->reflClass->hasProperty($name)
  2463.             && $this->reflClass->getProperty($name)->hasType();
  2464.     }
  2465.     /**
  2466.      * Validates & completes the given field mapping based on typed property.
  2467.      *
  2468.      * @phpstan-param FieldMappingConfig $mapping
  2469.      *
  2470.      * @return FieldMappingConfig
  2471.      */
  2472.     private function validateAndCompleteTypedFieldMapping(array $mapping): array
  2473.     {
  2474.         if (isset($mapping['type'])) {
  2475.             return $mapping;
  2476.         }
  2477.         $type $this->reflClass->getProperty($mapping['fieldName'])->getType();
  2478.         if (! $type instanceof ReflectionNamedType) {
  2479.             return $mapping;
  2480.         }
  2481.         if (! $type->isBuiltin() && Type::hasType($type->getName())) {
  2482.             $mapping['type'] = $type->getName();
  2483.             return $mapping;
  2484.         }
  2485.         if (! $type->isBuiltin() && enum_exists($type->getName())) {
  2486.             $reflection = new ReflectionEnum($type->getName());
  2487.             if (! $reflection->isBacked()) {
  2488.                 throw MappingException::nonBackedEnumMapped($this->name$mapping['fieldName'], $reflection->getName());
  2489.             }
  2490.             // Use the backing type of the enum for the mapping type
  2491.             $type $reflection->getBackingType();
  2492.             assert($type instanceof ReflectionNamedType);
  2493.             $mapping['enumType'] = $reflection->getName();
  2494.         }
  2495.         switch ($type->getName()) {
  2496.             case UuidV1::class:
  2497.             case UuidV4::class:
  2498.             case UuidV7::class:
  2499.                 $mapping['type'] = Type::UUID;
  2500.                 break;
  2501.             case DateTime::class:
  2502.                 $mapping['type'] = Type::DATE;
  2503.                 break;
  2504.             case DateTimeImmutable::class:
  2505.                 $mapping['type'] = Type::DATE_IMMUTABLE;
  2506.                 break;
  2507.             case 'array':
  2508.                 $mapping['type'] = Type::HASH;
  2509.                 break;
  2510.             case 'bool':
  2511.                 $mapping['type'] = Type::BOOL;
  2512.                 break;
  2513.             case 'float':
  2514.                 $mapping['type'] = Type::FLOAT;
  2515.                 break;
  2516.             case 'int':
  2517.                 $mapping['type'] = Type::INT;
  2518.                 break;
  2519.             case 'string':
  2520.                 $mapping['type'] = Type::STRING;
  2521.                 break;
  2522.         }
  2523.         return $mapping;
  2524.     }
  2525.     /**
  2526.      * Validates & completes the basic mapping information based on typed property.
  2527.      *
  2528.      * @phpstan-param FieldMappingConfig $mapping
  2529.      *
  2530.      * @return FieldMappingConfig
  2531.      */
  2532.     private function validateAndCompleteTypedManyAssociationMapping(array $mapping): array
  2533.     {
  2534.         $type $this->reflClass->getProperty($mapping['fieldName'])->getType();
  2535.         if (! $type instanceof ReflectionNamedType) {
  2536.             return $mapping;
  2537.         }
  2538.         if (! isset($mapping['collectionClass']) && class_exists($type->getName())) {
  2539.             $mapping['collectionClass'] = $type->getName();
  2540.         }
  2541.         return $mapping;
  2542.     }
  2543.     private function validateTimeSeriesOptions(TimeSeries $options): void
  2544.     {
  2545.         if (! $this->hasField($options->timeField)) {
  2546.             throw MappingException::timeSeriesFieldNotFound($this->name$options->timeField'time');
  2547.         }
  2548.         if ($options->metaField !== null && ! $this->hasField($options->metaField)) {
  2549.             throw MappingException::timeSeriesFieldNotFound($this->name$options->metaField'metadata');
  2550.         }
  2551.     }
  2552. }