diff --git a/.doctrine-project.json b/.doctrine-project.json index 61c211eec7..362e4d7f4f 100644 --- a/.doctrine-project.json +++ b/.doctrine-project.json @@ -5,142 +5,113 @@ "slug": "mongodb-odm", "docsSlug": "doctrine-mongodb-odm", "versions": [ + { + "name": "2.13", + "branchName": "2.13.x", + "slug": "latest", + "upcoming": true + }, + { + "name": "2.12", + "branchName": "2.12.x", + "slug": "latest", + "current": true + }, + { + "name": "2.11", + "branchName": "2.11.x", + "slug": "2.11", + "maintained": false + }, { "name": "2.10", "branchName": "2.10.x", - "slug": "latest", - "upcoming": true, - "aliases": [ - "2.10.x" - ] + "slug": "2.10", + "maintained": false }, { "name": "2.9", "branchName": "2.9.x", "slug": "2.9", - "current": true, - "aliases": [ - "current", - "stable", - "2.9.x" - ] + "maintained": false }, { "name": "2.8", "branchName": "2.8.x", "slug": "2.8", - "maintained": false, - "aliases": [ - "2.8.x" - ] + "maintained": false }, { "name": "2.7", "branchName": "2.7.x", "slug": "2.7", - "maintained": false, - "aliases": [ - "2.7.x" - ] + "maintained": false }, { "name": "2.6", "branchName": "2.6.x", "slug": "2.6", - "maintained": false, - "aliases": [ - "2.6.x" - ] + "maintained": false }, { "name": "2.5", "branchName": "2.5.x", "slug": "2.5", - "maintained": false, - "aliases": [ - "2.5.x" - ] + "maintained": false }, { "name": "2.4", "branchName": "2.4.x", "slug": "2.4", - "maintained": false, - "aliases": [ - "2.4.x" - ] + "maintained": false }, { "name": "2.3", "branchName": "2.3.x", "slug": "2.3", - "maintained": false, - "aliases": [ - "2.3.x" - ] + "maintained": false }, { "name": "2.2", "branchName": "2.2.x", "slug": "2.2", - "maintained": false, - "aliases": [ - "2.2.x" - ] + "maintained": false }, { "name": "2.1", "branchName": "2.1.x", "slug": "2.1", - "maintained": false, - "aliases": [ - "2.1.x" - ] + "maintained": false }, { "name": "2.0", "branchName": "2.0.x", "slug": "2.0", - "maintained": false, - "aliases": [ - "2.0.x" - ] + "maintained": false }, { "name": "1.3", "branchName": "1.3.x", "slug": "1.3", - "maintained": false, - "aliases": [ - "1.3.x" - ] + "maintained": false }, { "name": "1.2", "branchName": "1.2.x", "slug": "1.2", - "maintained": false, - "aliases": [ - "1.2.x" - ] + "maintained": false }, { "name": "1.1", "branchName": "1.1.x", "slug": "1.1", - "maintained": false, - "aliases": [ - "1.1.x" - ] + "maintained": false }, { "name": "1.0", "branchName": "1.0.x", "slug": "1.0", - "maintained": false, - "aliases": [ - "1.0.x" - ] + "maintained": false } ] } diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index e756cea228..4de5e5ff3b 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -11,4 +11,4 @@ on: jobs: coding-standards: name: "Coding Standards" - uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.2.2" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.3.0" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index aa6aeecf9e..54410af6da 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -62,6 +62,14 @@ jobs: dependencies: "highest" symfony-version: "stable" proxy: "lazy-ghost" + # Test with a 8.0 replica set + - topology: "replica_set" + php-version: "8.2" + mongodb-version: "8.0" + driver-version: "stable" + dependencies: "highest" + symfony-version: "stable" + proxy: "lazy-ghost" # Test with ProxyManager - php-version: "8.2" mongodb-version: "6.0" @@ -89,7 +97,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" with: fetch-depth: 2 @@ -155,3 +163,4 @@ jobs: env: DOCTRINE_MONGODB_SERVER: ${{ steps.setup-mongodb.outputs.cluster-uri }} USE_LAZY_GHOST_OBJECTS: ${{ matrix.proxy == 'lazy-ghost' && '1' || '0' }}" + CRYPT_SHARED_LIB_PATH: ${{ steps.setup-mongodb.outputs.crypt-shared-lib-path }} diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 906d17c7e8..8cf3021054 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -21,7 +21,7 @@ jobs: steps: - name: "Checkout code" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" - name: "Install PHP" uses: "shivammathur/setup-php@v2" diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index fd1487f4aa..7dce4fee8a 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -25,7 +25,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" - name: Setup cache environment id: extcache diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index c0bfaa8656..951002cd7d 100644 --- a/.github/workflows/release-on-milestone-closed.yml +++ b/.github/workflows/release-on-milestone-closed.yml @@ -8,7 +8,7 @@ on: jobs: release: name: "Git tag, release & create merge-up PR" - uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.2.2" + uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.3.0" secrets: GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index ac90e75989..acd9c56664 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -16,11 +16,11 @@ jobs: strategy: matrix: php-version: - - "8.2" + - "8.4" steps: - name: "Checkout code" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" - name: Setup cache environment id: extcache diff --git a/.github/workflows/website-schema.yml b/.github/workflows/website-schema.yml index e51b4a588e..3b30ece554 100644 --- a/.github/workflows/website-schema.yml +++ b/.github/workflows/website-schema.yml @@ -18,4 +18,4 @@ on: jobs: json-validate: name: "Validate JSON schema" - uses: "doctrine/.github/.github/workflows/website-schema.yml@7.2.2" + uses: "doctrine/.github/.github/workflows/website-schema.yml@7.3.0" diff --git a/composer.json b/composer.json index 221c40a328..6f38150dbf 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "doctrine/instantiator": "^1.1 || ^2", "doctrine/persistence": "^3.2 || ^4", "friendsofphp/proxy-manager-lts": "^1.0", - "mongodb/mongodb": "^1.21 || ^2.0@dev", + "mongodb/mongodb": "^1.21.2 || ^2.1.1", "psr/cache": "^1.0 || ^2.0 || ^3.0", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/deprecation-contracts": "^2.2 || ^3.0", @@ -44,8 +44,9 @@ "doctrine/orm": "^3.2", "jmikola/geojson": "^1.0", "phpbench/phpbench": "^1.0.0", - "phpstan/phpstan": "~1.10.67", - "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^10.4", "squizlabs/php_codesniffer": "^3.5", "symfony/cache": "^5.4 || ^6.0 || ^7.0" diff --git a/docs/en/cookbook/lookup-reference.rst b/docs/en/cookbook/lookup-reference.rst index 8d0c4e2d25..5f7fe8dd85 100644 --- a/docs/en/cookbook/lookup-reference.rst +++ b/docs/en/cookbook/lookup-reference.rst @@ -68,7 +68,7 @@ and a date. #[Field(type: 'date_immutable')] public DateTimeImmutable $date; - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( targetDocument: Item::class, cascade: 'all', @@ -378,7 +378,7 @@ You need to create a new class to hold the result of the aggregation. #[Field(type: 'date_immutable')] public DateTimeImmutable $date; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Item::class)] public Collection $items; @@ -631,7 +631,7 @@ but not the user. #[Field(type: 'string')] public string $name; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: UserOrderResult::class)] public Collection $orders; } @@ -645,7 +645,7 @@ but not the user. #[Field(type: 'date_immutable')] public DateTimeImmutable $date; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Item::class)] public Collection $items; } diff --git a/docs/en/cookbook/queryable-encryption.rst b/docs/en/cookbook/queryable-encryption.rst new file mode 100644 index 0000000000..cf0f0ed10b --- /dev/null +++ b/docs/en/cookbook/queryable-encryption.rst @@ -0,0 +1,271 @@ +Queryable Encryption +==================== + +This cookbook provides a tutorial on setting up and using Queryable Encryption +(QE) with Doctrine MongoDB ODM to protect sensitive data in your documents. + +Introduction +------------ + +In many applications, you need to store sensitive information like social +security numbers, financial data, or personal details. MongoDB's Queryable +Encryption allows you to encrypt this data on the client-side, store it as +fully randomized encrypted data, and still run expressive queries on it. This +ensures that sensitive data is never exposed in an unencrypted state on the +server, in system logs, or in backups. + +This tutorial will guide you through the process of securing a document's +fields using queryable encryption, from defining the document and configuring +the connection to storing and querying the encrypted data. + +.. note:: + + Queryable Encryption is only available on MongoDB Enterprise 7.0+ or + MongoDB Atlas. + +The Scenario +------------ + +We will model a ``Patient`` document that has an embedded ``PatientRecord``. +This record contains sensitive information: + +- A Social Security Number (``ssn``), which we need to query for exact + matches. +- A ``billingAmount``, which should support range queries. +- A ``billing`` object, which should be encrypted but not directly queryable. + +Defining the Documents +---------------------- + +First, let's define our ``Patient``, ``PatientRecord``, and ``Billing`` +classes. We use the :ref:`#[Encrypt] ` attribute to mark +fields that require encryption. + +.. code-block:: php + + .datakeys`` by default, but you can change it using the +``keyVaultNamespace`` option. + +.. code-block:: php + + setAutoEncryption([ + 'keyVaultNamespace' => 'encryption.datakeys' + ]); + $config->setKmsProvider([ + 'type' => 'local', + 'key' => new Binary($masterKey), + ]); + + // Other configuration + $config->setProxyDir(__DIR__ . '/Proxies'); + $config->setProxyNamespace('Proxies'); + $config->setHydratorDir(__DIR__ . '/Hydrators'); + $config->setHydratorNamespace('Hydrators'); + $config->setPersistentCollectionDir(__DIR__ . '/PersistentCollections'); + $config->setPersistentCollectionNamespace('PersistentCollections'); + $config->setDefaultDB('my_db'); + $config->setMetadataDriverImpl(new AttributeDriver([__DIR__])); + +Step 2: Create the DocumentManager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``MongoDB\Client`` will be instantiated with the options from the +configuration. + +.. code-block:: php + + getDriverOptions(), + ); + $documentManager = DocumentManager::create($client, $config); + +The ``driverOptions`` passed to the client contain the ``autoEncryption`` option +that was configured in the previous step. + +Step 3: Create the Encrypted Collection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Next, we use the ``SchemaManager`` to create the collection with the necessary +encryption metadata. To make the example re-runnable, we can drop the collection +first. + +.. code-block:: php + + getSchemaManager(); + $schemaManager->dropDocumentCollection(Patient::class); + $schemaManager->createDocumentCollection(Patient::class); + +Step 4: Persist and Query Documents +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Finally, we can persist and query documents as usual. The encryption and +decryption will be handled automatically. + +.. code-block:: php + + patientRecord = new PatientRecord(); + $patient->patientRecord->ssn = '123-456-7890'; + $patient->patientRecord->billingAmount = 1500; + $patient->patientRecord->billing = new Billing(); + $patient->patientRecord->billing->creditCardNumber = '9876-5432-1098-7654'; + + $documentManager->persist($patient); + $documentManager->flush(); + $documentManager->clear(); + + // Query the document using an encrypted field + $foundPatient = $documentManager->getRepository(Patient::class)->findOneBy([ + 'patientRecord.ssn' => '123-456-7890', + ]); + + // The document is retrieved and its fields are automatically decrypted + assert($foundPatient instanceof Patient); + assert($foundPatient->patientRecord->billingAmount === 1500); + +What the Document Looks Like in the Database +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you inspect the document directly in the database (e.g., using ``mongosh`` +or `MongoDB Compass`_), you will see that the fields marked with ``#[Encrypt]`` +are stored as BSON binary data (subtype 6), not the original BSON type. The +driver also adds a ``__safeContent__`` field to the document. For more details, +see the `Queryable Encryption Fundamentals`_ in the MongoDB manual. + +.. code-block:: js + + { + "_id": ObjectId("..."), + "patientRecord": { + "ssn": Binary("...", 6), + "billing": Binary("...", 6), + "billingAmount": Binary("...", 6) + }, + "__safeContent__": [ + Binary("...", 0) + ] + } + +Limitations +----------- + +- The ODM simplifies configuration by supporting a single KMS provider per + ``DocumentManager`` through ``Configuration::setKmsProvider()``. If you need + to work with multiple KMS providers, you must manually configure the + ``kmsProviders`` array and pass it as a driver option, bypassing the ODM's + helper method. +- Automatic generation of the ``encryptedFieldsMap`` is not compatible with + ``SINGLE_COLLECTION`` inheritance. Because all classes in the hierarchy + share a single collection, the ``SchemaManager`` cannot merge their encrypted + fields before creating the collection. +- Embedded documents and collections are encrypted as a whole. As such, + they cannot be partially updated. Only the ``set*`` and ``atomicSet*`` + collection strategies can be used. +- For a complete list of limitations, please refer to the official + `Queryable Encryption Limitations`_ documentation. + +.. _MongoDB\Driver\Manager: https://www.php.net/manual/en/mongodb-driver-manager.construct.php#mongodb-driver-manager.construct-autoencryption +.. _MongoDB Compass: https://www.mongodb.com/products/compass +.. _Queryable Encryption Fundamentals: https://www.mongodb.com/docs/manual/core/queryable-encryption/fundamentals/#behavior +.. _Queryable Encryption Limitations: https://www.mongodb.com/docs/manual/core/queryable-encryption/reference/limitations/ diff --git a/docs/en/cookbook/simple-search-engine.rst b/docs/en/cookbook/simple-search-engine.rst index 267e860954..a2cdedb72c 100644 --- a/docs/en/cookbook/simple-search-engine.rst +++ b/docs/en/cookbook/simple-search-engine.rst @@ -141,7 +141,7 @@ Now you can embed the ``Keyword`` document many times in the ``Product``: { // ... - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Keyword::class)] public Collection $keywords; diff --git a/docs/en/reference/aggregation-stage-reference.rst b/docs/en/reference/aggregation-stage-reference.rst index 767ca625a4..a5926aec16 100644 --- a/docs/en/reference/aggregation-stage-reference.rst +++ b/docs/en/reference/aggregation-stage-reference.rst @@ -381,7 +381,7 @@ pipeline stages. Take the following relationship for example: class Orders { - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( targetDocument: Item::class, cascade: 'all', diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst index 1b3927a7f4..601934d6ef 100644 --- a/docs/en/reference/attributes-reference.rst +++ b/docs/en/reference/attributes-reference.rst @@ -217,7 +217,7 @@ Optional arguments: class User { - /** @var Collection */ + /** @var Collection */ #[EmbedMany( strategy:'set', discriminatorField:'type', @@ -336,6 +336,94 @@ Unlike normal documents, embedded documents cannot specify their own database or collection. That said, a single embedded document class may be used with multiple document classes, and even other embedded documents! +.. _encrypt_attribute: + +#[Encrypt] +---------- + +The ``#[Encrypt]`` attribute is used to define an encrypted field mapping for a +document property. It allows you to configure fields for automatic and queryable +encryption in MongoDB. + +Optional arguments: + +- ``queryType`` - Specifies the query type for the field. Possible values: + - ``null`` (default) - Field is not queryable. + - ``EncryptQuery::Equality`` - Enables equality queries. + - ``EncryptQuery::Range`` - Enables range queries. +- ``min``, ``max`` - Specify minimum and maximum (inclusive) queryable values + for a field when possible, as smaller bounds improve query efficiency. If + querying values outside of these bounds, MongoDB returns an error. +- ``sparsity``, ``precision``, ``trimFactor``, ``contention`` - For advanced + users only. The default values for these options are suitable for the majority + of use cases, and should only be modified if your use case requires it. + +.. note:: + + Queryable encryption is only supported in MongoDB version 7.0 and later. + +Example: + +.. code-block:: php + + `_. + + +.. note:: + + The encrypted collection must be created with the `Schema Manager`_ before + before inserting documents. + +.. note:: + + Due to the way the encrypted fields map is generated, the queryable encryption + is not compatible with ``SINGLE_COLLECTION`` inheritance. + #[Field] -------- @@ -987,7 +1075,7 @@ Optional arguments: class User { - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( strategy: 'set', targetDocument: Item::class, @@ -1399,5 +1487,6 @@ root class specified in the view mapping. .. _DBRef: https://docs.mongodb.com/manual/reference/database-references/#dbrefs .. _geoNear command: https://docs.mongodb.com/manual/reference/command/geoNear/ .. _MongoDB\BSON\ObjectId: https://www.php.net/class.mongodb-bson-objectid +.. _Schema Manager: ../reference/migrating-schemas .. |FQCN| raw:: html FQCN diff --git a/docs/en/reference/basic-mapping.rst b/docs/en/reference/basic-mapping.rst index 2c45ed8790..397ff3df91 100644 --- a/docs/en/reference/basic-mapping.rst +++ b/docs/en/reference/basic-mapping.rst @@ -145,6 +145,7 @@ Here is a quick overview of the built-in mapping types: - ``hash`` - ``id`` - ``int`` +- ``int64`` - ``key`` - ``object_id`` - ``raw`` diff --git a/docs/en/reference/bidirectional-references.rst b/docs/en/reference/bidirectional-references.rst index d6b1550c10..d2edeff631 100644 --- a/docs/en/reference/bidirectional-references.rst +++ b/docs/en/reference/bidirectional-references.rst @@ -23,7 +23,7 @@ and changes are tracked and persisted separately. Here is an example: { // ... - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: BlogPost::class)] private Collection $posts; } @@ -66,7 +66,7 @@ One to Many { // ... - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: BlogPost::class, mappedBy: 'user')] private Collection $posts; } @@ -189,11 +189,11 @@ Self-Referencing Many to Many { // ... - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: User::class, mappedBy: 'myFriends')] public Collection $friendsWithMe; - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: User::class, inversedBy: 'friendsWithMe')] public Collection $myFriends; diff --git a/docs/en/reference/complex-references.rst b/docs/en/reference/complex-references.rst index d3a899766a..8daba330ea 100644 --- a/docs/en/reference/complex-references.rst +++ b/docs/en/reference/complex-references.rst @@ -35,11 +35,11 @@ querying by the BlogPost's ID. { // ... - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: Comment::class, mappedBy: 'blogPost')] private Collection $comments; - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( targetDocument: Comment::class, mappedBy: 'blogPost', @@ -88,7 +88,7 @@ administrators: class BlogPost { - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( targetDocument: Comment::class, mappedBy: 'blogPost', @@ -109,7 +109,7 @@ call on the Comment repository class to populate the reference. class BlogPost { - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( targetDocument: Comment::class, mappedBy: 'blogPost', diff --git a/docs/en/reference/custom-collections.rst b/docs/en/reference/custom-collections.rst index 3727e372a5..ca1ec32bba 100644 --- a/docs/en/reference/custom-collections.rst +++ b/docs/en/reference/custom-collections.rst @@ -20,7 +20,7 @@ persistence-related features. { // ... - /** @var Collection
*/ + /** @var Collection */ #[EmbedMany(targetDocument: Section::class)] public Collection $sections; @@ -55,7 +55,7 @@ and ensuring that your custom class is initialized in the owning class' construc { // ... - /** @var Collection
*/ + /** @var Collection */ #[EmbedMany( collectionClass: SectionCollection::class, targetDocument: Section::class, diff --git a/docs/en/reference/custom-mapping-types.rst b/docs/en/reference/custom-mapping-types.rst index abee5d13b1..2e18807193 100644 --- a/docs/en/reference/custom-mapping-types.rst +++ b/docs/en/reference/custom-mapping-types.rst @@ -25,6 +25,7 @@ a ``DateTimeImmutable`` when the data is read from the database. use Doctrine\ODM\MongoDB\Types\ClosureToPHP; use Doctrine\ODM\MongoDB\Types\Type; use MongoDB\BSON\UTCDateTime; + use RuntimeException; class DateTimeWithTimezoneType extends Type { @@ -33,6 +34,10 @@ a ``DateTimeImmutable`` when the data is read from the database. public function convertToPHPValue($value): DateTimeImmutable { + if (!isset($value['utc'], $value['tz'])) { + throw new RuntimeException('Database value cannot be converted to date with timezone. Expected array with "utc" and "tz" keys.'); + } + $timeZone = new DateTimeZone($value['tz']); $dateTime = $value['utc'] ->toDateTime() @@ -43,8 +48,13 @@ a ``DateTimeImmutable`` when the data is read from the database. public function convertToDatabaseValue($value): array { - if (! isset($value['utc'], $value['tz'])) { - throw new RuntimeException('Database value cannot be converted to date with timezone. Expected array with "utc" and "tz" keys.'); + if (!$value instanceof DateTimeImmutable) { + throw new \RuntimeException( + sprintf( + 'Expected instance of \DateTimeImmutable, got %s', + gettype($value) + ) + ); } return [ diff --git a/docs/en/reference/embedded-mapping.rst b/docs/en/reference/embedded-mapping.rst index 86691e8401..d413379973 100644 --- a/docs/en/reference/embedded-mapping.rst +++ b/docs/en/reference/embedded-mapping.rst @@ -73,7 +73,7 @@ Embed many documents: { // ... - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Phonenumber::class)] private Collection $phoneNumbers; @@ -283,7 +283,7 @@ You can achieve this behavior by using the `storeEmptyArray` option for embedded { // ... - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: PhoneNumber::class, storeEmptyArray: true)] private Collection $phoneNumbers; // ... diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index 383313300b..e67b56d6af 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -128,65 +128,65 @@ The DocumentManager and UnitOfWork trigger several events during the life-time of their registered documents. - - preRemove - The preRemove event occurs for a given document before + ``preRemove`` - The preRemove event occurs for a given document before the respective DocumentManager remove operation for that document is executed. - - postRemove - The postRemove event occurs for a document after the + ``postRemove`` - The postRemove event occurs for a document after the document has been removed. It will be invoked after the database delete operations. - - prePersist - The prePersist event occurs for a given document + ``prePersist`` - The prePersist event occurs for a given document before the respective DocumentManager persist operation for that document is executed. - - postPersist - The postPersist event occurs for a document after + ``postPersist`` - The postPersist event occurs for a document after the document has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event. - - preUpdate - The preUpdate event occurs before the database update + ``preUpdate`` - The preUpdate event occurs before the database update operations to document data. - - postUpdate - The postUpdate event occurs after the database update + ``postUpdate`` - The postUpdate event occurs after the database update operations to document data. - - preLoad - The preLoad event occurs for a document before the + ``preLoad`` - The preLoad event occurs for a document before the document has been loaded into the current DocumentManager from the database or after the refresh operation has been applied to it. - - postLoad - The postLoad event occurs for a document after the + ``postLoad`` - The postLoad event occurs for a document after the document has been loaded into the current DocumentManager from the database or after the refresh operation has been applied to it. - - loadClassMetadata - The loadClassMetadata event occurs after the + ``loadClassMetadata`` - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (attributes/xml). - - onClassMetadataNotFound - Loading class metadata for a particular + ``onClassMetadataNotFound`` - Loading class metadata for a particular requested class name failed. Manipulating the given event args instance allows providing fallback metadata even when no actual metadata exists or could be found. This event is not a lifecycle callback. Support for this event was added in MongoDB ODM 1.3. - - preFlush - The preFlush event occurs before the change-sets of all + ``preFlush`` - The preFlush event occurs before the change-sets of all managed documents are computed. This both a lifecycle call back and and listener. - - postFlush - The postFlush event occurs after the change-sets of all + ``postFlush`` - The postFlush event occurs after the change-sets of all managed documents are computed. - - onFlush - The onFlush event occurs after the change-sets of all + ``onFlush`` - The onFlush event occurs after the change-sets of all managed documents are computed. This event is not a lifecycle callback. - - onClear - The onClear event occurs after the UnitOfWork has had + ``onClear`` - The onClear event occurs after the UnitOfWork has had its state cleared. - - documentNotFound - The documentNotFound event occurs when a proxy object + ``documentNotFound`` - The documentNotFound event occurs when a proxy object could not be initialized. This event is not a lifecycle callback. - - postCollectionLoad - The postCollectionLoad event occurs just after + ``postCollectionLoad`` - The postCollectionLoad event occurs just after collection has been initialized (loaded) and before new elements are re-added to it. diff --git a/docs/en/reference/indexes.rst b/docs/en/reference/indexes.rst index 520292dced..8a838d1055 100644 --- a/docs/en/reference/indexes.rst +++ b/docs/en/reference/indexes.rst @@ -246,7 +246,7 @@ Now if we had a ``BlogPost`` document with the ``Comment`` document embedded man #[Index] private string $slug; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Comment::class)] private Collection $comments; } diff --git a/docs/en/reference/introduction.rst b/docs/en/reference/introduction.rst index 4452b34e98..c7a06ec622 100644 --- a/docs/en/reference/introduction.rst +++ b/docs/en/reference/introduction.rst @@ -87,7 +87,7 @@ the features. #[ODM\Document] class Manager extends BaseEmployee { - /** @var Collection */ + /** @var Collection */ #[ODM\ReferenceMany(targetDocument: Project::class)] public Collection $projects; diff --git a/docs/en/reference/metadata-drivers.rst b/docs/en/reference/metadata-drivers.rst index 2c42bdffad..3552dab6d5 100644 --- a/docs/en/reference/metadata-drivers.rst +++ b/docs/en/reference/metadata-drivers.rst @@ -48,6 +48,55 @@ namespace: $driver = new \Doctrine\ODM\MongoDB\Mapping\Driver\XmlDriver('/path/to/mapping/files'); $em->getConfiguration()->setMetadataDriverImpl($driver); +The ``AttributeDriver`` can be initialized from a list of directories that +contain the PHP classes with ODM attributes, or from an instance of a class +locator that will find the classes: + +From a list of directories: + +.. code-block:: php + + getConfiguration()->setMetadataDriverImpl($driver); + +Using Symfony Finder to locate classes. For example, if you are using Vertical +Slice architecture, you can exclude ``*Test.php``, ``*Controller.php``, +``*Service.php``, etc.: + +.. code-block:: php + files()->in([__DIR__ . '/src/']) + ->name('*.php') + ->notName(['*Test.php', '*Controller.php', '*Service.php']); + + $classLocator = new FileClassLocator($finder); + + $driver = new AttributeDriver($classLocator); + $em->getConfiguration()->setMetadataDriverImpl($driver); + +If you know the list of class names you want to track, use +``Doctrine\Persistence\Mapping\Driver\ClassNames``: + +.. code-block:: php + getConfiguration()->setMetadataDriverImpl($driver); + Implementing Metadata Drivers ----------------------------- diff --git a/docs/en/reference/migrating-schemas.rst b/docs/en/reference/migrating-schemas.rst index 85fce2d0f7..0b979572b0 100644 --- a/docs/en/reference/migrating-schemas.rst +++ b/docs/en/reference/migrating-schemas.rst @@ -15,6 +15,44 @@ problem! for the Google App Engine datastore. Additional information may be found in the `Objectify schema migration`_ documentation. +Creating a collection +-------------------- + +Collections are automatically created by the MongoDB server upon first insertion. +You must explicitly create the collections if you need specific options, such as +validation rules. In particular, encrypted collections must be created explicitly. + +.. code-block:: php + + getSchemaManager(); + +To create the collections for all the document classes, you can use the +`createCollections()` method on the ``DocumentManager``: + +.. code-block:: php + + createCollections(); + +For a specific document class, you can use the `createDocumentCollection()` +method with the class name as an argument: + + createDocumentCollection(Person::class); + +Once the collection is created, you can also set up indexes with ``ensureIndexes``, +and search indexes with ``createSearchIndexes``: + + ensureIndexes(); + $schemaManager->createSearchIndexes(); + Renaming a Field ---------------- diff --git a/docs/en/reference/priming-references.rst b/docs/en/reference/priming-references.rst index dbe4dbfe38..3378dc739e 100644 --- a/docs/en/reference/priming-references.rst +++ b/docs/en/reference/priming-references.rst @@ -18,7 +18,7 @@ Consider the following abbreviated model: #[Document] class User { - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: Account::class)] private Collection $accounts; } @@ -112,7 +112,7 @@ specifying them in the mapping: #[Document] class User { - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: Account::class, prime: ['user'])] private Collection $accounts; } diff --git a/docs/en/reference/reference-mapping.rst b/docs/en/reference/reference-mapping.rst index f40776a714..eaf4e1efa1 100644 --- a/docs/en/reference/reference-mapping.rst +++ b/docs/en/reference/reference-mapping.rst @@ -94,7 +94,7 @@ Reference many documents: { // ... - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: Account::class)] private Collection $accounts; @@ -205,7 +205,7 @@ in each `DBRef`_ object: { // .. - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( discriminatorMap: [ 'album' => Album::class, @@ -240,7 +240,7 @@ a certain class, you can optionally specify a default discriminator value: { // .. - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( discriminatorMap: [ 'album' => Album::class, @@ -453,7 +453,7 @@ You can achieve this behavior by using the `storeEmptyArray` option. { // ... - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: Account::class, storeEmptyArray: true)] private Collection $accounts; diff --git a/docs/en/reference/trees.rst b/docs/en/reference/trees.rst index 2b5c1b302c..1f818822d5 100644 --- a/docs/en/reference/trees.rst +++ b/docs/en/reference/trees.rst @@ -23,7 +23,7 @@ Full Tree in Single Document #[Field(type: 'string')] private string $body; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Comment::class)] private Collection $comments; @@ -39,7 +39,7 @@ Full Tree in Single Document #[Field(type: 'string')] private string $text; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Comment::class)] private Collection $replies; @@ -115,7 +115,7 @@ Child Reference #[Field(type: 'string')] private string $name; - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: Category::class)] #[Index] private Collection $children; @@ -172,12 +172,12 @@ Array of Ancestors #[Id] private string $id; - /** @var Collection */ + /** @var Collection */ #[ReferenceMany(targetDocument: Category::class)] #[Index] private Collection $ancestors; - /** @var Collection */ + /** @var Collection */ #[ReferenceOne(targetDocument: Category::class)] #[Index] private ?Category $parent = null; diff --git a/docs/en/reference/working-with-objects.rst b/docs/en/reference/working-with-objects.rst index e96fd3a13b..c34921623a 100644 --- a/docs/en/reference/working-with-objects.rst +++ b/docs/en/reference/working-with-objects.rst @@ -423,7 +423,7 @@ in the $addresses collection. class User { //... - /** @var Collection
*/ + /** @var Collection */ #[ReferenceMany(targetDocument: Address::class, cascade: ['persist', 'remove'])] private Collection $addresses; //... diff --git a/doctrine-mongo-mapping.xsd b/doctrine-mongo-mapping.xsd index 90d3c8fdee..6ff939cd59 100644 --- a/doctrine-mongo-mapping.xsd +++ b/doctrine-mongo-mapping.xsd @@ -16,9 +16,9 @@ - - - + + + @@ -26,15 +26,71 @@ - + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + @@ -128,7 +184,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -199,6 +279,7 @@ + @@ -214,6 +295,7 @@ + @@ -480,7 +562,7 @@ - + diff --git a/lib/Doctrine/ODM/MongoDB/APM/Command.php b/lib/Doctrine/ODM/MongoDB/APM/Command.php index 1bc16b9b92..2302d9d866 100644 --- a/lib/Doctrine/ODM/MongoDB/APM/Command.php +++ b/lib/Doctrine/ODM/MongoDB/APM/Command.php @@ -11,6 +11,8 @@ use MongoDB\Driver\Server; use Throwable; +use function method_exists; + final class Command { private CommandStartedEvent $startedEvent; @@ -70,11 +72,26 @@ public function getRequestId(): string return $this->startedEvent->getRequestId(); } + /** @deprecated This method is failing with MongoDB Extension v2.0+, use getHost and getPort instead. */ public function getServer(): Server { + if (! method_exists($this->finishedEvent, 'getServer')) { + throw new LogicException('getServer() is not available in MongoDB Extension v2.0+'); + } + return $this->finishedEvent->getServer(); } + public function getPort(): int + { + return $this->finishedEvent->getPort(); + } + + public function getHost(): string + { + return $this->finishedEvent->getHost(); + } + public function getReply(): object { return $this->finishedEvent->getReply(); diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php index 32379a82ca..013e7e1bdd 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php @@ -11,12 +11,10 @@ use Doctrine\ODM\MongoDB\Iterator\Iterator; use Doctrine\ODM\MongoDB\Iterator\UnrewindableIterator; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Iterator as SPLIterator; use MongoDB\Collection; use MongoDB\Driver\CursorInterface; use function array_merge; -use function assert; /** @phpstan-import-type PipelineExpression from Builder */ final class Aggregation implements IterableResult @@ -56,14 +54,11 @@ public function getIterator(): Iterator $options = array_merge($this->options, ['cursor' => true]); $cursor = $this->collection->aggregate($this->pipeline, $options); - // This assertion can be dropped when requiring mongodb/mongodb 1.17.0 - assert($cursor instanceof CursorInterface); - assert($cursor instanceof SPLIterator); return $this->prepareIterator($cursor); } - private function prepareIterator(CursorInterface&SPLIterator $cursor): Iterator + private function prepareIterator(CursorInterface $cursor): Iterator { if ($this->classMetadata) { $cursor = new HydratingIterator($cursor, $this->dm->getUnitOfWork(), $this->classMetadata); diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php index fe84d3107e..03bf828d8a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php @@ -78,6 +78,7 @@ class Expr implements /** @var array{case: mixed|self, then?: mixed|self}|null */ private ?array $switchBranch = null; + /** @final This constructor is used in {@see self::expr()} */ public function __construct(private DocumentManager $dm, private ClassMetadata $class) { } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php index e1eea694c5..dc38503739 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php @@ -34,6 +34,8 @@ * maxNumPassages?: int, * }, * returnStoredSource?: bool, + * searchBefore?: string, + * searchAfter?: string, * sort?: object, * autocomplete?: object, * compound?: object, @@ -61,6 +63,8 @@ class Search extends Stage implements SupportsAllSearchOperators private ?object $count = null; private ?object $highlight = null; private ?bool $returnStoredSource = null; + private ?string $searchBefore = null; + private ?string $searchAfter = null; private ?SearchOperator $operator = null; /** @var array */ @@ -92,6 +96,14 @@ public function getExpression(): array $params->returnStoredSource = $this->returnStoredSource; } + if ($this->searchBefore) { + $params->searchBefore = $this->searchBefore; + } + + if ($this->searchAfter) { + $params->searchAfter = $this->searchAfter; + } + if ($this->sort) { $params->sort = (object) $this->sort; } @@ -145,6 +157,20 @@ public function returnStoredSource(bool $returnStoredSource = true): static return $this; } + public function searchBefore(string $searchBefore): static + { + $this->searchBefore = $searchBefore; + + return $this; + } + + public function searchAfter(string $searchAfter): static + { + $this->searchAfter = $searchAfter; + + return $this; + } + /** * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index 04ae28d7ac..be717bf644 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -4,6 +4,7 @@ namespace Doctrine\ODM\MongoDB; +use Composer\InstalledVersions; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\Psr6\CacheAdapter; @@ -25,6 +26,8 @@ use Doctrine\Persistence\ObjectRepository; use InvalidArgumentException; use LogicException; +use MongoDB\Client; +use MongoDB\Driver\Manager; use MongoDB\Driver\WriteConcern; use ProxyManager\Configuration as ProxyManagerConfiguration; use ProxyManager\Factory\LazyLoadingGhostFactory; @@ -32,10 +35,15 @@ use ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy; use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; +use stdClass; +use Throwable; +use function array_diff_key; +use function array_intersect_key; use function array_key_exists; use function class_exists; use function interface_exists; +use function is_string; use function trigger_deprecation; use function trim; @@ -50,6 +58,7 @@ * $dm = DocumentManager::create(new Connection(), $config); * * @phpstan-import-type CommitOptions from UnitOfWork + * @phpstan-type KmsProvider array{type: string, ...} */ class Configuration { @@ -121,7 +130,10 @@ class Configuration * persistentCollectionNamespace?: string, * proxyDir?: string, * proxyNamespace?: string, - * repositoryFactory?: RepositoryFactory + * repositoryFactory?: RepositoryFactory, + * kmsProvider?: KmsProvider, + * defaultMasterKey?: array|null, + * autoEncryption?: array, * } */ private array $attributes = []; @@ -135,6 +147,50 @@ class Configuration private bool $useLazyGhostObject = false; + private static string $version; + + /** + * Provides the driver options to be used when creating the MongoDB client. + * + * @return array + */ + public function getDriverOptions(): array + { + $driverOptions = [ + 'driver' => [ + 'name' => 'doctrine-odm', + 'version' => self::getVersion(), + ], + ]; + + if (isset($this->attributes['kmsProvider'])) { + $driverOptions['autoEncryption'] = $this->getAutoEncryptionOptions(); + } + + return $driverOptions; + } + + /** + * Get options to create a ClientEncryption instance. + * + * @see https://www.php.net/manual/en/mongodb-driver-clientencryption.construct.php + * + * @return array{keyVaultClient?: Client|Manager, keyVaultNamespace: string, kmsProviders: array, tlsOptions?: array} + */ + public function getClientEncryptionOptions(): array + { + if (! isset($this->attributes['kmsProvider'])) { + throw ConfigurationException::clientEncryptionOptionsNotSet(); + } + + return array_intersect_key($this->getAutoEncryptionOptions(), [ + 'keyVaultClient' => 1, + 'keyVaultNamespace' => 1, + 'kmsProviders' => 1, + 'tlsOptions' => 1, + ]); + } + /** * Adds a namespace under a certain alias. */ @@ -286,8 +342,6 @@ public function getAutoGenerateProxyClasses(): int * during each script execution. * * @param self::AUTOGENERATE_* $mode - * - * @throws InvalidArgumentException If an invalid mode was given. */ public function setAutoGenerateProxyClasses(int $mode): void { @@ -653,6 +707,107 @@ public function isLazyGhostObjectEnabled(): bool { return $this->useLazyGhostObject; } + + /** + * Set the KMS provider to use for auto-encryption. The name of the KMS provider + * must be specified in the 'type' key of the array. + * + * @see https://www.php.net/manual/en/mongodb-driver-clientencryption.construct.php + * + * @param KmsProvider $kmsProvider + */ + public function setKmsProvider(array $kmsProvider): void + { + if (! isset($kmsProvider['type'])) { + throw ConfigurationException::kmsProviderTypeRequired(); + } + + if (! is_string($kmsProvider['type'])) { + throw ConfigurationException::kmsProviderTypeMustBeString(); + } + + $this->attributes['kmsProvider'] = $kmsProvider; + } + + /** + * Set the default master key to use when creating encrypted collections. + * + * @param array|null $masterKey + */ + public function setDefaultMasterKey(?array $masterKey): void + { + $this->attributes['defaultMasterKey'] = $masterKey; + } + + /** + * Set the options for auto-encryption. + * + * @see https://www.php.net/manual/en/mongodb-driver-manager.construct.php#mongodb-driver-manager.construct-autoencryption + * + * @param array{ keyVaultClient?: Client|Manager, keyVaultNamespace?: string, tlsOptions?: array, schemaMap?: array, bypassAutoEncryption?: bool, bypassQueryAnalysis?: bool, encryptedFieldsMap?: array, extraOptions?: array} $options + */ + public function setAutoEncryption(array $options): void + { + if (isset($options['kmsProviders'])) { + throw ConfigurationException::kmsProvidersOptionMustUseSetter(); + } + + $this->attributes['autoEncryption'] = $options; + } + + /** + * Get the default KMS provider name used when creating encrypted collections. + */ + public function getDefaultKmsProvider(): ?string + { + return $this->attributes['kmsProvider']['type'] ?? null; + } + + /** + * Get the default master key used when creating encrypted collections. + * + * @return array|null + */ + public function getDefaultMasterKey(): ?array + { + if (! isset($this->attributes['kmsProvider']) || $this->attributes['kmsProvider']['type'] === 'local') { + return null; + } + + return $this->attributes['defaultMasterKey'] ?? throw ConfigurationException::masterKeyRequired($this->attributes['kmsProvider']['type']); + } + + private static function getVersion(): string + { + if (! isset(self::$version)) { + try { + return self::$version ??= InstalledVersions::getPrettyVersion('doctrine/mongodb-odm') ?? 'unknown'; + } catch (Throwable) { + return self::$version = 'unknown'; + } + } + + return self::$version; + } + + /** @return array */ + private function getAutoEncryptionOptions(): array + { + $kmsProviderName = $this->attributes['kmsProvider']['type']; + $kmsProviderOpts = array_diff_key($this->attributes['kmsProvider'], ['type' => 0]); + // To use "Automatic Credentials", the provider options must be an empty document. + // Fix the empty array to an empty stdClass object, as the driver expects it. + if ($kmsProviderOpts === []) { + $kmsProviderOpts = new stdClass(); + } + + return [ + // Each kmsProvider must be an object, it can be empty + 'kmsProviders' => [$kmsProviderName => $kmsProviderOpts], + 'keyVaultNamespace' => $this->getDefaultDB() . '.datakeys', + ...$this->attributes['autoEncryption'] ?? [], + ]; + } } interface_exists(MappingDriver::class); diff --git a/lib/Doctrine/ODM/MongoDB/ConfigurationException.php b/lib/Doctrine/ODM/MongoDB/ConfigurationException.php index dd1bad9a43..21ff646573 100644 --- a/lib/Doctrine/ODM/MongoDB/ConfigurationException.php +++ b/lib/Doctrine/ODM/MongoDB/ConfigurationException.php @@ -6,6 +6,8 @@ use Exception; +use function sprintf; + final class ConfigurationException extends Exception { public static function persistentCollectionDirMissing(): self @@ -27,4 +29,29 @@ public static function proxyDirMissing(): self { return new self('No proxy directory was configured. Please set a target directory first!'); } + + public static function clientEncryptionOptionsNotSet(): self + { + return new self('MongoDB client encryption options are not set in configuration'); + } + + public static function kmsProviderTypeRequired(): self + { + return new self('The KMS provider "type" is required'); + } + + public static function kmsProviderTypeMustBeString(): self + { + return new self('The KMS provider "type" must be a non-empty string'); + } + + public static function kmsProvidersOptionMustUseSetter(): self + { + return new self('The "kmsProviders" encryption option must be set using the "setKmsProvider()" method'); + } + + public static function masterKeyRequired(string $provider): self + { + return new self(sprintf('The "masterKey" configuration is required for the KMS provider "%s"', $provider)); + } } diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index 21f7cbc73b..b9f440aefb 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -4,7 +4,6 @@ namespace Doctrine\ODM\MongoDB; -use Composer\InstalledVersions; use Doctrine\Common\EventManager; use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; @@ -29,6 +28,7 @@ use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; +use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\ReadPreference; use MongoDB\GridFS\Bucket; use ProxyManager\Proxy\GhostObjectInterface; @@ -64,6 +64,8 @@ class DocumentManager implements ObjectManager */ private Client $client; + private ClientEncryption $clientEncryption; + /** * The used Configuration. */ @@ -138,8 +140,6 @@ class DocumentManager implements ObjectManager /** @var ProxyClassNameResolver&ClassNameResolver */ private ProxyClassNameResolver $classNameResolver; - private static ?string $version = null; - /** * Creates a new Document that operates on the given Mongo connection * and uses the given Configuration. @@ -151,12 +151,7 @@ protected function __construct(?Client $client = null, ?Configuration $config = $this->client = $client ?: new Client( 'mongodb://127.0.0.1', [], - [ - 'driver' => [ - 'name' => 'doctrine-odm', - 'version' => self::getVersion(), - ], - ], + $this->config->getDriverOptions(), ); $this->classNameResolver = $this->config->isLazyGhostObjectEnabled() @@ -225,6 +220,22 @@ public function getClient(): Client return $this->client; } + /** @internal */ + public function getClientEncryption(): ClientEncryption + { + if (isset($this->clientEncryption)) { + return $this->clientEncryption; + } + + $options = $this->config->getClientEncryptionOptions(); + + if (! $options) { + throw new RuntimeException('Auto-encryption is not enabled.'); + } + + return $this->clientEncryption = $this->client->createClientEncryption($options); + } + /** Gets the metadata factory used to gather the metadata of classes. */ public function getMetadataFactory(): ClassmetadataFactoryInterface { @@ -923,13 +934,4 @@ public function getClassNameForAssociation(array $mapping, $data): string return $mapping['targetDocument']; } - - private static function getVersion(): string - { - try { - return self::$version ??= InstalledVersions::getPrettyVersion('doctrine/mongodb-odm') ?? 'unknown'; - } catch (Throwable) { - return 'error'; - } - } } diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Encrypt.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Encrypt.php new file mode 100644 index 0000000000..1f7fbc90e4 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Encrypt.php @@ -0,0 +1,47 @@ +|null $sparsity + * @param positive-int|null $precision + * @param positive-int|null $trimFactor + * @param positive-int|null $contention + */ + public function __construct( + public ?EncryptQuery $queryType = null, + int|float|Int64|Decimal128|UTCDateTime|DateTimeInterface|null $min = null, + int|float|Int64|Decimal128|UTCDateTime|DateTimeInterface|null $max = null, + public ?int $sparsity = null, + public ?int $precision = null, + public ?int $trimFactor = null, + public ?int $contention = null, + ) { + $this->min = $min instanceof DateTimeInterface ? new UTCDateTime($min) : $min; + $this->max = $max instanceof DateTimeInterface ? new UTCDateTime($max) : $max; + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/EncryptQuery.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/EncryptQuery.php new file mode 100644 index 0000000000..71b5ea6d04 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/EncryptQuery.php @@ -0,0 +1,13 @@ +, + * precision?: positive-int, + * trimFactor?: positive-int, + * contention?: positive-int, + * } * @phpstan-type FieldMappingConfig array{ * type?: string, * fieldName?: string, @@ -107,6 +122,7 @@ * order?: int|string, * background?: bool, * enumType?: class-string, + * encrypt?: EncryptConfig, * } * @phpstan-type FieldMapping array{ * type: string, @@ -153,6 +169,7 @@ * alsoLoadFields?: list, * enumType?: class-string, * storeEmptyArray?: bool, + * encrypt?: EncryptConfig, * } * @phpstan-type AssociationFieldMapping array{ * type?: string, @@ -801,6 +818,11 @@ */ public $isReadOnly; + /** + * READ-ONLY: A flag for whether or not this document has encrypted fields. + */ + public bool $isEncrypted = false; + /** READ ONLY: stores metadata about the time series collection */ public ?TimeSeries $timeSeriesOptions = null; @@ -2176,6 +2198,15 @@ public function isView(): bool return $this->isView; } + public function isDocument(): bool + { + return ! $this->isView + && ! $this->isEmbeddedDocument + && ! $this->isFile + && ! $this->isQueryResultDocument + && ! $this->isMappedSuperclass; + } + /** @param class-string $rootClass */ public function markViewOf(string $rootClass): void { @@ -2355,6 +2386,16 @@ public function mapField(array $mapping): array $mapping['nullable'] = false; } + if (isset($mapping['encrypt']['queryType'])) { + // The encrypted range query options min and max must be converted to the database type + $type = Type::getType($mapping['type']); + foreach (['min', 'max'] as $option) { + if (isset($mapping['encrypt'][$option])) { + $mapping['encrypt'][$option] = $type->convertToDatabaseValue($mapping['encrypt'][$option]); + } + } + } + if ( isset($mapping['reference']) && isset($mapping['storeAs']) diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php index d23e601d9b..d7c480d821 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php @@ -6,6 +6,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\Reader; +use Doctrine\Persistence\Mapping\Driver\ClassLocator; /** * The AnnotationDriver reads the mapping metadata from docblock annotations. @@ -25,20 +26,21 @@ class AnnotationDriver extends AttributeDriver * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading * docblock annotations. * - * @param Reader $reader The AnnotationReader to use, duck-typed. - * @param string|string[]|null $paths One or multiple paths where mapping classes can be found. + * @param Reader $reader The AnnotationReader to use, duck-typed. + * @param string|string[]|ClassLocator|null $paths One or multiple paths where mapping classes can be found. */ public function __construct($reader, $paths = null) { - $this->reader = $reader; + parent::__construct($paths); - $this->addPaths((array) $paths); + // Setting the reader in the parent constructor is deprecated. + $this->reader = $reader; } /** * Factory method for the Annotation Driver * - * @param string[]|string $paths + * @param string|string[]|ClassLocator $paths */ public static function create($paths = [], ?Reader $reader = null): AnnotationDriver { diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php index 6a0ed2ee24..99415f205f 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php @@ -14,6 +14,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\MappingException; use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; +use Doctrine\Persistence\Mapping\Driver\ClassLocator; use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use MongoDB\BSON\Document; @@ -32,7 +33,7 @@ use function trigger_deprecation; /** - * The AtttributeDriver reads the mapping metadata from attributes. + * The AttributeDriver reads the mapping metadata from attributes. */ class AttributeDriver implements MappingDriver { @@ -45,7 +46,7 @@ class AttributeDriver implements MappingDriver */ protected $reader; - /** @param string|string[]|null $paths */ + /** @param string|string[]|ClassLocator|null $paths */ public function __construct($paths = null, ?Reader $reader = null) { if ($reader !== null) { @@ -59,7 +60,11 @@ public function __construct($paths = null, ?Reader $reader = null) $this->reader = $reader ?? new AttributeReader(); - $this->addPaths((array) $paths); + if ($paths instanceof ClassLocator) { + $this->classLocator = $paths; + } else { + $this->addPaths((array) $paths); + } } public function isTransient($className): bool @@ -149,6 +154,8 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad if (isset($attribute->level)) { $metadata->setValidationLevel($attribute->level); } + } elseif ($attribute instanceof ODM\Encrypt) { + $metadata->isEncrypted = true; } } @@ -264,6 +271,8 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad $mapping['version'] = true; } elseif ($propertyAttribute instanceof ODM\Lock) { $mapping['lock'] = true; + } elseif ($propertyAttribute instanceof ODM\Encrypt) { + $mapping['encrypt'] = (array) $propertyAttribute; } } @@ -416,7 +425,7 @@ public function getReader() /** * Factory method for the Attribute Driver * - * @param string[]|string $paths + * @param string|string[]|ClassLocator $paths * * @return AttributeDriver */ diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php index 2b4454b0da..22c02483bb 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php @@ -4,6 +4,7 @@ namespace Doctrine\ODM\MongoDB\Mapping\Driver; +use Doctrine\ODM\MongoDB\Mapping\Annotations\EncryptQuery; use Doctrine\ODM\MongoDB\Mapping\Annotations\TimeSeries; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\MappingException; @@ -45,6 +46,7 @@ * XmlDriver is a metadata driver that enables mapping through XML files. * * @phpstan-import-type FieldMappingConfig from ClassMetadata + * @phpstan-import-type EncryptConfig from ClassMetadata * @template-extends FileDriver */ class XmlDriver extends FileDriver @@ -98,6 +100,9 @@ public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\C $metadata->isMappedSuperclass = true; } elseif ($xmlRoot->getName() === 'embedded-document') { $metadata->isEmbeddedDocument = true; + if (isset($xmlRoot->encrypt)) { + $metadata->isEncrypted = true; + } } elseif ($xmlRoot->getName() === 'query-result-document') { $metadata->isQueryResultDocument = true; } elseif ($xmlRoot->getName() === 'view') { @@ -307,6 +312,10 @@ public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\C $mapping['lock'] = ((string) $attributes['lock'] === 'true'); } + if (isset($field->encrypt)) { + $mapping['encrypt'] = $this->addEncryptionMapping($field->encrypt, $mapping['type']); + } + $this->addFieldMapping($metadata, $mapping); } } @@ -449,6 +458,10 @@ private function addEmbedMapping(ClassMetadata $class, SimpleXMLElement $embed, $mapping['defaultDiscriminatorValue'] = (string) $embed->{'default-discriminator-value'}['value']; } + if (isset($embed->encrypt)) { + $mapping['encrypt'] = $this->addEncryptionMapping($embed->encrypt, $mapping['type']); + } + if (isset($attributes['not-saved'])) { $mapping['notSaved'] = ((string) $attributes['not-saved'] === 'true'); } @@ -919,6 +932,21 @@ private function addGridFSMappings(ClassMetadata $class, SimpleXMLElement $xmlRo $xmlRoot->metadata->addAttribute('field', 'metadata'); $this->addEmbedMapping($class, $xmlRoot->metadata, ClassMetadata::ONE); } + + /** @psalm-return EncryptConfig */ + private function addEncryptionMapping(SimpleXMLElement $encrypt, string $type): array + { + $encryptMapping = []; + foreach ($encrypt->attributes() as $key => $value) { + $encryptMapping[$key] = match ($key) { + 'queryType' => EncryptQuery::from((string) $value), + 'min', 'max' => (string) $value, + 'sparsity', 'precision', 'trimFactor', 'contention' => (int) $value, + }; + } + + return $encryptMapping; + } } interface_exists(ClassMetadata::class); diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php index e51e21c0af..7b7d46d1ba 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php @@ -306,4 +306,12 @@ public static function timeSeriesFieldNotFound(string $className, string $fieldN $fieldName, )); } + + public static function rootDocumentCannotBeEncrypted(string $className): self + { + return new self(sprintf( + 'The root document class "%s" cannot be encrypted. Only fields and embedded documents can be encrypted.', + $className, + )); + } } diff --git a/lib/Doctrine/ODM/MongoDB/MongoDBException.php b/lib/Doctrine/ODM/MongoDB/MongoDBException.php index a9d14a140e..0735611a6e 100644 --- a/lib/Doctrine/ODM/MongoDB/MongoDBException.php +++ b/lib/Doctrine/ODM/MongoDB/MongoDBException.php @@ -160,4 +160,9 @@ public static function transactionalSessionMismatch(): self { return new self('The transactional operation cannot be executed because it was started in a different session.'); } + + public static function notADocumentClass(string $className): self + { + return new self(sprintf('The class "%s" is not a document class.', $className)); + } } diff --git a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php index d4847d8810..44cef23070 100644 --- a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php +++ b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php @@ -29,12 +29,11 @@ use Doctrine\ODM\MongoDB\Utility\CollectionHelper; use Doctrine\Persistence\Mapping\MappingException; use InvalidArgumentException; -use Iterator as SplIterator; use MongoDB\BSON\ObjectId; use MongoDB\Collection; use MongoDB\Driver\CursorInterface; +use MongoDB\Driver\Exception\BulkWriteException; use MongoDB\Driver\Exception\Exception as DriverException; -use MongoDB\Driver\Exception\WriteException; use MongoDB\Driver\Session; use MongoDB\Driver\WriteConcern; use MongoDB\GridFS\Bucket; @@ -266,7 +265,7 @@ public function executeUpserts(array $options = []): void $this->executeUpsert($document, $options); $this->handleCollections($document, $options); unset($this->queuedUpserts[$oid]); - } catch (WriteException $e) { + } catch (BulkWriteException $e) { unset($this->queuedUpserts[$oid]); throw $e; @@ -333,18 +332,17 @@ private function executeUpsert(object $document, array $options): void $data = ['$set' => ['_id' => $criteria['_id']]]; } + assert($this->collection instanceof Collection); try { - assert($this->collection instanceof Collection); $this->collection->updateOne($criteria, $data, $options); return; - } catch (WriteException $e) { + } catch (BulkWriteException $e) { if (empty($retry) || strpos($e->getMessage(), 'Mod on _id not allowed') === false) { throw $e; } } - assert($this->collection instanceof Collection); $this->collection->updateOne($criteria, ['$set' => new stdClass()], $options); } @@ -544,8 +542,6 @@ public function loadAll(array $criteria = [], ?array $sort = null, ?int $limit = assert($this->collection instanceof Collection); $baseCursor = $this->collection->find($criteria, $options); - assert($baseCursor instanceof CursorInterface && $baseCursor instanceof SplIterator); - return $this->wrapCursor($baseCursor); } @@ -592,7 +588,7 @@ private function getShardKeyQuery(object $document): array /** * Wraps the supplied base cursor in the corresponding ODM class. */ - private function wrapCursor(SplIterator&CursorInterface $baseCursor): Iterator + private function wrapCursor(CursorInterface $baseCursor): Iterator { return new CachingIterator(new HydratingIterator($baseCursor, $this->dm->getUnitOfWork(), $this->class)); } diff --git a/lib/Doctrine/ODM/MongoDB/SchemaManager.php b/lib/Doctrine/ODM/MongoDB/SchemaManager.php index c16c37815c..d7dea185db 100644 --- a/lib/Doctrine/ODM/MongoDB/SchemaManager.php +++ b/lib/Doctrine/ODM/MongoDB/SchemaManager.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface; use Doctrine\ODM\MongoDB\Repository\ViewRepository; +use Doctrine\ODM\MongoDB\Utility\EncryptedFieldsMapGenerator; use InvalidArgumentException; use MongoDB\Driver\Exception\CommandException; use MongoDB\Driver\Exception\RuntimeException; @@ -47,6 +48,7 @@ final class SchemaManager private const CODE_SHARDING_ALREADY_INITIALIZED = 23; private const CODE_COMMAND_NOT_SUPPORTED = 115; + private const CODE_SEARCH_NOT_ENABLED = 31082; private const ALLOWED_MISSING_INDEX_OPTIONS = [ 'background', @@ -643,10 +645,29 @@ public function createDocumentCollection(string $documentName, ?int $maxTimeMs = } } - $this->dm->getDocumentDatabase($documentName)->createCollection( - $class->getCollection(), - $this->getWriteOptions($maxTimeMs, $writeConcern, $options), - ); + // Encryption is enabled only if the KMS provider is set and at least one field is encrypted + if ($this->dm->getConfiguration()->getDefaultKmsProvider()) { + $encryptedFields = (new EncryptedFieldsMapGenerator($this->dm->getMetadataFactory()))->getEncryptedFieldsForClass($class->name); + + if ($encryptedFields) { + $options['encryptedFields'] = $encryptedFields; + } + } + + if (isset($options['encryptedFields'])) { + $this->dm->getDocumentDatabase($documentName)->createEncryptedCollection( + $class->getCollection(), + $this->dm->getClientEncryption(), + $this->dm->getConfiguration()->getDefaultKmsProvider(), + $this->dm->getConfiguration()->getDefaultMasterKey(), + $this->getWriteOptions($maxTimeMs, $writeConcern, $options), + ); + } else { + $this->dm->getDocumentDatabase($documentName)->createCollection( + $class->getCollection(), + $this->getWriteOptions($maxTimeMs, $writeConcern, $options), + ); + } } /** @@ -1079,6 +1100,11 @@ private function getWriteOptions(?int $maxTimeMs = null, ?WriteConcern $writeCon private function isSearchIndexCommandException(CommandException $e): bool { + // MongoDB 8.0+: "Using Atlas Search Database Commands and the $listSearchIndexes aggregation stage requires additional configuration." + if ($e->getCode() === self::CODE_SEARCH_NOT_ENABLED) { + return true; + } + // MongoDB 6.0.7+ and 7.0+: "Search indexes are only available on Atlas" if ($e->getCode() === self::CODE_COMMAND_NOT_SUPPORTED && str_contains($e->getMessage(), 'Search index')) { return true; @@ -1090,7 +1116,7 @@ private function isSearchIndexCommandException(CommandException $e): bool } // Older server versions don't support $listSearchIndexes - // We don't check for an error code here as the code is not documented and we can't rely on it + // We don't check for an error code here as the code is not documented, and we can't rely on it return str_contains($e->getMessage(), 'Unrecognized pipeline stage name: \'$listSearchIndexes\''); } } diff --git a/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/CreateCommand.php b/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/CreateCommand.php index 4a237a1a1b..f1c4162343 100644 --- a/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/CreateCommand.php +++ b/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/CreateCommand.php @@ -72,8 +72,10 @@ private function doExecute(InputInterface $input, OutputInterface $output): int try { if (isset($class)) { + // @phpstan-ignore arguments.count, arguments.count $this->{'processDocument' . $method}($sm, $class, $this->getMaxTimeMsFromInput($input), $this->getWriteConcernFromInput($input), $background); } else { + // @phpstan-ignore arguments.count, arguments.count $this->{'process' . $method}($sm, $this->getMaxTimeMsFromInput($input), $this->getWriteConcernFromInput($input), $background); } diff --git a/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/DropCommand.php b/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/DropCommand.php index 4fdc74a895..1ae039fa83 100644 --- a/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/DropCommand.php +++ b/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/DropCommand.php @@ -72,8 +72,10 @@ private function doExecute(InputInterface $input, OutputInterface $output): int try { if (is_string($class)) { + // @phpstan-ignore arguments.count $this->{'processDocument' . $method}($sm, $class, $this->getMaxTimeMsFromInput($input), $this->getWriteConcernFromInput($input)); } else { + // @phpstan-ignore arguments.count $this->{'process' . $method}($sm, $this->getMaxTimeMsFromInput($input), $this->getWriteConcernFromInput($input)); } diff --git a/lib/Doctrine/ODM/MongoDB/Tools/Console/MetadataFilter.php b/lib/Doctrine/ODM/MongoDB/Tools/Console/MetadataFilter.php index 09e6e0d431..a77f4d819b 100644 --- a/lib/Doctrine/ODM/MongoDB/Tools/Console/MetadataFilter.php +++ b/lib/Doctrine/ODM/MongoDB/Tools/Console/MetadataFilter.php @@ -16,6 +16,8 @@ /** * Used by CLI Tools to restrict entity-based commands to given patterns. + * + * @extends FilterIterator, ArrayIterator> */ class MetadataFilter extends FilterIterator implements Countable { diff --git a/lib/Doctrine/ODM/MongoDB/Types/Int64Type.php b/lib/Doctrine/ODM/MongoDB/Types/Int64Type.php new file mode 100644 index 0000000000..c0ee027855 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Types/Int64Type.php @@ -0,0 +1,27 @@ + Types\BooleanType::class, self::INT => Types\IntType::class, self::INTEGER => Types\IntType::class, + self::INT64 => Types\Int64Type::class, self::FLOAT => Types\FloatType::class, self::STRING => Types\StringType::class, self::DATE => Types\DateType::class, diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index 82d4195aa6..2ae5830b86 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -255,6 +255,7 @@ final class UnitOfWork implements PropertyChangedListener * The document persister instances used to persist document instances. * * @var array + * @phpstan-ignore missingType.generics */ private array $persisters = []; @@ -1749,7 +1750,7 @@ public function persist(object $document): void * NOTE: This method always considers documents that are not yet known to * this UnitOfWork as NEW. * - * @param array $visited + * @param array $visited * * @throws InvalidArgumentException * @throws MongoDBException @@ -1952,7 +1953,7 @@ private function doMerge(object $document, array &$visited, ?object $prevManaged $prop = $this->reflectionService->getAccessibleProperty($class->name, $name); assert($prop instanceof ReflectionProperty); - if (method_exists($prop, 'isInitialized') && ! $prop->isInitialized($document)) { + if (! $prop->isInitialized($document)) { continue; } diff --git a/lib/Doctrine/ODM/MongoDB/Utility/EncryptedFieldsMapGenerator.php b/lib/Doctrine/ODM/MongoDB/Utility/EncryptedFieldsMapGenerator.php new file mode 100644 index 0000000000..19e3c48da7 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Utility/EncryptedFieldsMapGenerator.php @@ -0,0 +1,148 @@ +}> + */ + public function getEncryptedFieldsMap(): array + { + $encryptedFieldsMap = []; + + $allMetadata = $this->classMetadataFactory->getAllMetadata(); + foreach ($allMetadata as $classMetadata) { + if (! $classMetadata->isDocument()) { + continue; + } + + $classMap = iterator_to_array($this->createEncryptedFieldsForClass($classMetadata)); + if ($classMap === []) { + continue; + } + + $encryptedFieldsMap[$classMetadata->getName()] = ['fields' => $classMap]; + } + + return $encryptedFieldsMap; + } + + /** + * Generate the encryption field map from the class metadata. + * + * @param class-string $className + * + * @return array{fields: array}|null + */ + public function getEncryptedFieldsForClass(string $className): ?array + { + $classMetadata = $this->classMetadataFactory->getMetadataFor($className); + + if (! $classMetadata->isDocument()) { + throw MongoDBException::notADocumentClass($className); + } + + $fields = iterator_to_array($this->createEncryptedFieldsForClass($classMetadata)); + + if ($fields === []) { + return null; + } + + return ['fields' => $fields]; + } + + /** + * @param array $visitedClasses + * @phpstan-param ClassMetadata $classMetadata + * + * @return Generator + * + * @template T of object + */ + private function createEncryptedFieldsForClass( + ClassMetadata $classMetadata, + string $parentPath = '', + array $visitedClasses = [], + ): Generator { + if ($classMetadata->isEncrypted && ! $classMetadata->isEmbeddedDocument) { + throw MappingException::rootDocumentCannotBeEncrypted($classMetadata->getName()); + } + + if (isset($visitedClasses[$classMetadata->getName()])) { + // Prevent infinite recursion due to circular references in the metadata + return; + } + + foreach ($classMetadata->fieldMappings as $mapping) { + // Add fields recursively + if ($mapping['embedded'] ?? false) { + $embedMetadata = $this->classMetadataFactory->getMetadataFor($mapping['targetDocument']); + + // When the embedded document class is encrypted, the field is encrypted, + // but none of the embedded fields are encrypted separately. + if ($embedMetadata->isEncrypted) { + $mapping['encrypt'] ??= []; + } elseif (! isset($mapping['encrypt'])) { + yield from $this->createEncryptedFieldsForClass( + $embedMetadata, + $parentPath . $mapping['name'] . '.', + $visitedClasses + [$classMetadata->getName() => true], + ); + } + } + + if (! isset($mapping['encrypt'])) { + continue; + } + + $field = [ + 'path' => $parentPath . $mapping['name'], + 'bsonType' => match ($mapping['type']) { + ClassMetadata::ONE, Type::HASH => 'object', + ClassMetadata::MANY, Type::COLLECTION => 'array', + Type::INT, Type::INTEGER => 'int', + Type::INT64 => 'long', + Type::FLOAT => 'double', + Type::DECIMAL128 => 'decimal', + Type::DATE, Type::DATE_IMMUTABLE => 'date', + Type::TIMESTAMP => 'timestamp', + Type::OBJECTID => 'objectId', + Type::STRING => 'string', + Type::BINDATA, Type::BINDATABYTEARRAY, Type::BINDATAFUNC, Type::BINDATACUSTOM, Type::BINDATAUUID, Type::BINDATAMD5, Type::BINDATAUUIDRFC4122 => 'binData', + Type::BOOL, Type::BOOLEAN => 'bool', + default => throw new LogicException(sprintf('Type "%s" is not supported in encrypted fields map.', $mapping['type'])), + }, + 'keyId' => null, // Generate the key automatically + ]; + + // When queryType is null, the field is not queryable + if (isset($mapping['encrypt']['queryType'])) { + $field['queries'] = array_filter($mapping['encrypt'], static fn ($v) => $v !== null); + $field['queries']['queryType'] = $field['queries']['queryType']->value; + } + + yield $field; + } + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 27fc740944..62e3a7c837 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,1296 +1,2341 @@ parameters: ignoreErrors: - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$applyFilters$#" + message: '#^Call to an undefined method MongoDB\\Driver\\Monitoring\\CommandFailedEvent\|MongoDB\\Driver\\Monitoring\\CommandSucceededEvent\:\:getServer\(\)\.$#' + identifier: method.notFound + count: 1 + path: lib/Doctrine/ODM/MongoDB/APM/Command.php + + - + message: '#^Call to function assert\(\) with true will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 2 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php + + - + message: '#^Instanceof between MongoDB\\Driver\\CursorInterface and Iterator will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php + + - + message: '#^Instanceof between MongoDB\\Driver\\CursorInterface and MongoDB\\Driver\\CursorInterface will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Aggregation\:\:__construct\(\) has parameter \$classMetadata with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Aggregation\:\:execute\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Aggregation\:\:getIterator\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Aggregation\:\:prepareIterator\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Builder\:\:execute\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics count: 1 path: lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php - - message: "#^Unsafe usage of new static\\(\\)\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Builder\:\:getDocumentPersister\(\) return type with generic class Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php + + - + message: '#^PHPDoc tag @param references unknown parameter\: \$applyFilters$#' + identifier: parameter.notFound + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Expr\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Expr\:\:getDocumentPersister\(\) return type with generic class Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister does not specify its types\: T$#' + identifier: missingType.generics count: 1 path: lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Bucket\\\\AbstractOutput\\:\\:avg\\(\\) has parameter \\$expressions with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\:\:execute\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\AbstractBucket\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractBucket.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\AbstractBucket\:\:getDocumentPersister\(\) return type with generic class Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractBucket.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\AbstractReplace\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractReplace.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\AbstractReplace\:\:getDocumentPersister\(\) return type with generic class Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AbstractReplace.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Bucket\\AbstractOutput\:\:avg\(\) has parameter \$expressions with no type specified\.$#' + identifier: missingType.parameter count: 1 path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Bucket\\\\AbstractOutput\\:\\:max\\(\\) has parameter \\$expressions with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Bucket\\AbstractOutput\:\:max\(\) has parameter \$expressions with no type specified\.$#' + identifier: missingType.parameter count: 1 path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Bucket\\\\AbstractOutput\\:\\:mergeObjects\\(\\) has parameter \\$expressions with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Bucket\\AbstractOutput\:\:mergeObjects\(\) has parameter \$expressions with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Bucket\\AbstractOutput\:\:min\(\) has parameter \$expressions with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Bucket\\AbstractOutput\:\:stdDevPop\(\) has parameter \$expressions with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Bucket\\AbstractOutput\:\:stdDevSamp\(\) has parameter \$expressions with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Bucket\\AbstractOutput\:\:sum\(\) has parameter \$expressions with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + + - + message: '#^Class Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Fill has type alias FillStageExpression with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\GraphLookup\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\GraphLookup\:\:getDocumentPersister\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\GraphLookup\:\:getDocumentPersister\(\) return type with generic class Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\GraphLookup\:\:\$from \(string\|null\) is never assigned null so it can be removed from the property type\.$#' + identifier: property.unusedType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\GraphLookup\:\:\$startWith \(array\\|Doctrine\\ODM\\MongoDB\\Aggregation\\Expr\|string\|null\) is never assigned null so it can be removed from the property type\.$#' + identifier: property.unusedType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php + + - + message: '#^Unable to resolve the template type T in call to method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getClassMetadata\(\)$#' + identifier: argument.templateType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Lookup\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Lookup\:\:getDocumentPersister\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Lookup\:\:getDocumentPersister\(\) return type with generic class Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Lookup\:\:prepareFieldName\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: '#^Unable to resolve the template type T in call to method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getClassMetadata\(\)$#' + identifier: argument.templateType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Merge\:\:\$whenMatched \(''fail''\|''keepExisting''\|''merge''\|''replace''\|Doctrine\\ODM\\MongoDB\\Aggregation\\Builder\|Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\|list\\>\|null\) is never assigned Doctrine\\ODM\\MongoDB\\Aggregation\\Stage so it can be removed from the property type\.$#' + identifier: property.unusedType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php + + - + message: '#^Unable to resolve the template type T in call to method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getClassMetadata\(\)$#' + identifier: argument.templateType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Out\:\:fromDocument\(\) has parameter \$classMetadata with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php + + - + message: '#^Unable to resolve the template type T in call to method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getClassMetadata\(\)$#' + identifier: argument.templateType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedAutocomplete\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedAutocomplete\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedAutocomplete\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedEmbeddedDocument\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedEmbeddedDocument\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedEmbeddedDocument\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedEquals\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedEquals\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedEquals\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedExists\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedExists\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedExists\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedGeoShape\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedGeoShape\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedGeoShape\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedGeoWithin\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedGeoWithin\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedGeoWithin\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedMoreLikeThis\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedMoreLikeThis\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedMoreLikeThis\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedNear\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedNear\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedNear\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedPhrase\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedPhrase\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedPhrase\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedQueryString\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedQueryString\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedQueryString\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedRange\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedRange\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedRange\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedRegex\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedRegex\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedRegex\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedText\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedText\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedText\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedWildcard\:\:__construct\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedWildcard\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Compound\\CompoundedWildcard\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\EmbeddedDocument\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\EmbeddedDocument\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoShape\:\:__construct\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoShape\:\:geometry\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoShape\:\:\$geometry type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoWithin\:\:box\(\) has parameter \$bottomLeft with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoWithin\:\:box\(\) has parameter \$topRight with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoWithin\:\:circle\(\) has parameter \$center with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoWithin\:\:convertGeometry\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoWithin\:\:convertGeometry\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoWithin\:\:geometry\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoWithin\:\:\$geometry type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\GeoWithin\:\:\$relation is never read, only written\.$#' + identifier: property.onlyWritten + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Near\:\:__construct\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Near\:\:origin\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Near\:\:\$origin type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\Range\:\:\$includeUpperBound is never read, only written\.$#' + identifier: property.onlyWritten + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Range.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\SupportsGeoShapeOperator\:\:geoShape\(\) has parameter \$geometry with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsGeoShapeOperator.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Search\\SupportsNearOperator\:\:near\(\) has parameter \$origin with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsNearOperator.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\SortByCount\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php + + - + message: '#^Class Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\UnionWith has type alias PipelineParamType with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\UnionWith\:\:pipeline\(\) has parameter \$pipeline with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\UnionWith\:\:\$pipeline \(array\|Doctrine\\ODM\\MongoDB\\Aggregation\\Builder\|Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\|null\) is never assigned Doctrine\\ODM\\MongoDB\\Aggregation\\Stage so it can be removed from the property type\.$#' + identifier: property.unusedType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\UnionWith\:\:\$pipeline type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: '#^Unable to resolve the template type T in call to method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getClassMetadata\(\)$#' + identifier: argument.templateType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\UnsetStage\:\:__construct\(\) has parameter \$documentPersister with generic class Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php + + - + message: '#^Return type \(Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\) of method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getMetadataFactory\(\) should be compatible with return type \(Doctrine\\Persistence\\Mapping\\ClassMetadataFactory\\>\) of method Doctrine\\Persistence\\ObjectManager\:\:getMetadataFactory\(\)$#' + identifier: method.childReturnType + count: 1 + path: lib/Doctrine/ODM/MongoDB/DocumentManager.php + + - + message: '#^Unsafe usage of new static\(\)\.$#' + identifier: new.static + count: 1 + path: lib/Doctrine/ODM/MongoDB/DocumentManager.php + + - + message: '#^Method Doctrine\\Persistence\\Event\\OnClearEventArgs\\:\:__construct\(\) invoked with 2 parameters, 1 required\.$#' + identifier: arguments.count + count: 1 + path: lib/Doctrine/ODM/MongoDB/Event/OnClearEventArgs.php + + - + message: '#^Parameter \#1 \$initializer of method ProxyManager\\Proxy\\GhostObjectInterface\\:\:setProxyInitializer\(\) expects \(Closure\(ProxyManager\\Proxy\\GhostObjectInterface\\=, string\=, array\\=, Closure\|null\=, array\\=\)\: bool\)\|null, Closure\(ProxyManager\\Proxy\\GhostObjectInterface, string, array, mixed, array\)\: true given\.$#' + identifier: argument.type + count: 1 + path: lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php + + - + message: '#^Property Doctrine\\ODM\\MongoDB\\Hydrator\\HydratorFactory\:\:\$hydratorNamespace \(string\|null\) is never assigned null so it can be removed from the property type\.$#' + identifier: property.unusedType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php + + - + message: '#^Interface Doctrine\\ODM\\MongoDB\\Iterator\\IterableResult extends generic interface IteratorAggregate but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Iterator/IterableResult.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Iterator\\IterableResult\:\:getIterator\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Iterator/IterableResult.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\Annotations\\SearchIndex\:\:__construct\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/Annotations/SearchIndex.php + + - + message: '#^Call to function assert\(\) with true will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + + - + message: '#^Class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata has type alias SearchIndexDefinition with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 2 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + + - + message: '#^Class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata has type alias SearchIndexMapping with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 2 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + + - + message: '#^Instanceof between Doctrine\\Persistence\\Reflection\\RuntimeReflectionProperty and ReflectionProperty will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:addInheritedAssociationMapping\(\) has Doctrine\\ODM\\MongoDB\\Mapping\\MappingException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:addSearchIndex\(\) has parameter \$definition with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 2 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:getBucketName\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:getSearchIndexes\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 2 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + + - + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:mapField\(\) should return array\{type\: string, fieldName\: string, name\: string, isCascadeRemove\: bool, isCascadePersist\: bool, isCascadeRefresh\: bool, isCascadeMerge\: bool, isCascadeDetach\: bool, \.\.\.\} but returns array\{type\?\: string, fieldName\?\: string, name\?\: string, strategy\?\: string, association\?\: int, id\?\: bool, isOwningSide\?\: bool, collectionClass\?\: class\-string, \.\.\.\}\.$#' + identifier: return.type count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Bucket\\\\AbstractOutput\\:\\:min\\(\\) has parameter \\$expressions with no type specified\\.$#" + message: '#^Parameter \#1 \$mapping of method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\\:\:checkDuplicateMapping\(\) expects array\{type\: string, fieldName\: string, name\: string, isCascadeRemove\: bool, isCascadePersist\: bool, isCascadeRefresh\: bool, isCascadeMerge\: bool, isCascadeDetach\: bool, \.\.\.\}, array\{type\?\: string, fieldName\?\: string, name\?\: string, strategy\?\: string, association\?\: int, id\?\: bool, isOwningSide\?\: bool, collectionClass\?\: class\-string, \.\.\.\} given\.$#' + identifier: argument.type count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Bucket\\\\AbstractOutput\\:\\:stdDevPop\\(\\) has parameter \\$expressions with no type specified\\.$#" + message: '#^Parameter \#1 \$mapping of method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\\:\:setVersionMapping\(\) expects array\{type\: string, fieldName\: string, name\: string, isCascadeRemove\: bool, isCascadePersist\: bool, isCascadeRefresh\: bool, isCascadeMerge\: bool, isCascadeDetach\: bool, \.\.\.\}, array\{type\: string, fieldName\: string, name\: string, strategy\?\: string, association\?\: int, id\?\: bool, isOwningSide\?\: bool, collectionClass\?\: class\-string, \.\.\.\} given\.$#' + identifier: argument.type count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Bucket\\\\AbstractOutput\\:\\:stdDevSamp\\(\\) has parameter \\$expressions with no type specified\\.$#" + message: '#^Parameter \#1 \$mapping of method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\\:\:typeRequirementsAreMet\(\) expects array\{type\: string, fieldName\: string, name\: string, isCascadeRemove\: bool, isCascadePersist\: bool, isCascadeRefresh\: bool, isCascadeMerge\: bool, isCascadeDetach\: bool, \.\.\.\}, array\{type\?\: string, fieldName\?\: string, name\?\: string, strategy\?\: string, association\?\: int, id\?\: bool, isOwningSide\?\: bool, collectionClass\?\: class\-string, \.\.\.\} given\.$#' + identifier: argument.type count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Bucket\\\\AbstractOutput\\:\\:sum\\(\\) has parameter \\$expressions with no type specified\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:\$rootClass \(class\-string\|null\) is never assigned null so it can be removed from the property type\.$#' + identifier: property.unusedType count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket/AbstractOutput.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php + message: '#^Property Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:\$searchIndexes type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 2 + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + message: '#^Template type T is declared as covariant, but occurs in invariant position in property Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:\$name\.$#' + identifier: generics.variance count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + message: '#^Template type T is declared as covariant, but occurs in invariant position in property Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:\$reflClass\.$#' + identifier: generics.variance count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + message: '#^Template type T is declared as covariant, but occurs in invariant position in return type of method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:getReflectionClass\(\)\.$#' + identifier: generics.variance count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:addInheritedFields\(\) has parameter \$parentClass with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:addInheritedFields\(\) has parameter \$subClass with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:addInheritedIndexes\(\) has parameter \$parentClass with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:addInheritedIndexes\(\) has parameter \$subClass with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedAutocomplete\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:addInheritedRelations\(\) has parameter \$parentClass with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedAutocomplete\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:addInheritedRelations\(\) has parameter \$subClass with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedAutocomplete\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:completeIdGeneratorMapping\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedAutocomplete.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEmbeddedDocument\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:doLoadMetadata\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEmbeddedDocument\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:doLoadMetadata\(\) has parameter \$parent with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEmbeddedDocument\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:initializeReflection\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEmbeddedDocument.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEquals\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:isEntity\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEquals\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:setInheritedShardKey\(\) has parameter \$parentClass with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedEquals\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:setInheritedShardKey\(\) has parameter \$subClass with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedEquals.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedExists\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:validateIdentifier\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedExists\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactory\:\:wakeupReflection\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedExists\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^PHPDoc tag @method for method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:getAllMetadata\(\) return type contains generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedExists.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactoryInterface.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoShape\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^PHPDoc tag @method for method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:getLoadedMetadata\(\) return type contains generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactoryInterface.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoShape\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^PHPDoc tag @method for method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:getMetadataFor\(\) return type contains generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactoryInterface.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoShape\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\Driver\\AttributeDriver\:\:getClassAttributes\(\) has parameter \$class with generic class ReflectionClass but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoShape.php + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoWithin\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\Driver\\AttributeReader\:\:getClassAttributes\(\) has parameter \$class with generic class ReflectionClass but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeReader.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoWithin\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Match expression does not handle remaining value\: string$#' + identifier: match.unhandled count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedGeoWithin\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Mapping\\Driver\\XmlDriver\:\:getSearchIndexFieldDefinition\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedGeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedMoreLikeThis\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Parameter \#2 \$mapping of method Doctrine\\ODM\\MongoDB\\Mapping\\Driver\\XmlDriver\:\:addFieldMapping\(\) expects array\{type\?\: string, fieldName\?\: string, name\?\: string, strategy\?\: string, association\?\: int, id\?\: bool, isOwningSide\?\: bool, collectionClass\?\: class\-string, \.\.\.\}, array\, Doctrine\\ODM\\MongoDB\\Mapping\\Annotations\\EncryptQuery\|float\|int\|MongoDB\\BSON\\Decimal128\|MongoDB\\BSON\\Int64\|MongoDB\\BSON\\UTCDateTime\|string\|null\>\|bool\|string\> given\.$#' + identifier: argument.type count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedMoreLikeThis\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Parameter \#2 \$mapping of method Doctrine\\ODM\\MongoDB\\Mapping\\Driver\\XmlDriver\:\:addFieldMapping\(\) expects array\{type\?\: string, fieldName\?\: string, name\?\: string, strategy\?\: string, association\?\: int, id\?\: bool, isOwningSide\?\: bool, collectionClass\?\: class\-string, \.\.\.\}, non\-empty\-array\\|string\|true\> given\.$#' + identifier: argument.type count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedMoreLikeThis\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Parameter \#2 \$options of method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\\:\:addIndex\(\) expects array\{background\?\: bool, bits\?\: int, default_language\?\: string, expireAfterSeconds\?\: int, language_override\?\: string, min\?\: float, max\?\: float, name\?\: string, \.\.\.\}, array\\|string, mixed\>\|bool\|float\|int\|string\|null\>\|bool\|float\|int\|string\|null\> given\.$#' + identifier: argument.type count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedMoreLikeThis.php + path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedNear\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Call to function is_object\(\) with Doctrine\\Common\\Collections\\Collection will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedNear\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Call to function method_exists\(\) with Doctrine\\Common\\Collections\\Collection and ''findFirst'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedNear\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Call to function method_exists\(\) with Doctrine\\Common\\Collections\\Collection and ''reduce'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedNear.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedPhrase\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\PersistentCollection\:\:add\(\) with return type void returns true but should not return anything\.$#' + identifier: return.void count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedPhrase\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^PHPDoc tag @var for property Doctrine\\ODM\\MongoDB\\PersistentCollection\:\:\$hints with type Doctrine\\ODM\\MongoDB\\PersistentCollection\\Hints is incompatible with native type array\.$#' + identifier: property.phpDocType count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedPhrase\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^PHPDoc tag @var for property Doctrine\\ODM\\MongoDB\\PersistentCollection\:\:\$mapping with type Doctrine\\ODM\\MongoDB\\PersistentCollection\\FieldMapping\|null is not subtype of native type array\|null\.$#' + identifier: property.phpDocType count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedPhrase.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedQueryString\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\PersistentCollection\:\:\$hints has unknown class Doctrine\\ODM\\MongoDB\\PersistentCollection\\Hints as its type\.$#' + identifier: class.notFound count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedQueryString\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\PersistentCollection\:\:\$hints type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedQueryString\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\PersistentCollection\:\:\$mapping has unknown class Doctrine\\ODM\\MongoDB\\PersistentCollection\\FieldMapping as its type\.$#' + identifier: class.notFound count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedQueryString.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRange\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\PersistentCollection\:\:\$mapping type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRange\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Unable to resolve the template type T in call to method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getClassMetadata\(\)$#' + identifier: argument.templateType count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRange\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\PersistentCollection\\AbstractPersistentCollectionFactory\:\:createCollectionClass\(\) return type with generic interface Doctrine\\Common\\Collections\\Collection does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRange.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection/AbstractPersistentCollectionFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRegex\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\PersistentCollection\\DefaultPersistentCollectionFactory\:\:createCollectionClass\(\) return type with generic interface Doctrine\\Common\\Collections\\Collection does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + path: lib/Doctrine/ODM/MongoDB/PersistentCollection/DefaultPersistentCollectionFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRegex\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\CollectionPersister\:\:delete\(\) has parameter \$collections with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedRegex\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\CollectionPersister\:\:deleteElements\(\) has parameter \$collections with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedRegex.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedText\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\CollectionPersister\:\:getPathAndParent\(\) has parameter \$coll with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedText\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\CollectionPersister\:\:getValuePrepareCallback\(\) has parameter \$coll with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedText\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\CollectionPersister\:\:insertElements\(\) has parameter \$collections with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedText.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedWildcard\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\CollectionPersister\:\:setCollections\(\) has parameter \$collections with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedWildcard\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\CollectionPersister\:\:update\(\) has parameter \$collections with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Compound\\\\CompoundedWildcard\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^PHPDoc tag @var for variable \$addToSetColls contains generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Compound/CompoundedWildcard.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\EmbeddedDocument\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^PHPDoc tag @var for variable \$pushAllColls contains generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\EmbeddedDocument\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^PHPDoc tag @var for variable \$setColls contains generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/EmbeddedDocument.php + path: lib/Doctrine/ODM/MongoDB/Persisters/CollectionPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoShape\\:\\:__construct\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + message: '#^Call to function assert\(\) with true will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 2 + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoShape\\:\\:geometry\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Call to function is_array\(\) with array will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoShape\\:\\:\\$geometry type has no value type specified in iterable type array\\.$#" + message: '#^Instanceof between MongoDB\\Collection and MongoDB\\Collection will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoShape.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:box\\(\\) has parameter \\$bottomLeft with no value type specified in iterable type array\\.$#" + message: '#^Instanceof between MongoDB\\Driver\\CursorInterface and Iterator will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:box\\(\\) has parameter \\$topRight with no value type specified in iterable type array\\.$#" + message: '#^Instanceof between MongoDB\\Driver\\CursorInterface and MongoDB\\Driver\\CursorInterface will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:circle\\(\\) has parameter \\$center with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:createReferenceManyInverseSideQuery\(\) has parameter \$collection with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:convertGeometry\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:createReferenceManyWithRepositoryMethodCursor\(\) has parameter \$collection with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:convertGeometry\\(\\) return type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:createReferenceManyWithRepositoryMethodCursor\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:geometry\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:getClassDiscriminatorValues\(\) has parameter \$metadata with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:\\$geometry type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:isInTransaction\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\GeoWithin\\:\\:\\$relation is never read, only written\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:loadAll\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/GeoWithin.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Near\\:\\:__construct\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:loadCollection\(\) has parameter \$collection with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Near\\:\\:origin\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:loadEmbedManyCollection\(\) has parameter \$collection with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Near\\:\\:\\$origin type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:loadReferenceManyCollectionInverseSide\(\) has parameter \$collection with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Near.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\Range\\:\\:\\$includeUpperBound is never read, only written\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:loadReferenceManyCollectionOwningSide\(\) has parameter \$collection with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Range.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\SupportsGeoShapeOperator\\:\\:geoShape\\(\\) has parameter \\$geometry with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:loadReferenceManyWithRepositoryMethod\(\) has parameter \$collection with generic interface Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface but does not specify its types\: TKey, T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsGeoShapeOperator.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Search\\\\SupportsNearOperator\\:\\:near\\(\\) has parameter \\$origin with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:prepareQueryElement\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/SupportsNearOperator.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWith\\:\\:pipeline\\(\\) has parameter \\$pipeline with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:prepareQueryExpression\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\UnionWith\\:\\:\\$pipeline type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister\:\:wrapCursor\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + message: '#^Parameter \#1 \$array \(non\-empty\-list\) of array_values is already a list, call has no effect\.$#' + identifier: arrayValues.list count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Return type \\(Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadataFactoryInterface\\) of method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getMetadataFactory\\(\\) should be compatible with return type \\(Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\) of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getMetadataFactory\\(\\)$#" + message: '#^Result of && is always true\.$#' + identifier: booleanAnd.alwaysTrue count: 1 - path: lib/Doctrine/ODM/MongoDB/DocumentManager.php + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Unsafe usage of new static\\(\\)\\.$#" + message: '#^Call to an undefined static method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:createLazyGhost\(\)\.$#' + identifier: staticMethod.notFound count: 1 - path: lib/Doctrine/ODM/MongoDB/DocumentManager.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Method Doctrine\\\\Persistence\\\\Event\\\\OnClearEventArgs\\\\:\\:__construct\\(\\) invoked with 2 parameters, 1 required\\.$#" + message: '#^Call to function is_scalar\(\) with int\\|int\<5, max\> will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType count: 1 - path: lib/Doctrine/ODM/MongoDB/Event/OnClearEventArgs.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Parameter \\#1 \\$initializer of method ProxyManager\\\\Proxy\\\\GhostObjectInterface\\\\:\\:setProxyInitializer\\(\\) expects \\(Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\\\=, string\\=, array\\\\=, Closure\\|null\\=, array\\\\=\\)\\: bool\\)\\|null, Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface, string, array, mixed, array\\)\\: true given\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:createLazyInitializer\(\) has Doctrine\\ODM\\MongoDB\\DocumentNotFoundException in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType count: 1 - path: lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Annotations\\\\SearchIndex\\:\\:__construct\\(\\) has parameter \\$analyzers with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:createLazyInitializer\(\) has parameter \$persister with generic class Doctrine\\ODM\\MongoDB\\Persisters\\DocumentPersister but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Mapping/Annotations/SearchIndex.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Annotations\\\\SearchIndex\\:\\:__construct\\(\\) has parameter \\$fields with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:createLazyInitializer\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Proxy\\InternalProxy does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Mapping/Annotations/SearchIndex.php - - - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\:\\:addSearchIndex\\(\\) has parameter \\$definition with no value type specified in iterable type array\\.$#" - count: 2 - path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\:\\:getSearchIndexes\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 2 - path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:generateProxyClass\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Parameter \\#2 \\$enumType of class Doctrine\\\\Persistence\\\\Reflection\\\\EnumReflectionProperty constructor expects class\\-string\\, class\\-string\\ given\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:generateProxyClasses\(\) has parameter \$classes with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\:\\:\\$searchIndexes type has no value type specified in iterable type array\\.$#" - count: 2 - path: lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:generateSerializeImpl\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Driver\\\\XmlDriver\\:\\:getSearchIndexFieldDefinition\\(\\) return type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:generateUseLazyGhostTrait\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Parameter \\#2 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Driver\\\\XmlDriver\\:\\:addFieldMapping\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\\\|bool\\|string\\> given\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:getProxy\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Proxy\\InternalProxy does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Parameter \\#2 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Driver\\\\XmlDriver\\:\\:addFieldMapping\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, non\\-empty\\-array\\\\|string\\|true\\> given\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:loadProxyClass\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Parameter \\#2 \\$options of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:addIndex\\(\\) expects array\\{background\\?\\: bool, bits\\?\\: int, default_language\\?\\: string, expireAfterSeconds\\?\\: int, language_override\\?\\: string, min\\?\\: float, max\\?\\: float, name\\?\\: string, \\.\\.\\.\\}, array\\\\|string, mixed\\>\\|bool\\|float\\|int\\|string\\|null\\>\\|bool\\|float\\|int\\|string\\|null\\> given\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\LazyGhostProxyFactory\:\:skipClass\(\) has parameter \$metadata with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:add\\(\\) with return type void returns true but should not return anything\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Proxy\\Factory\\StaticProxyFactory\:\:createInitializer\(\) should return Closure\(ProxyManager\\Proxy\\GhostObjectInterface\&TDocument\=, string\=, array\\=, Closure\|null\=, array\\=\)\: bool but returns Closure\(ProxyManager\\Proxy\\GhostObjectInterface, string, array, mixed, array\)\: true\.$#' + identifier: return.type count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php - - message: "#^PHPDoc tag @var for property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$hints with type Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints is incompatible with native type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Query\\Expr\:\:convertExpression\(\) has parameter \$classMetadata with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + path: lib/Doctrine/ODM/MongoDB/Query/Expr.php - - message: "#^PHPDoc tag @var for property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$mapping with type Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping\\|null is not subtype of native type array\\|null\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Query\\Expr\:\:convertExpressions\(\) has parameter \$classMetadata with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + path: lib/Doctrine/ODM/MongoDB/Query/Expr.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$hints has unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\Hints as its type\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Query\\Expr\:\:setClassMetadata\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + path: lib/Doctrine/ODM/MongoDB/Query/Expr.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$hints type has no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + message: '#^Unsafe call to private method Doctrine\\ODM\\MongoDB\\Query\\Expr\:\:convertExpression\(\) through static\:\:\.$#' + identifier: staticClassAccess.privateMethod + count: 3 + path: lib/Doctrine/ODM/MongoDB/Query/Expr.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$mapping has unknown class Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\\\FieldMapping as its type\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Query\\Query\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + path: lib/Doctrine/ODM/MongoDB/Query/Query.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\PersistentCollection\\:\\:\\$mapping type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Query\\Query\:\:getIterator\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + path: lib/Doctrine/ODM/MongoDB/Query/Query.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Query\\Query\:\:makeIterator\(\) return type with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + path: lib/Doctrine/ODM/MongoDB/Query/Query.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Persisters\\\\DocumentPersister\\:\\:isInTransaction\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Query\\Query\:\:\$iterator with generic interface Doctrine\\ODM\\MongoDB\\Iterator\\Iterator does not specify its types\: TValue$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php + path: lib/Doctrine/ODM/MongoDB/Query/Query.php - - message: "#^Call to an undefined static method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\Factory\\\\LazyGhostProxyFactory\\:\\:createLazyGhost\\(\\)\\.$#" + message: '#^Strict comparison using \!\=\= between array\\|bool\|int\|MongoDB\\Driver\\ReadPreference\|string and null will always evaluate to true\.$#' + identifier: notIdentical.alwaysTrue count: 1 - path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php + path: lib/Doctrine/ODM/MongoDB/Query/Query.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\Factory\\\\StaticProxyFactory\\:\\:createInitializer\\(\\) should return Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\&TDocument\\=, string\\=, array\\\\=, Closure\\|null\\=, array\\\\=\\)\\: bool but returns Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface, string, array, mixed, array\\)\\: true\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Query\\ReferencePrimer\:\:defaultPrimer\(\) has parameter \$class with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics count: 1 - path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php + path: lib/Doctrine/ODM/MongoDB/Query/ReferencePrimer.php - - message: "#^Unsafe call to private method Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Expr\\:\\:convertExpression\\(\\) through static\\:\\:\\.$#" - count: 3 - path: lib/Doctrine/ODM/MongoDB/Query/Expr.php + message: '#^Parameter &\$groupedIds by\-ref type of method Doctrine\\ODM\\MongoDB\\Query\\ReferencePrimer\:\:addManyReferences\(\) expects array\\>, array\\> given\.$#' + identifier: parameterByRef.type + count: 1 + path: lib/Doctrine/ODM/MongoDB/Query/ReferencePrimer.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + message: '#^Call to an undefined method Symfony\\Component\\Console\\Helper\\HelperInterface\:\:getDocumentManager\(\)\.$#' + identifier: method.notFound count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/ClearCache/MetadataCommand.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + message: '#^Call to an undefined method Symfony\\Component\\Console\\Helper\\HelperInterface\:\:getDocumentManager\(\)\.$#' + identifier: method.notFound count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/GenerateHydratorsCommand.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + message: '#^Call to an undefined method Symfony\\Component\\Console\\Helper\\HelperInterface\:\:getDocumentManager\(\)\.$#' + identifier: method.notFound count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/GeneratePersistentCollectionsCommand.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + message: '#^Call to an undefined method Symfony\\Component\\Console\\Helper\\HelperInterface\:\:getDocumentManager\(\)\.$#' + identifier: method.notFound count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/GenerateProxiesCommand.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + message: '#^Call to an undefined method Symfony\\Component\\Console\\Helper\\HelperInterface\:\:getDocumentManager\(\)\.$#' + identifier: method.notFound count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/QueryCommand.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + message: '#^Call to an undefined method Symfony\\Component\\Console\\Helper\\HelperInterface\:\:getDocumentManager\(\)\.$#' + identifier: method.notFound count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/AbstractCommand.php - - message: "#^Match expression does not handle remaining value\\: string$#" + message: '#^Match expression does not handle remaining value\: string$#' + identifier: match.unhandled count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/CreateCommand.php - - message: "#^Match expression does not handle remaining value\\: string$#" + message: '#^Match expression does not handle remaining value\: string$#' + identifier: match.unhandled count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/DropCommand.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:getDocumentManager\\(\\)\\.$#" + message: '#^Call to an undefined method Symfony\\Component\\Console\\Helper\\HelperInterface\:\:getDocumentManager\(\)\.$#' + identifier: method.notFound count: 1 path: lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/ValidateCommand.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable count: 1 path: lib/Doctrine/ODM/MongoDB/Types/DateImmutableType.php - - message: "#^Unsafe call to private method Doctrine\\\\ODM\\\\MongoDB\\\\Types\\\\DateType\\:\\:craftDateTime\\(\\) through static\\:\\:\\.$#" + message: '#^Unsafe call to private method Doctrine\\ODM\\MongoDB\\Types\\DateType\:\:craftDateTime\(\) through static\:\:\.$#' + identifier: staticClassAccess.privateMethod count: 1 path: lib/Doctrine/ODM/MongoDB/Types/DateType.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\UnitOfWork\\:\\:getTransactionOptions\\(\\) return type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\UnitOfWork\:\:getTransactionOptions\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: lib/Doctrine/ODM/MongoDB/UnitOfWork.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\UnitOfWork\\:\\:stripTransactionOptions\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\UnitOfWork\:\:stripTransactionOptions\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: lib/Doctrine/ODM/MongoDB/UnitOfWork.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\UnitOfWork\\:\\:stripTransactionOptions\\(\\) return type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\UnitOfWork\:\:stripTransactionOptions\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: lib/Doctrine/ODM/MongoDB/UnitOfWork.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\UnitOfWork\\:\\:withTransaction\\(\\) has parameter \\$transactionOptions with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\UnitOfWork\:\:withTransaction\(\) has parameter \$transactionOptions with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: lib/Doctrine/ODM/MongoDB/UnitOfWork.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + message: '#^Unable to resolve the template type T in call to method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getClassMetadata\(\)$#' + identifier: argument.templateType count: 1 path: lib/Doctrine/ODM/MongoDB/UnitOfWork.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" + message: '#^Unable to resolve the template type T in call to method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:getClassMetadata\(\)$#' + identifier: argument.templateType count: 3 path: lib/Doctrine/ODM/MongoDB/Utility/LifecycleEventManager.php - - message: "#^Parameter \\#1 \\$builder of method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Facet\\:\\:pipeline\\(\\) expects Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Builder\\|Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage, stdClass given\\.$#" + message: '#^Parameter \#1 \$builder of method Doctrine\\ODM\\MongoDB\\Aggregation\\Stage\\Facet\:\:pipeline\(\) expects Doctrine\\ODM\\MongoDB\\Aggregation\\Builder\|Doctrine\\ODM\\MongoDB\\Aggregation\\Stage, stdClass given\.$#' + identifier: argument.type count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\ProjectTest\\:\\:testAccumulatorsWithMultipleArguments\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\ProjectTest\:\:testAccumulatorsWithMultipleArguments\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ProjectTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\ProjectTest\\:\\:testAccumulatorsWithMultipleArguments\\(\\) has parameter \\$expected with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\ProjectTest\:\:testAccumulatorsWithMultipleArguments\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/ProjectTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchCompoundOperators\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SearchTest\:\:testSearchCompoundOperators\(\) has parameter \$expectedOperator with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchEmbeddedDocumentOperators\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SearchTest\:\:testSearchEmbeddedDocumentOperators\(\) has parameter \$expectedOperator with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchOperators\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SearchTest\:\:testSearchOperators\(\) has parameter \$expectedOperator with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SearchTest\\:\\:testSearchOperatorsWithSort\\(\\) has parameter \\$expectedOperator with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SearchTest\:\:testSearchOperatorsWithSearchAfter\(\) has parameter \$expectedOperator with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SetWindowFieldsTest\\:\\:testOperators\\(\\) has parameter \\$args with no type specified\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SearchTest\:\:testSearchOperatorsWithSearchBefore\(\) has parameter \$expectedOperator with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetWindowFieldsTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Aggregation\\\\Stage\\\\SetWindowFieldsTest\\:\\:testOperators\\(\\) has parameter \\$expected with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SearchTest\:\:testSearchOperatorsWithSort\(\) has parameter \$expectedOperator with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetWindowFieldsTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php - - message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" - count: 5 - path: tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SetWindowFieldsTest\:\:testOperators\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetWindowFieldsTest.php - - message: "#^Constant DOCTRINE_MONGODB_SERVER not found\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Aggregation\\Stage\\SetWindowFieldsTest\:\:testOperators\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php + path: tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SetWindowFieldsTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\BaseTestCase\\:\\:assertArraySubset\\(\\) has parameter \\$array with no value type specified in iterable type array\\.$#" + message: '#^Constant DOCTRINE_MONGODB_SERVER not found\.$#' + identifier: constant.notFound count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\BaseTestCase\\:\\:assertArraySubset\\(\\) has parameter \\$subset with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\BaseTestCase\:\:assertArraySubset\(\) has parameter \$array with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php - - message: "#^Used constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\BaseTestCase\:\:assertArraySubset\(\) has parameter \$subset with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php - - message: "#^Used constant DOCTRINE_MONGODB_SERVER not found\\.$#" + message: '#^Used constant DOCTRINE_MONGODB_SERVER not found\.$#' + identifier: constant.notFound count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php - - message: "#^Parameter \\#2 \\$referenceMapping of method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:createReference\\(\\) expects array\\{type\\: string, fieldName\\: string, name\\: string, isCascadeRemove\\: bool, isCascadePersist\\: bool, isCascadeRefresh\\: bool, isCascadeMerge\\: bool, isCascadeDetach\\: bool, \\.\\.\\.\\}, array\\{storeAs\\: 'dbRef'\\} given\\.$#" + message: '#^Parameter \#2 \$referenceMapping of method Doctrine\\ODM\\MongoDB\\DocumentManager\:\:createReference\(\) expects array\{type\: string, fieldName\: string, name\: string, isCascadeRemove\: bool, isCascadePersist\: bool, isCascadeRefresh\: bool, isCascadeMerge\: bool, isCascadeDetach\: bool, \.\.\.\}, array\{storeAs\: ''dbRef''\} given\.$#' + identifier: argument.type count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/DocumentManagerTest.php - - message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php - - - - message: "#^Parameter \\#2 \\$projects of class Documents\\\\Developer constructor expects Doctrine\\\\Common\\\\Collections\\\\Collection\\\\|null, Doctrine\\\\Common\\\\Collections\\\\ArrayCollection\\ given\\.$#" + message: '#^Parameter \#2 \$projects of class Documents\\Developer constructor expects Doctrine\\Common\\Collections\\Collection\\|null, Doctrine\\Common\\Collections\\ArrayCollection\ given\.$#' + identifier: argument.type count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php - - message: "#^Used constant DOCTRINE_MONGODB_DATABASE not found\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/DocumentRepositoryTest.php + message: '#^Parameter \#2 \$collections of method Doctrine\\ODM\\MongoDB\\Persisters\\CollectionPersister\:\:delete\(\) expects array\, array\\|Doctrine\\Common\\Collections\\Collection\\> given\.$#' + identifier: argument.type + count: 3 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/CollectionPersisterTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\CustomDatabaseTest\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\CustomDatabaseTest\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DatabasesTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\DefaultDatabaseTest\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\DefaultDatabaseTest\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DatabasesTest.php - - message: "#^PHPDoc type Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$embeddedChildren\\.$#" + message: '#^PHPDoc type Doctrine\\Common\\Collections\\Collection\ of property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentDocumentWithDiscriminator\:\:\$embeddedChildren is not covariant with PHPDoc type array\\|Doctrine\\Common\\Collections\\Collection\ of overridden property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentDocument\:\:\$embeddedChildren\.$#' + identifier: property.phpDocType count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php - - message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$referencedChildren\\.$#" + message: '#^PHPDoc type array\\|Doctrine\\Common\\Collections\\Collection\ of property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentDocumentWithDiscriminator\:\:\$referencedChildren is not covariant with PHPDoc type array\\|Doctrine\\Common\\Collections\\Collection\ of overridden property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentDocument\:\:\$referencedChildren\.$#' + identifier: property.phpDocType count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php - - message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$embeddedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$embeddedChildren\\.$#" + message: '#^PHPDoc type array\\|Doctrine\\Common\\Collections\\Collection\ of property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentDocumentWithoutDiscriminator\:\:\$embeddedChildren is not covariant with PHPDoc type array\\|Doctrine\\Common\\Collections\\Collection\ of overridden property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentDocument\:\:\$embeddedChildren\.$#' + identifier: property.phpDocType count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php - - message: "#^PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocumentWithoutDiscriminator\\:\\:\\$referencedChildren is not covariant with PHPDoc type array\\\\|Doctrine\\\\Common\\\\Collections\\\\Collection\\ of overridden property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentDocument\\:\\:\\$referencedChildren\\.$#" + message: '#^PHPDoc type array\\|Doctrine\\Common\\Collections\\Collection\ of property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentDocumentWithoutDiscriminator\:\:\$referencedChildren is not covariant with PHPDoc type array\\|Doctrine\\Common\\Collections\\Collection\ of overridden property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentDocument\:\:\$referencedChildren\.$#' + identifier: property.phpDocType count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DiscriminatorsDefaultValueTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\DocumentPersisterTestDocumentWithReferenceToDocumentWithCustomId\\:\\:\\$documentWithCustomId is never read, only written\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\DocumentPersisterTestDocumentWithReferenceToDocumentWithCustomId\:\:\$documentWithCustomId is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DocumentPersisterTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\DocumentPersisterTestDocumentWithReferenceToDocumentWithCustomId\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\DocumentPersisterTestDocumentWithReferenceToDocumentWithCustomId\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/DocumentPersisterTest.php - - message: "#^Expression \"\\$groups\\[0\\]\" on a separate line does not do anything\\.$#" + message: '#^Expression "\$groups\[0\]" on a separate line does not do anything\.$#' + identifier: expr.resultUnused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/FunctionalTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ChildObject\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ChildObject\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/LifecycleTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\ParentObject\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\ParentObject\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/LifecycleTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Hierarchy\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Hierarchy\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/NestedDocumentsTest.php - - message: "#^Parameter \\$discriminatorMap of attribute class Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Annotations\\\\ReferenceOne constructor expects array\\\\|null, array\\ given\\.$#" + message: '#^Access to an undefined property MongoDB\\Model\\BSONDocument\:\:\$patientId\.$#' + identifier: property.notFound + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/QueryableEncryptionTest.php + + - + message: '#^Access to an undefined property MongoDB\\Model\\BSONDocument\:\:\$patientName\.$#' + identifier: property.notFound + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/QueryableEncryptionTest.php + + - + message: '#^Access to an undefined property MongoDB\\Model\\BSONDocument\:\:\$patientRecord\.$#' + identifier: property.notFound + count: 6 + path: tests/Doctrine/ODM/MongoDB/Tests/Functional/QueryableEncryptionTest.php + + - + message: '#^Parameter \$discriminatorMap of attribute class Doctrine\\ODM\\MongoDB\\Mapping\\Annotations\\ReferenceOne constructor expects array\\|null, array\ given\.$#' + identifier: argument.type count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/TargetDocumentTest.php - - message: "#^Parameter \\$targetDocument of attribute class Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Annotations\\\\ReferenceOne constructor expects class\\-string\\|null, string given\\.$#" + message: '#^Parameter \$targetDocument of attribute class Doctrine\\ODM\\MongoDB\\Mapping\\Annotations\\ReferenceOne constructor expects class\-string\|null, string given\.$#' + identifier: argument.type count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/TargetDocumentTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1058PersistDocument\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH1058PersistDocument\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1058Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1058PersistDocument\\:\\:\\$value is never read, only written\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH1058PersistDocument\:\:\$value is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1058Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1058UpsertDocument\\:\\:\\$value is never read, only written\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH1058UpsertDocument\:\:\$value is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1058Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1964Document\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH1964Document\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1964Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1990Document\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH1990Document\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1990Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH1990Document\\:\\:\\$parent is never read, only written\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH1990Document\:\:\$parent is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1990Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH921Post\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH921Post\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH921Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH921User\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH921User\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH921Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\GH999Document\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\GH999Document\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH999Test.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Functional\\\\Ticket\\\\MODM116Parent\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Functional\\Ticket\\MODM116Parent\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/MODM116Test.php - - message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" - count: 2 - path: tests/Doctrine/ODM/MongoDB/Tests/Id/IncrementGeneratorTest.php - - - - message: "#^Used constant DOCTRINE_MONGODB_DATABASE not found\\.$#" - count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Id/IncrementGeneratorTest.php - - - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\AnnotationDriverTestSuper\\:\\:\\$private is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\AnnotationDriverTestSuper\:\:\$private is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractAnnotationDriverTestCase.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\DocumentSubClass2\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\DocumentSubClass2\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\DocumentSubClass2\\:\\:\\$name is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\DocumentSubClass2\:\:\$name is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\DocumentSubClass\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\DocumentSubClass\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\DocumentSubClass\\:\\:\\$name is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\DocumentSubClass\:\:\$name is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\GridFSChildClass\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\GridFSChildClass\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\GridFSParentClass\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\GridFSParentClass\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassBase\\:\\:\\$mapped1 is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\MappedSuperclassBase\:\:\$mapped1 is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassBase\\:\\:\\$mapped2 is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\MappedSuperclassBase\:\:\$mapped2 is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassBase\\:\\:\\$mappedRelated1 is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\MappedSuperclassBase\:\:\$mappedRelated1 is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassBase\\:\\:\\$transient is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\MappedSuperclassBase\:\:\$transient is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassRelated1\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\MappedSuperclassRelated1\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\MappedSuperclassRelated1\\:\\:\\$name is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\MappedSuperclassRelated1\:\:\$name is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\TransientBaseClass\\:\\:\\$transient1 is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\TransientBaseClass\:\:\$transient1 is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\TransientBaseClass\\:\\:\\$transient2 is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\TransientBaseClass\:\:\$transient2 is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/BasicInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\LoadEventTestDocument\\:\\:\\$about is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\LoadEventTestDocument\:\:\$about is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataLoadEventTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\LoadEventTestDocument\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\LoadEventTestDocument\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataLoadEventTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\LoadEventTestDocument\\:\\:\\$name is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\LoadEventTestDocument\:\:\$name is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataLoadEventTest.php - - message: "#^Property DoctrineGlobal_User\\:\\:\\$email is unused\\.$#" + message: '#^Property DoctrineGlobal_User\:\:\$email is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/Documents/GlobalNamespaceDocument.php - - message: "#^Property DoctrineGlobal_User\\:\\:\\$id is unused\\.$#" + message: '#^Property DoctrineGlobal_User\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/Documents/GlobalNamespaceDocument.php - - message: "#^Property DoctrineGlobal_User\\:\\:\\$username is unused\\.$#" + message: '#^Property DoctrineGlobal_User\:\:\$username is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/Documents/GlobalNamespaceDocument.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\ShardedCollectionPerClass1\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\ShardedCollectionPerClass1\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\ShardedSingleCollInheritance1\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\ShardedSingleCollInheritance1\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\ShardedSubclass\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\ShardedSubclass\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Mapping\\\\ShardedSuperclass\\:\\:\\$name is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\ShardedSuperclass\:\:\$name is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Query\\\\BuilderTest\\:\\:testExclude\\(\\) has parameter \\$expected with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Query\\BuilderTest\:\:testExclude\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Query\\\\BuilderTest\\:\\:testProxiedExprMethods\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Query\\BuilderTest\:\:testSelect\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Query\\\\BuilderTest\\:\\:testSelect\\(\\) has parameter \\$expected with no value type specified in iterable type array\\.$#" + message: '#^Parameter \#1 \$primer of method Doctrine\\ODM\\MongoDB\\Query\\Builder\:\:prime\(\) expects bool\|\(callable\(\)\: mixed\), 1 given\.$#' + identifier: argument.type count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php - - message: "#^Parameter \\#1 \\$primer of method Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Builder\\:\\:prime\\(\\) expects bool\\|\\(callable\\(\\)\\: mixed\\), 1 given\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\QueryTest\:\:createCursorMock\(\) return type has no value type specified in iterable type Traversable\.$#' + identifier: missingType.iterableValue count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php - - message: "#^Constant DOCTRINE_MONGODB_DATABASE not found\\.$#" - count: 2 + message: '#^Parameter \#4 \$query of class Doctrine\\ODM\\MongoDB\\Query\\Query constructor expects array\{distinct\?\: string, hint\?\: array\\|string, limit\?\: int, maxTimeMS\?\: int, multiple\?\: bool, new\?\: bool, newObj\?\: array\, query\?\: array\, \.\.\.\}, array\{type\: \-1\} given\.$#' + identifier: argument.type + count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\QueryTest\\:\\:createCursorMock\\(\\) return type has no value type specified in iterable type Traversable\\.$#" + message: '#^Instantiated class MongoDB\\Model\\CollectionInfoCommandIterator not found\.$#' + identifier: class.notFound count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php - - message: "#^Method class@anonymous/tests/Doctrine/ODM/MongoDB/Tests/QueryTest\\.php\\:561\\:\\:current\\(\\) return type has no value type specified in iterable type array\\.$#" + message: '#^Instantiated class MongoDB\\Model\\IndexInfoIteratorIterator not found\.$#' + identifier: class.notFound count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php - - message: "#^Method class@anonymous/tests/Doctrine/ODM/MongoDB/Tests/QueryTest\\.php\\:561\\:\\:setTypeMap\\(\\) has parameter \\$typemap with no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\SchemaManagerTest\:\:createCollectionIterator\(\) has parameter \$collections with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php - - message: "#^Method class@anonymous/tests/Doctrine/ODM/MongoDB/Tests/QueryTest\\.php\\:561\\:\\:toArray\\(\\) return type has no value type specified in iterable type array\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\SchemaManagerTest\:\:createCollectionIterator\(\) should return Iterator but returns MongoDB\\Model\\CollectionInfoCommandIterator\.$#' + identifier: return.type count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php - - message: "#^Parameter \\#4 \\$query of class Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Query constructor expects array\\{distinct\\?\\: string, hint\\?\\: array\\\\|string, limit\\?\\: int, maxTimeMS\\?\\: int, multiple\\?\\: bool, new\\?\\: bool, newObj\\?\\: array\\, query\\?\\: array\\, \\.\\.\\.\\}, array\\{type\\: \\-1\\} given\\.$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\SchemaManagerTest\:\:createIndexIterator\(\) has parameter \$indexes with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php - - message: "#^Return type \\(MongoDB\\\\BSON\\\\Int64\\) of method class@anonymous/tests/Doctrine/ODM/MongoDB/Tests/QueryTest\\.php\\:561\\:\\:getId\\(\\) should be compatible with return type \\(MongoDB\\\\Driver\\\\CursorId\\) of method MongoDB\\\\Driver\\\\CursorInterface\\:\\:getId\\(\\)$#" + message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\SchemaManagerTest\:\:createIndexIterator\(\) should return Iterator but returns MongoDB\\Model\\IndexInfoIteratorIterator\.$#' + identifier: return.type count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php - - message: "#^Used constant DOCTRINE_MONGODB_DATABASE not found\\.$#" + message: '#^Call to an undefined method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:doLoadMetadata\(\)\.$#' + identifier: method.notFound count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php - - message: "#^Instantiated class MongoDB\\\\Model\\\\CollectionInfoCommandIterator not found\\.$#" + message: '#^Call to an undefined method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:getDriver\(\)\.$#' + identifier: method.notFound count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php - - message: "#^Instantiated class MongoDB\\\\Model\\\\IndexInfoIteratorIterator not found\\.$#" + message: '#^Call to an undefined method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:initializeReflection\(\)\.$#' + identifier: method.notFound count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\SchemaManagerTest\\:\\:createCollectionIterator\\(\\) has parameter \\$collections with no value type specified in iterable type array\\.$#" + message: '#^Call to an undefined method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:isEntity\(\)\.$#' + identifier: method.notFound count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\SchemaManagerTest\\:\\:createCollectionIterator\\(\\) should return Iterator but returns MongoDB\\\\Model\\\\CollectionInfoCommandIterator\\.$#" + message: '#^Call to an undefined method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:newClassMetadataInstance\(\)\.$#' + identifier: method.notFound count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\SchemaManagerTest\\:\\:createIndexIterator\\(\\) has parameter \\$indexes with no value type specified in iterable type array\\.$#" + message: '#^Call to an undefined method Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadataFactoryInterface\:\:wakeupReflection\(\)\.$#' + identifier: method.notFound count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\SchemaManagerTest\\:\\:createIndexIterator\\(\\) should return Iterator but returns MongoDB\\\\Model\\\\IndexInfoIteratorIterator\\.$#" + message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:212\:\:__construct\(\) has parameter \$classNames with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 - path: tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php + + - + message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:212\:\:doLoadMetadata\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php + + - + message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:212\:\:doLoadMetadata\(\) has parameter \$parent with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php + + - + message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:212\:\:getAllMetadata\(\) return type with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php + + - + message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:212\:\:initializeReflection\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php + + - + message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:212\:\:isEntity\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php + + - + message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:212\:\:wakeupReflection\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\GH297\\\\User\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Tools\\GH297\\User\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Tools/GH297/User.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$embedMany is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Tools\\ResolveTargetDocument\:\:\$embedMany is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$embedOne is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Tools\\ResolveTargetDocument\:\:\$embedOne is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Tools\\ResolveTargetDocument\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$refMany is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Tools\\ResolveTargetDocument\:\:\$refMany is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\ResolveTargetDocument\\:\\:\\$refOne is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Tools\\ResolveTargetDocument\:\:\$refOne is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\Tools\\\\TargetDocument\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\Tools\\TargetDocument\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Tools/ResolveTargetDocumentListenerTest.php - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Tests\\\\ArrayTest\\:\\:\\$id is unused\\.$#" + message: '#^Property Doctrine\\ODM\\MongoDB\\Tests\\ArrayTest\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php - - message: "#^Property Documents\\\\Account\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Account\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Account.php - - message: "#^Property Documents\\\\Address\\:\\:\\$test is unused\\.$#" + message: '#^Property Documents\\Address\:\:\$test is unused\.$#' + identifier: property.unused count: 1 path: tests/Documents/Address.php - - message: "#^Property Documents\\\\Album\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Album\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Album.php - - message: "#^Property Documents\\\\Article\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Article\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Article.php - - message: "#^Property Documents\\\\Bars\\\\Bar\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Bars\\Bar\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Bars/Bar.php - - message: "#^Property Documents\\\\Category\\:\\:\\$id is unused\\.$#" + message: '#^Property Documents\\Category\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Documents/Category.php - - message: "#^Property Documents\\\\Developer\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Developer\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Developer.php - - message: "#^Property Documents\\\\Developer\\:\\:\\$name is never read, only written\\.$#" + message: '#^Property Documents\\Developer\:\:\$name is never read, only written\.$#' + identifier: property.onlyWritten count: 1 path: tests/Documents/Developer.php - - message: "#^Property Documents\\\\Ecommerce\\\\StockItem\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Ecommerce\\StockItem\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Ecommerce/StockItem.php - - message: "#^Property Documents\\\\Event\\:\\:\\$id is never written, only read\\.$#" + message: '#^Method Documents\\Encryption\\Client\:\:__construct\(\) has parameter \$clientCards with generic interface Doctrine\\Common\\Collections\\Collection but does not specify its types\: TKey, T$#' + identifier: missingType.generics + count: 1 + path: tests/Documents/Encryption/Client.php + + - + message: '#^Property Documents\\Event\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Event.php - - message: "#^Property Documents\\\\File\\:\\:\\$chunkSize is never written, only read\\.$#" + message: '#^Property Documents\\File\:\:\$chunkSize is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/File.php - - message: "#^Property Documents\\\\File\\:\\:\\$filename is never written, only read\\.$#" + message: '#^Property Documents\\File\:\:\$filename is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/File.php - - message: "#^Property Documents\\\\File\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\File\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/File.php - - message: "#^Property Documents\\\\File\\:\\:\\$length is never written, only read\\.$#" + message: '#^Property Documents\\File\:\:\$length is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/File.php - - message: "#^Property Documents\\\\File\\:\\:\\$uploadDate is never written, only read\\.$#" + message: '#^Property Documents\\File\:\:\$uploadDate is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/File.php - - message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$chunkSize is never written, only read\\.$#" + message: '#^Property Documents\\FileWithoutChunkSize\:\:\$chunkSize is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/FileWithoutChunkSize.php - - message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$filename is never written, only read\\.$#" + message: '#^Property Documents\\FileWithoutChunkSize\:\:\$filename is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/FileWithoutChunkSize.php - - message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\FileWithoutChunkSize\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/FileWithoutChunkSize.php - - message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$length is never written, only read\\.$#" + message: '#^Property Documents\\FileWithoutChunkSize\:\:\$length is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/FileWithoutChunkSize.php - - message: "#^Property Documents\\\\FileWithoutChunkSize\\:\\:\\$uploadDate is never written, only read\\.$#" + message: '#^Property Documents\\FileWithoutChunkSize\:\:\$uploadDate is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/FileWithoutChunkSize.php - - message: "#^Property Documents\\\\FileWithoutMetadata\\:\\:\\$filename is never written, only read\\.$#" + message: '#^Property Documents\\FileWithoutMetadata\:\:\$filename is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/FileWithoutMetadata.php - - message: "#^Property Documents\\\\FileWithoutMetadata\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\FileWithoutMetadata\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/FileWithoutMetadata.php - - message: "#^Property Documents\\\\Functional\\\\FavoritesUser\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Functional\\FavoritesUser\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Functional/FavoritesUser.php - - message: "#^Property Documents\\\\Group\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Group\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Group.php - - message: "#^Property Documents\\\\Message\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Message\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Message.php - - message: "#^Property Documents\\\\ProfileNotify\\:\\:\\$profileId is never written, only read\\.$#" + message: '#^Property Documents\\ProfileNotify\:\:\$profileId is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/ProfileNotify.php - - message: "#^Property Documents\\\\Project\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Project\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Project.php - - message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$email is unused\\.$#" + message: '#^Property Documents\\SchemaValidated\:\:\$email is unused\.$#' + identifier: property.unused count: 1 path: tests/Documents/SchemaValidated.php - - message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$id is unused\\.$#" + message: '#^Property Documents\\SchemaValidated\:\:\$id is unused\.$#' + identifier: property.unused count: 1 path: tests/Documents/SchemaValidated.php - - message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$name is unused\\.$#" + message: '#^Property Documents\\SchemaValidated\:\:\$name is unused\.$#' + identifier: property.unused count: 1 path: tests/Documents/SchemaValidated.php - - message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$phone is unused\\.$#" + message: '#^Property Documents\\SchemaValidated\:\:\$phone is unused\.$#' + identifier: property.unused count: 1 path: tests/Documents/SchemaValidated.php - - message: "#^Property Documents\\\\SchemaValidated\\:\\:\\$status is unused\\.$#" + message: '#^Property Documents\\SchemaValidated\:\:\$status is unused\.$#' + identifier: property.unused count: 1 path: tests/Documents/SchemaValidated.php - - message: "#^Property Documents\\\\Task\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Task\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Task.php - - message: "#^Property Documents\\\\Tournament\\\\Participant\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Tournament\\Participant\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Tournament/Participant.php - - message: "#^Property Documents\\\\Tournament\\\\Tournament\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\Tournament\\Tournament\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/Tournament/Tournament.php - - message: "#^Property Documents\\\\UserName\\:\\:\\$id is never written, only read\\.$#" + message: '#^Property Documents\\UserName\:\:\$id is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/UserName.php - - message: "#^Property Documents\\\\UserName\\:\\:\\$username is never written, only read\\.$#" + message: '#^Property Documents\\UserName\:\:\$username is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/UserName.php - - message: "#^Property Documents\\\\UserName\\:\\:\\$viewReference is never written, only read\\.$#" + message: '#^Property Documents\\UserName\:\:\$viewReference is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/UserName.php - - message: "#^Property Documents\\\\ViewReference\\:\\:\\$referenceOneViewMappedBy is never written, only read\\.$#" + message: '#^Property Documents\\ViewReference\:\:\$referenceOneViewMappedBy is never written, only read\.$#' + identifier: property.onlyRead count: 1 path: tests/Documents/ViewReference.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 56abe7643d..2c2c3e1985 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,7 +11,7 @@ parameters: excludePaths: - tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/User.php - tests/Hydrators/ - - tests/PersistentCollections/ + - tests/PersistentCollections/ (?) - tests/Proxies/ treatPhpDocTypesAsCertain: false ignoreErrors: @@ -22,10 +22,20 @@ parameters: - message: '#^Circular definition detected in type alias#' path: lib/Doctrine/ODM/MongoDB/Aggregation/ + # Keep type assertions even when type is declared + - identifier: staticMethod.alreadyNarrowedType + path: tests/Doctrine/ + + - message: '#with generic class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata does not specify its types: T$#' + identifier: missingType.generics + + - message: '#^(Constant|Used constant) DOCTRINE_MONGODB_DATABASE not found\.$#' + identifier: constant.notFound + path: tests/ + # To be removed when reaching phpstan level 6 checkMissingVarTagTypehint: true checkMissingTypehints: true - checkMissingIterableValueType: true # Disabled due to inconsistent errors upon encountering psalm types reportUnmatchedIgnoredErrors: false diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php index 19ee8f9a56..7e67db19c6 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SearchTest.php @@ -1313,4 +1313,86 @@ public function testSearchEmbeddedDocumentOperators(array $expectedOperator, Clo $searchStage->getExpression(), ); } + + #[DataProvider('provideAutocompleteBuilders')] + public function testSearchOperatorsWithSearchBefore(array $expectedOperator, Closure $createOperator): void + { + $baseExpected = [ + 'index' => 'my_search_index', + 'highlight' => (object) [ + 'path' => 'content', + 'maxCharsToExamine' => 2, + 'maxNumPassages' => 3, + ], + 'count' => (object) [ + 'type' => 'lowerBound', + 'threshold' => 1000, + ], + 'returnStoredSource' => true, + 'searchBefore' => 'marker', + ]; + + $searchStage = new Search($this->getTestAggregationBuilder()); + $searchStage + ->index('my_search_index') + ->searchBefore('marker'); + + $result = $createOperator($searchStage); + + self::logicalOr( + new IsInstanceOf(AbstractSearchOperator::class), + new IsInstanceOf(Search::class), + ); + + $result + ->highlight('content', 2, 3) + ->countDocuments('lowerBound', 1000) + ->returnStoredSource(); + + self::assertEquals( + ['$search' => (object) array_merge($baseExpected, $expectedOperator)], + $searchStage->getExpression(), + ); + } + + #[DataProvider('provideAutocompleteBuilders')] + public function testSearchOperatorsWithSearchAfter(array $expectedOperator, Closure $createOperator): void + { + $baseExpected = [ + 'index' => 'my_search_index', + 'highlight' => (object) [ + 'path' => 'content', + 'maxCharsToExamine' => 2, + 'maxNumPassages' => 3, + ], + 'count' => (object) [ + 'type' => 'lowerBound', + 'threshold' => 1000, + ], + 'returnStoredSource' => true, + 'searchAfter' => 'marker', + ]; + + $searchStage = new Search($this->getTestAggregationBuilder()); + $searchStage + ->index('my_search_index') + ->searchAfter('marker'); + + $result = $createOperator($searchStage); + + self::logicalOr( + new IsInstanceOf(AbstractSearchOperator::class), + new IsInstanceOf(Search::class), + ); + + $result + ->highlight('content', 2, 3) + ->countDocuments('lowerBound', 1000) + ->returnStoredSource(); + + self::assertEquals( + ['$search' => (object) array_merge($baseExpected, $expectedOperator)], + $searchStage->getExpression(), + ); + } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php index 13b366d69d..e1c347eff9 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php @@ -10,8 +10,10 @@ use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\Query\Filter\Filter; use Doctrine\ODM\MongoDB\UnitOfWork; +use Doctrine\Persistence\Mapping\Driver\FileClassLocator; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use MongoDB\Client; +use MongoDB\Driver\Command; use MongoDB\Driver\Manager; use MongoDB\Driver\Server; use MongoDB\Model\DatabaseInfo; @@ -20,6 +22,7 @@ use function array_key_exists; use function array_map; +use function class_exists; use function count; use function explode; use function getenv; @@ -124,7 +127,14 @@ public static function isLazyObject(object $document): bool protected static function createMetadataDriverImpl(): MappingDriver { - return AttributeDriver::create(__DIR__ . '/../../../../Documents'); + $paths = [__DIR__ . '/../../../../Documents']; + + // Available in Doctrine Persistence 4.1+ + if (class_exists(FileClassLocator::class)) { + $paths = FileClassLocator::createFromDirectories($paths); + } + + return AttributeDriver::create($paths); } protected static function createTestDocumentManager(): DocumentManager @@ -192,6 +202,24 @@ protected function skipTestIfSharded(string $className): void $this->markTestSkipped('Test does not apply on sharded clusters'); } + protected function skipTestIfQueryableEncryptionNotSupported(): void + { + if ($this->getPrimaryServer()->getType() === Server::TYPE_STANDALONE) { + $this->markTestSkipped('Queryable Encryption test requires a replica set or sharded cluster'); + } + + $buildInfo = $this->getPrimaryServer()->executeCommand( + DOCTRINE_MONGODB_DATABASE, + new Command(['buildInfo' => 1]), + )->toArray()[0]; + + if (! in_array('enterprise', $buildInfo->modules ?? [])) { + $this->markTestSkipped('Queryable Encryption test requires MongoDB Atlas or Enterprise'); + } + + $this->requireVersion($buildInfo->version, '7.0', '<', 'Queryable Encryption test requires MongoDB 7.0 or higher'); + } + protected function requireVersion(string $installedVersion, string $requiredVersion, ?string $operator, string $message): void { if (! version_compare($installedVersion, $requiredVersion, $operator)) { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/ConfigurationTest.php b/tests/Doctrine/ODM/MongoDB/Tests/ConfigurationTest.php index 38797b822c..6432494156 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/ConfigurationTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/ConfigurationTest.php @@ -5,10 +5,17 @@ namespace Doctrine\ODM\MongoDB\Tests; use Doctrine\ODM\MongoDB\Configuration; +use Doctrine\ODM\MongoDB\ConfigurationException; use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionFactory; use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionGenerator; +use MongoDB\Driver\Manager; +use PHPUnit\Framework\TestCase; +use stdClass; -class ConfigurationTest extends BaseTestCase +use function base64_encode; +use function str_repeat; + +class ConfigurationTest extends TestCase { public function testDefaultPersistentCollectionFactory(): void { @@ -40,4 +47,133 @@ public function testEnableTransactionalFlush(): void $c->setUseTransactionalFlush(false); self::assertFalse($c->isTransactionalFlushEnabled(), 'Transactional flush is disabled after setTransactionalFlush(false)'); } + + public function testLocalKmsProvider(): void + { + $c = new Configuration(); + $c->setKmsProvider(['type' => 'local', 'key' => base64_encode(str_repeat('1', 96))]); + $c->setAutoEncryption(['extraOptions' => ['mongocryptdURI' => 'mongodb://localhost:27020']]); + $c->setDefaultDB('default_database'); + + self::assertSame('local', $c->getDefaultKmsProvider()); + self::assertNull($c->getDefaultMasterKey()); + self::assertEquals([ + 'kmsProviders' => [ + 'local' => ['key' => base64_encode(str_repeat('1', 96))], + ], + 'extraOptions' => ['mongocryptdURI' => 'mongodb://localhost:27020'], + // Default key vault namespace + 'keyVaultNamespace' => 'default_database.datakeys', + ], $c->getDriverOptions()['autoEncryption']); + } + + public function testKmsProvider(): void + { + $c = new Configuration(); + $c->setKmsProvider(['type' => 'aws', 'accessKeyId' => 'AKIA', 'secretAccessKey' => 'SECRET']); + $c->setAutoEncryption(['keyVaultNamespace' => 'keyvault.datakeys']); + $c->setDefaultMasterKey($masterKey = ['region' => 'us-east-1', 'key' => 'arn:aws:kms:us-east-1:123456789012:key/abcd1234-ab12-cd34-ef56-1234567890ab']); + + self::assertSame('aws', $c->getDefaultKmsProvider()); + self::assertSame($masterKey, $c->getDefaultMasterKey()); + self::assertEquals([ + 'kmsProviders' => [ + 'aws' => ['accessKeyId' => 'AKIA', 'secretAccessKey' => 'SECRET'], + ], + // Key vault namespace from the configuration + 'keyVaultNamespace' => 'keyvault.datakeys', + ], $c->getDriverOptions()['autoEncryption']); + } + + public function testEmptyKmsProviderOptions(): void + { + $c = new Configuration(); + $c->setKmsProvider(['type' => 'aws']); + $c->setAutoEncryption(['keyVaultNamespace' => 'keyvault.datakeys']); + + self::assertEquals([ + 'kmsProviders' => [ + 'aws' => new stdClass(), + ], + 'keyVaultNamespace' => 'keyvault.datakeys', + ], $c->getDriverOptions()['autoEncryption']); + } + + public function testAutoEncryptionOptions(): void + { + $c = new Configuration(); + $c->setAutoEncryption([ + 'keyVaultClient' => $keyVaultClient = new Manager(), + 'keyVaultNamespace' => 'keyvault.datakeys', + 'extraOptions' => ['mongocryptdURI' => 'mongodb://localhost:27020'], + 'tlsOptions' => ['local' => ['tlsDisableOCSPEndpointCheck' => true]], + ]); + $c->setKmsProvider(['type' => 'local', 'key' => '1234567890123456789012345678901234567890123456789012345678901234']); + + self::assertEquals([ + 'kmsProviders' => [ + 'local' => ['key' => '1234567890123456789012345678901234567890123456789012345678901234'], + ], + 'keyVaultNamespace' => 'keyvault.datakeys', + 'keyVaultClient' => $keyVaultClient, + 'extraOptions' => ['mongocryptdURI' => 'mongodb://localhost:27020'], + 'tlsOptions' => ['local' => ['tlsDisableOCSPEndpointCheck' => true]], + ], $c->getDriverOptions()['autoEncryption']); + + self::assertEquals([ + 'kmsProviders' => [ + 'local' => ['key' => '1234567890123456789012345678901234567890123456789012345678901234'], + ], + 'keyVaultNamespace' => 'keyvault.datakeys', + 'keyVaultClient' => $keyVaultClient, + 'tlsOptions' => ['local' => ['tlsDisableOCSPEndpointCheck' => true]], + ], $c->getClientEncryptionOptions()); + } + + public function testMissingDefaultMasterKey(): void + { + $c = new Configuration(); + $c->setKmsProvider(['type' => 'aws', 'accessKeyId' => 'AKIA', 'secretAccessKey' => 'SECRET']); + + self::expectException(ConfigurationException::class); + self::expectExceptionMessage('The "masterKey" configuration is required for the KMS provider "aws"'); + $c->getDefaultMasterKey(); + } + + public function testKmsProvidersIsForbiddenInAutoEncryptionOptions(): void + { + $c = new Configuration(); + + self::expectException(ConfigurationException::class); + self::expectExceptionMessage('The "kmsProviders" encryption option must be set using the "setKmsProvider()" method'); + $c->setAutoEncryption(['kmsProviders' => ['aws' => ['accessKeyId' => 'AKIA', 'secretAccessKey' => 'SECRET']]]); + } + + public function testClientEncryptionOptionsNotSet(): void + { + $c = new Configuration(); + self::expectException(ConfigurationException::class); + self::expectExceptionMessage('MongoDB client encryption options are not set in configuration'); + $c->getClientEncryptionOptions(); + } + + public function testKmsProviderTypeRequired(): void + { + $c = new Configuration(); + self::expectException(ConfigurationException::class); + self::expectExceptionMessage('The KMS provider "type" is required'); + + // @phpstan-ignore argument.type + $c->setKmsProvider(['foo' => 'bar']); + } + + public function testKmsProviderTypeMustBeString(): void + { + $c = new Configuration(); + self::expectException(ConfigurationException::class); + self::expectExceptionMessage('The KMS provider "type" must be a non-empty string'); + + // @phpstan-ignore argument.type + $c->setKmsProvider(['type' => ['not', 'a', 'string']]); + } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/LifecycleTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/LifecycleTest.php index 880014c69e..d51e5b3dc6 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/LifecycleTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/LifecycleTest.php @@ -4,6 +4,7 @@ namespace Doctrine\ODM\MongoDB\Tests\Functional; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; @@ -60,7 +61,7 @@ class ParentObject #[ODM\Id] private $id; - /** @var Collection|array */ + /** @var Collection */ #[ODM\ReferenceMany(targetDocument: ChildObject::class, cascade: 'all')] private $children; @@ -96,7 +97,7 @@ public function getName(): ?string #[ODM\PreUpdate] public function prePersistPreUpdate(): void { - $this->children = [$this->child]; + $this->children = new ArrayCollection([$this->child]); } #[ODM\PreUpdate] @@ -105,7 +106,7 @@ public function preUpdate(): void $this->childEmbedded->setName('changed'); } - /** @return Collection|array */ + /** @return Collection */ public function getChildren() { return $this->children; diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/QueryableEncryptionTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/QueryableEncryptionTest.php new file mode 100644 index 0000000000..f6219276a8 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/QueryableEncryptionTest.php @@ -0,0 +1,124 @@ +skipTestIfQueryableEncryptionNotSupported(); + } + + public function tearDown(): void + { + $this->dm?->getDocumentCollection(Patient::class)?->drop(['encryptedFields' => []]); + + parent::tearDown(); + } + + public function testCreateAndQueryEncryptedCollection(): void + { + $nonEncryptedClient = new Client(self::getUri()); + $nonEncryptedDatabase = $nonEncryptedClient->getDatabase(DOCTRINE_MONGODB_DATABASE); + + // Create the encrypted collection + $this->dm->getSchemaManager()->createDocumentCollection(Patient::class); + + // Test created collections + $collectionNames = iterator_to_array($nonEncryptedDatabase->listCollectionNames()); + self::assertContains('patients', $collectionNames); + self::assertContains('datakeys', $collectionNames); + + // Insert a document + $patient = new Patient( + patientName: 'Jon Doe', + patientId: 12345678, + patientRecord: new PatientRecord( + ssn: '987-65-4320', + billing: new PatientBilling( + type: 'Visa', + number: '4111111111111111', + ), + billingAmount: 1200, + ), + ); + + $this->dm->persist($patient); + $this->dm->flush(); + $this->dm->clear(); + + // Data is encrypted + $document = $nonEncryptedDatabase->getCollection('patients')->findOne(['patientName' => 'Jon Doe']); + self::assertInstanceOf(BSONDocument::class, $document); + self::assertSame('Jon Doe', $document->patientName); + self::assertSame(12345678, $document->patientId); + self::assertInstanceOf(Binary::class, $document->patientRecord->ssn); + self::assertSame(Binary::TYPE_ENCRYPTED, $document->patientRecord->ssn->getType()); + self::assertInstanceOf(Binary::class, $document->patientRecord->billing); + self::assertSame(Binary::TYPE_ENCRYPTED, $document->patientRecord->billing->getType()); + self::assertInstanceOf(Binary::class, $document->patientRecord->billingAmount); + self::assertSame(Binary::TYPE_ENCRYPTED, $document->patientRecord->billingAmount->getType()); + + // Queryable with equality + $result = $this->dm->getRepository(Patient::class)->findOneBy(['patientRecord.ssn' => '987-65-4320']); + self::assertNotNull($result); + self::assertSame('Jon Doe', $result->patientName); + self::assertSame('987-65-4320', $result->patientRecord->ssn); + self::assertSame('4111111111111111', $result->patientRecord->billing->number); + + $this->dm->clear(); + + // Queryable with range + $result = $this->dm->getRepository(Patient::class)->findOneBy(['patientRecord.billingAmount' => ['$gt' => 1000, '$lt' => 2000]]); + self::assertNotNull($result); + self::assertSame('Jon Doe', $result->patientName); + self::assertSame('987-65-4320', $result->patientRecord->ssn); + self::assertSame('4111111111111111', $result->patientRecord->billing->number); + + // Drop the encrypted collection + $this->dm->getSchemaManager()->dropDocumentCollection(Patient::class); + $collectionNames = iterator_to_array($nonEncryptedDatabase->listCollectionNames(['filter' => ['name' => new Regex('patients')]])); + self::assertSame([], $collectionNames, 'The 2 metadata collections should also be dropped'); + } + + protected static function createTestDocumentManager(): DocumentManager + { + $config = static::getConfiguration(); + $config->setDefaultDB(DOCTRINE_MONGODB_DATABASE); + $config->setKmsProvider([ + 'type' => 'local', + 'key' => new Binary(random_bytes(96)), + ]); + + $autoEncryptionOptions = []; + + $cryptSharedLibPath = getenv('CRYPT_SHARED_LIB_PATH'); + if ($cryptSharedLibPath) { + $autoEncryptionOptions['extraOptions']['cryptSharedLibPath'] = $cryptSharedLibPath; + } + + $config->setAutoEncryption($autoEncryptionOptions); + + $client = new Client(self::getUri(), [], $config->getDriverOptions()); + + return DocumentManager::create($client, $config); + } +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php index c1f2b70e9f..3511fba7eb 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php @@ -14,6 +14,8 @@ use MongoDB\Driver\WriteConcern; use PHPUnit\Framework\Attributes\DataProvider; +use function usleep; + /** @phpstan-type ReadPreferenceTagShape array{dc?: string, usage?: string} */ class ReadPreferenceTest extends BaseTestCase { @@ -57,7 +59,13 @@ public function testHintIsSetOnQuery(string $readPreference, array $tags = []): $this->assertReadPreferenceHint($readPreference, $query->getQuery()['readPreference'], $tags); - $user = $query->getSingleResult(); + $retries = 0; + do { + // Wait a bit to ensure replication has caught up. + usleep(1_000 * $retries); + $user = $query->getSingleResult(); + } while ($user === null && $retries++ < 100); + self::assertInstanceOf(User::class, $user); $groups = $user->getGroups(); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/TimeSeriesTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/TimeSeriesTest.php index 9556f298ce..388bb286ac 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/TimeSeriesTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/TimeSeriesTest.php @@ -41,7 +41,7 @@ public function testCreateTimeSeriesDocumentWithoutId(): void $document->metadata = 'energy'; $this->dm->persist($document); - $this->dm->flush(); + $this->dm->flush(['withTransaction' => false]); $this->assertCount(1, $this->dm->getDocumentCollection(TimeSeriesDocument::class)->find()); } @@ -57,7 +57,7 @@ public function testCreateTimeSeriesDocumentWithId(): void $document->metadata = 'energy'; $this->dm->persist($document); - $this->dm->flush(); + $this->dm->flush(['withTransaction' => false]); $this->assertCount(1, $this->dm->getDocumentCollection(TimeSeriesDocument::class)->find()); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/XmlMappingQueryableEncryptionTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/XmlMappingQueryableEncryptionTest.php new file mode 100644 index 0000000000..6f159196f6 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/XmlMappingQueryableEncryptionTest.php @@ -0,0 +1,16 @@ +expectException(MappingException::class); $annotationDriver->loadMetadataForClass('stdClass', $cm); @@ -87,8 +85,7 @@ public function testLoadMetadataForNonDocumentThrowsException(): void public function testColumnWithMissingTypeDefaultsToString(): void { $cm = new ClassMetadata(ColumnWithoutType::class); - $reader = new AnnotationReader(); - $annotationDriver = new AnnotationDriver($reader); + $annotationDriver = static::loadDriver(); $annotationDriver->loadMetadataForClass(stdClass::class, $cm); self::assertEquals('id', $cm->fieldMappings['id']['type']); @@ -255,9 +252,8 @@ public function testWrongValueForValidationValidatorShouldThrowException(): void protected function loadDriverForCMSDocuments(): MappingDriver { - $annotationDriver = static::loadDriver(); + $annotationDriver = static::loadDriver([__DIR__ . '/../../../../../Documents']); assert($annotationDriver instanceof AnnotationDriver || $annotationDriver instanceof AttributeDriver); - $annotationDriver->addPaths([__DIR__ . '/../../../../../Documents']); return $annotationDriver; } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTestCase.php index 0559892fc1..4799fd9ce9 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTestCase.php @@ -34,7 +34,8 @@ abstract class AbstractMappingDriverTestCase extends BaseTestCase { - abstract protected static function loadDriver(): MappingDriver; + /** @param list $paths */ + abstract protected static function loadDriver(array $paths = []): MappingDriver; protected static function createMetadataDriverImpl(): MappingDriver { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php index 6f6cd0144f..55064a4ffa 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php @@ -4,14 +4,15 @@ namespace Doctrine\ODM\MongoDB\Tests\Mapping; -use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\Annotations\Document; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver; +use Doctrine\Persistence\Mapping\Driver\FileClassLocator; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use function call_user_func; +use function class_exists; use function restore_error_handler; use function set_error_handler; use function sprintf; @@ -20,11 +21,13 @@ class AnnotationDriverTest extends AbstractAnnotationDriverTestCase { - protected static function loadDriver(): MappingDriver + protected static function loadDriver(array $paths = []): MappingDriver { - $reader = new AnnotationReader(); + if (class_exists(FileClassLocator::class)) { + $paths = FileClassLocator::createFromDirectories($paths); + } - return new AnnotationDriver($reader); + return AnnotationDriver::create($paths); } public function testIndexesClassAnnotationEmitsDeprecationMessage(): void @@ -84,7 +87,11 @@ public function testIndexesPropertyAnnotationEmitsDeprecationMessage(): void self::assertEquals(1, $indexes[0]['keys']['foo']); } - /** @param list|null $errors */ + /** + * @param list $errors + * + * @param-out list $errors + */ private function captureDeprecationMessages(callable $callable, ?array &$errors): mixed { /* TODO: this method can be replaced with expectUserDeprecationMessage() in PHPUnit 11+. diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AttributeDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AttributeDriverTest.php index 04e4adef46..e07a396425 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AttributeDriverTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AttributeDriverTest.php @@ -5,12 +5,19 @@ namespace Doctrine\ODM\MongoDB\Tests\Mapping; use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver; +use Doctrine\Persistence\Mapping\Driver\FileClassLocator; use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use function class_exists; + class AttributeDriverTest extends AbstractAnnotationDriverTestCase { - protected static function loadDriver(): MappingDriver + protected static function loadDriver(array $paths = []): MappingDriver { - return new AttributeDriver(); + if (class_exists(FileClassLocator::class)) { + $paths = FileClassLocator::createFromDirectories($paths); + } + + return AttributeDriver::create($paths); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/AbstractDriverTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/AbstractDriverTestCase.php index ca7061a223..c4a2df5b4c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/AbstractDriverTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/AbstractDriverTestCase.php @@ -4,13 +4,21 @@ namespace Doctrine\ODM\MongoDB\Tests\Mapping\Driver; +use DateTimeImmutable; +use Doctrine\ODM\MongoDB\Mapping\Annotations\EncryptQuery; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Documents\Account; use Documents\Address; +use Documents\Encryption\ClientCard; +use Documents\Encryption\PatientRecord; +use Documents\Encryption\RangeTypes; use Documents\Group; use Documents\Phonenumber; use Documents\Profile; +use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; +use MongoDB\BSON\UTCDateTime; use PHPUnit\Framework\TestCase; use TestDocuments\EmbeddedDocument; use TestDocuments\NullableFieldsDocument; @@ -19,10 +27,11 @@ use TestDocuments\QueryResultDocument; use TestDocuments\User; +use const PHP_INT_MAX; + abstract class AbstractDriverTestCase extends TestCase { - /** @var MappingDriver|null */ - protected $driver; + protected MappingDriver|null $driver; public function setUp(): void { @@ -31,7 +40,7 @@ public function setUp(): void public function tearDown(): void { - unset($this->driver); + $this->driver = null; } public function testDriver(): void @@ -522,4 +531,73 @@ public function testNullableFieldsMapping(): void 'storeEmptyArray' => false, ], $classMetadata->fieldMappings['groups']); } + + public function testEncryptFieldMapping(): void + { + $classMetadata = new ClassMetadata(PatientRecord::class); + $this->driver->loadMetadataForClass(PatientRecord::class, $classMetadata); + + self::assertFalse($classMetadata->isEncrypted); + + self::assertSame([ + 'queryType' => EncryptQuery::Equality, + ], $classMetadata->fieldMappings['ssn']['encrypt']); + + self::assertSame([], $classMetadata->fieldMappings['billing']['encrypt']); + + self::assertSame([ + 'queryType' => EncryptQuery::Range, + 'sparsity' => 1, + 'trimFactor' => 4, + 'min' => 100, + 'max' => 2000, + ], $classMetadata->fieldMappings['billingAmount']['encrypt']); + } + + public function testEncryptEmbeddedDocumentMapping(): void + { + $classMetadata = new ClassMetadata(ClientCard::class); + $this->driver->loadMetadataForClass(ClientCard::class, $classMetadata); + + self::assertTrue($classMetadata->isEncrypted); + + self::assertArrayNotHasKey('encrypt', $classMetadata->fieldMappings['type']); + self::assertArrayNotHasKey('encrypt', $classMetadata->fieldMappings['number']); + } + + public function testEncryptQueryRangeTypes(): void + { + $classMetadata = new ClassMetadata(RangeTypes::class); + $this->driver->loadMetadataForClass(RangeTypes::class, $classMetadata); + + self::assertEquals([ + 'queryType' => EncryptQuery::Range, + 'min' => 5, + 'max' => 10, + ], $classMetadata->fieldMappings['intField']['encrypt']); + + self::assertEquals([ + 'queryType' => EncryptQuery::Range, + 'min' => new Int64(5), + 'max' => new Int64(PHP_INT_MAX - 5), + ], $classMetadata->fieldMappings['int64Field']['encrypt']); + + self::assertEquals([ + 'queryType' => EncryptQuery::Range, + 'min' => 5.5, + 'max' => 10.5, + ], $classMetadata->fieldMappings['floatField']['encrypt']); + + self::assertEquals([ + 'queryType' => EncryptQuery::Range, + 'min' => new Decimal128('0.1'), + 'max' => new Decimal128('0.2'), + ], $classMetadata->fieldMappings['decimalField']['encrypt']); + + self::assertEquals([ + 'queryType' => EncryptQuery::Range, + 'min' => new UTCDateTime(new DateTimeImmutable('2000-01-01 00:00:00')), + 'max' => new UTCDateTime(new DateTimeImmutable('2100-01-01 00:00:00')), + ], $classMetadata->fieldMappings['dateField']['encrypt']); + } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/XmlDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/XmlDriverTest.php index 8280a1df13..b3961d0c40 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/XmlDriverTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/XmlDriverTest.php @@ -10,6 +10,7 @@ use MongoDB\BSON\Document; use TestDocuments\AlsoLoadDocument; use TestDocuments\CustomIdGenerator; +use TestDocuments\InvalidEmbeddedDocument; use TestDocuments\InvalidPartialFilterDocument; use TestDocuments\SchemaInvalidDocument; use TestDocuments\SchemaValidatedDocument; @@ -75,6 +76,16 @@ public function testInvalidPartialFilterExpressions(): void $this->driver->loadMetadataForClass(InvalidPartialFilterDocument::class, $classMetadata); } + public function testInvalidEmbeddedDocument(): void + { + $classMetadata = new ClassMetadata(InvalidEmbeddedDocument::class); + + $this->expectException(MappingException::class); + $this->expectExceptionMessageMatches('#The mapping file .+ is invalid#'); + + $this->driver->loadMetadataForClass(InvalidEmbeddedDocument::class, $classMetadata); + } + public function testWildcardIndexName(): void { $classMetadata = new ClassMetadata(WildcardIndexDocument::class); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/InvalidEmbeddedDocument.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/InvalidEmbeddedDocument.php new file mode 100644 index 0000000000..58f9c4d267 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/InvalidEmbeddedDocument.php @@ -0,0 +1,10 @@ + + + + + + + + + + + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.Patient.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.Patient.dcm.xml new file mode 100644 index 0000000000..3fe0acc85f --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.Patient.dcm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.PatientBilling.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.PatientBilling.dcm.xml new file mode 100644 index 0000000000..ce657992fe --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.PatientBilling.dcm.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.PatientRecord.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.PatientRecord.dcm.xml new file mode 100644 index 0000000000..bfe171f7f5 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.PatientRecord.dcm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.RangeTypes.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.RangeTypes.dcm.xml new file mode 100644 index 0000000000..94845576b4 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/Documents.Encryption.RangeTypes.dcm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/TestDocuments.InvalidEmbeddedDocument.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/TestDocuments.InvalidEmbeddedDocument.dcm.xml new file mode 100644 index 0000000000..c941a1482d --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures/xml/TestDocuments.InvalidEmbeddedDocument.dcm.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Symfony/AbstractDriverTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Symfony/AbstractDriverTestCase.php index 7030240eb1..9d6f7a45b6 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Symfony/AbstractDriverTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Symfony/AbstractDriverTestCase.php @@ -94,6 +94,8 @@ abstract protected function getFileExtension(): string; * @param string[] $paths * * @return FileDriver + * + * @phpstan-ignore missingType.generics */ abstract protected function getDriver(array $paths = []); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php index 35c0c779aa..c449d56890 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php @@ -12,12 +12,16 @@ use SimpleXMLElement; use stdClass; +use function assert; + use const DIRECTORY_SEPARATOR; class XmlMappingDriverTest extends AbstractMappingDriverTestCase { - protected static function loadDriver(): MappingDriver + protected static function loadDriver(array $paths = []): MappingDriver { + assert($paths === []); + return new XmlDriver(__DIR__ . DIRECTORY_SEPARATOR . 'xml'); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/PersistentCollection/CollWithPHP81Types.php b/tests/Doctrine/ODM/MongoDB/Tests/PersistentCollection/CollWithPHP81Types.php index e283386001..55e73e5ddb 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/PersistentCollection/CollWithPHP81Types.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/PersistentCollection/CollWithPHP81Types.php @@ -14,6 +14,7 @@ */ class CollWithPHP81Types extends ArrayCollection { + /** @phpstan-ignore missingType.generics, missingType.generics */ public function intersection(Collection&ArrayCollection $param): Collection&ArrayCollection { return $param; diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php index d639cd18e4..f3915f7993 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php @@ -468,6 +468,7 @@ public function testDeepClone(): void self::assertCount(1, $qb->getQueryArray()); } + /** @param array $args */ #[DataProvider('provideProxiedExprMethods')] public function testProxiedExprMethods(string $method, array $args = []): void { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php index 09fc5f6498..a9560914f6 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php @@ -574,21 +574,24 @@ public function isDead(): bool return false; } + /** @phpstan-ignore missingType.iterableValue */ public function setTypeMap(array $typemap): void { } + /** @phpstan-ignore missingType.iterableValue */ public function toArray(): array { return iterator_to_array($this); } - public function current(): array|null + /** @phpstan-ignore missingType.iterableValue */ + public function current(): object|array|null { return parent::current(); } - public function key(): int|null + public function key(): int { return parent::key(); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php index 7f4bdbbf7e..d1544b7319 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php @@ -492,6 +492,25 @@ public function testUpdateDocumentSearchIndexesNotSupportedForClassWithoutSearch $this->schemaManager->updateDocumentSearchIndexes(CmsProduct::class); } + public function testUpdateDocumentWhenSearchNotEnabled(): void + { + // Class has no search indexes, so if the server doesn't support them we assume everything is fine + $collectionName = $this->dm->getClassMetadata(CmsProduct::class)->getCollection(); + $collection = $this->documentCollections[$collectionName]; + $collection + ->expects($this->once()) + ->method('listSearchIndexes') + ->willThrowException($this->createSearchNotEnabledCommandException()); + $collection + ->expects($this->never()) + ->method('dropSearchIndex'); + $collection + ->expects($this->never()) + ->method('updateSearchIndex'); + + $this->schemaManager->updateDocumentSearchIndexes(CmsProduct::class); + } + public function testUpdateDocumentSearchIndexesNotSupportedForClassWithoutSearchIndexesOnOlderServers(): void { // Class has no search indexes, so if the server doesn't support them we assume everything is fine @@ -1375,6 +1394,11 @@ private function createSearchIndexCommandException(): CommandException return new CommandException('PlanExecutor error during aggregation :: caused by :: Search index commands are only supported with Atlas.', 115); } + private function createSearchNotEnabledCommandException(): CommandException + { + return new CommandException('Using Atlas Search Database Commands and the $listSearchIndexes aggregation stage requires additional configuration.', 31082); + } + private function createSearchIndexCommandExceptionForOlderServers(): CommandException { return new CommandException('Unrecognized pipeline stage name: \'$listSearchIndexes\'', 40234); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Tools/Console/Command/AbstractCommandTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/Tools/Console/Command/AbstractCommandTestCase.php index 5bf2000ed6..5e8d4ff7ad 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Tools/Console/Command/AbstractCommandTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Tools/Console/Command/AbstractCommandTestCase.php @@ -11,8 +11,7 @@ abstract class AbstractCommandTestCase extends BaseTestCase { - /** @var Application */ - protected $application; + protected ?Application $application; public function setUp(): void { @@ -32,6 +31,6 @@ public function tearDown(): void { parent::tearDown(); - unset($this->application); + $this->application = null; } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Tools/Console/Command/Schema/UpdateCommandTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Tools/Console/Command/Schema/UpdateCommandTest.php index c6202bc1b8..1f6c1fd270 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Tools/Console/Command/Schema/UpdateCommandTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Tools/Console/Command/Schema/UpdateCommandTest.php @@ -4,20 +4,23 @@ namespace Doctrine\ODM\MongoDB\Tests\Tools\Console\Command\Schema; -use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver; +use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver; use Doctrine\ODM\MongoDB\Tests\Tools\Console\Command\AbstractCommandTestCase; use Doctrine\ODM\MongoDB\Tools\Console\Command\Schema\UpdateCommand; +use Doctrine\Persistence\Mapping\Driver\ClassNames; +use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use Documents\Ecommerce; use Documents\SchemaValidated; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Tester\CommandTester; +use function class_exists; + class UpdateCommandTest extends AbstractCommandTestCase { - /** @var Command */ - protected $command; + protected ?Command $command; - /** @var CommandTester */ - protected $commandTester; + protected ?CommandTester $commandTester; public function setUp(): void { @@ -39,8 +42,8 @@ public function tearDown(): void { parent::tearDown(); - unset($this->command); - unset($this->commandTester); + $this->command = null; + $this->commandTester = null; } public function testProcessValidator(): void @@ -69,7 +72,7 @@ public function testDisabledValidatorProcessing(): void public function testProcessValidators(): void { // Only load a subset of documents with legit annotations - $annotationDriver = AnnotationDriver::create(__DIR__ . '/../../../../../../../../Documents/Ecommerce'); + $annotationDriver = $this->createDriver(); $this->dm->getConfiguration()->setMetadataDriverImpl($annotationDriver); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); @@ -79,10 +82,29 @@ public function testProcessValidators(): void public function testDisabledValidatorsProcessing(): void { // Only load a subset of documents with legit annotations - $annotationDriver = AnnotationDriver::create(__DIR__ . '/../../../../../../../../Documents/Ecommerce'); + $annotationDriver = $this->createDriver(); $this->dm->getConfiguration()->setMetadataDriverImpl($annotationDriver); $this->commandTester->execute(['--disable-validators' => true]); $output = $this->commandTester->getDisplay(); self::assertStringNotContainsString('Updated validation for all classes', $output); } + + private function createDriver(): MappingDriver + { + $paths = [__DIR__ . '/../../../../../../../../Documents/Ecommerce']; + // Available in Doctrine Persistence 4.1+ + if (class_exists(ClassNames::class)) { + $paths = new ClassNames([ + Ecommerce\Basket::class, + Ecommerce\ConfigurableProduct::class, + Ecommerce\Currency::class, + Ecommerce\Money::class, + Ecommerce\Option::class, + Ecommerce\Order::class, + Ecommerce\StockItem::class, + ]); + } + + return AttributeDriver::create($paths); + } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php new file mode 100644 index 0000000000..7157776c65 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php @@ -0,0 +1,274 @@ +dm->getMetadataFactory()); + $encryptedFieldsMap = $factory->getEncryptedFieldsForClass(Patient::class); + + $expected = [ + [ + 'path' => 'patientRecord.ssn', + 'bsonType' => 'string', + 'keyId' => null, + 'queries' => ['queryType' => 'equality'], + ], + [ + 'path' => 'patientRecord.billing', + 'bsonType' => 'object', + 'keyId' => null, + ], + [ + 'path' => 'patientRecord.billingAmount', + 'bsonType' => 'int', + 'keyId' => null, + 'queries' => ['queryType' => 'range', 'min' => 100, 'max' => 2000, 'sparsity' => 1, 'trimFactor' => 4], + ], + ]; + + self::assertEquals(['fields' => $expected], $encryptedFieldsMap); + } + + public function testGetEncryptionFieldsForClassWithEmbeddedDocument(): void + { + $factory = new EncryptedFieldsMapGenerator($this->dm->getMetadataFactory()); + $encryptedFieldsMap = $factory->getEncryptedFieldsForClass(Client::class); + + $expected = [ + [ + 'path' => 'name', + 'bsonType' => 'string', + 'keyId' => null, + ], + [ + 'path' => 'clientCards', + 'bsonType' => 'array', + 'keyId' => null, + ], + ]; + + self::assertEquals(['fields' => $expected], $encryptedFieldsMap); + } + + public function testVariousRangeTypes(): void + { + $factory = new EncryptedFieldsMapGenerator($this->dm->getMetadataFactory()); + $encryptedFieldsMap = $factory->getEncryptedFieldsForClass(RangeTypes::class); + + $expected = [ + [ + 'path' => 'intField', + 'bsonType' => 'int', + 'keyId' => null, + 'queries' => ['queryType' => 'range', 'min' => 5, 'max' => 10], + ], + [ + 'path' => 'int64Field', + 'bsonType' => 'long', + 'keyId' => null, + 'queries' => [ + 'queryType' => 'range', + 'min' => new Int64(5), + 'max' => new Int64(PHP_INT_MAX - 5), + ], + ], + [ + 'path' => 'floatField', + 'bsonType' => 'double', + 'keyId' => null, + 'queries' => ['queryType' => 'range', 'min' => 5.5, 'max' => 10.5, 'precision' => 1], + ], + [ + 'path' => 'decimalField', + 'bsonType' => 'decimal', + 'keyId' => null, + 'queries' => ['queryType' => 'range', 'min' => new Decimal128('0.1'), 'max' => new Decimal128('0.2'), 'precision' => 2], + ], + [ + 'path' => 'dateField', + 'bsonType' => 'date', + 'keyId' => null, + 'queries' => [ + 'queryType' => 'range', + 'min' => new UTCDateTime(new DateTimeImmutable('2000-01-01 00:00:00')), + 'max' => new UTCDateTime(new DateTimeImmutable('2100-01-01 00:00:00')), + 'sparsity' => 1, + 'trimFactor' => 3, + 'contention' => 4, + ], + ], + ]; + + self::assertEquals(['fields' => $expected], $encryptedFieldsMap); + } + + public function testRootDocumentsCannotBeEncrypted(): void + { + $this->expectException(MappingException::class); + $this->expectExceptionMessage('The root document class "Documents\Encryption\InvalidRootEncrypt" cannot be encrypted. Only fields and embedded documents can be encrypted.'); + + $factory = new EncryptedFieldsMapGenerator($this->dm->getMetadataFactory()); + $factory->getEncryptedFieldsForClass(InvalidRootEncrypt::class); + } + + public function testGetEncryptionFieldsMap(): void + { + $classMetadataFactory = $this->createMetadataFactory( + $this->dm->getMetadataFactory(), + Patient::class, + PatientRecord::class, + ); + + $factory = new EncryptedFieldsMapGenerator($classMetadataFactory); + $encryptedFieldsMap = $factory->getEncryptedFieldsMap(); + + $expectedEncryptedFieldsMap = [ + Patient::class => [ + 'fields' => [ + [ + 'path' => 'patientRecord.ssn', + 'bsonType' => 'string', + 'keyId' => null, + 'queries' => ['queryType' => 'equality'], + ], + [ + 'path' => 'patientRecord.billing', + 'bsonType' => 'object', + 'keyId' => null, + ], + [ + 'path' => 'patientRecord.billingAmount', + 'bsonType' => 'int', + 'keyId' => null, + 'queries' => ['queryType' => 'range', 'min' => 100, 'max' => 2000, 'sparsity' => 1, 'trimFactor' => 4], + ], + ], + ], + ]; + + $this->assertEquals($expectedEncryptedFieldsMap, $encryptedFieldsMap); + } + + public function testNoEncryptedFields(): void + { + $classMetadataFactory = $this->createMetadataFactory( + $this->dm->getMetadataFactory(), + Bar::class, + ); + + $factory = new EncryptedFieldsMapGenerator($classMetadataFactory); + + self::assertSame([], $factory->getEncryptedFieldsMap()); + self::assertNull($factory->getEncryptedFieldsForClass(Bar::class)); + } + + public function testNotADocumentClass(): void + { + $classMetadataFactory = $this->createMetadataFactory( + $this->dm->getMetadataFactory(), + PatientRecord::class, + ); + + $this->expectException(MongoDBException::class); + $this->expectExceptionMessage('The class "Documents\Encryption\PatientRecord" is not a document class.'); + + $factory = new EncryptedFieldsMapGenerator($classMetadataFactory); + $factory->getEncryptedFieldsForClass(PatientRecord::class); + } + + private function createMetadataFactory(ClassMetadataFactoryInterface $classMetadataFactory, string ...$className): ClassMetadataFactoryInterface + { + return new class ($classMetadataFactory, $className) extends AbstractClassMetadataFactory implements ClassMetadataFactoryInterface + { + public function __construct(private ClassMetadataFactoryInterface $classMetadataFactory, private array $classNames) + { + } + + public function getAllMetadata(): array + { + return array_map( + $this->classMetadataFactory->getMetadataFor(...), + $this->classNames, + ); + } + + public function getMetadataFor(string $className): ClassMetadata + { + return $this->classMetadataFactory->getMetadataFor($className); + } + + protected function initialize(): void + { + } + + protected function getDriver(): MappingDriver + { + return $this->classMetadataFactory->getDriver(); + } + + protected function wakeupReflection(\Doctrine\Persistence\Mapping\ClassMetadata $class, ReflectionService $reflService): void + { + $this->classMetadataFactory->wakeupReflection($class, $reflService); + } + + protected function initializeReflection(\Doctrine\Persistence\Mapping\ClassMetadata $class, ReflectionService $reflService): void + { + $this->classMetadataFactory->initializeReflection($class, $reflService); + } + + protected function isEntity(\Doctrine\Persistence\Mapping\ClassMetadata $class): bool + { + return $this->classMetadataFactory->isEntity($class); + } + + protected function doLoadMetadata(\Doctrine\Persistence\Mapping\ClassMetadata $class, ?\Doctrine\Persistence\Mapping\ClassMetadata $parent, bool $rootEntityFound, array $nonSuperclassParents): void + { + $this->classMetadataFactory->doLoadMetadata($class, $parent, $rootEntityFound, $nonSuperclassParents); + } + + protected function newClassMetadataInstance(string $className): ClassMetadata + { + return $this->classMetadataFactory->newClassMetadataInstance($className); + } + + public function setConfiguration(Configuration $config): void + { + } + + public function setDocumentManager(DocumentManager $dm): void + { + } + }; + } +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php index 10bdf005db..4013e7e8b7 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php @@ -10,11 +10,15 @@ use Doctrine\ODM\MongoDB\Types\Type; use MongoDB\BSON\Binary; use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; +use MongoDB\BSON\MaxKey; +use MongoDB\BSON\MinKey; use MongoDB\BSON\ObjectId; use MongoDB\BSON\Timestamp; use MongoDB\BSON\UTCDateTime; use PHPUnit\Framework\Attributes\DataProvider; +use function get_debug_type; use function md5; use function str_pad; use function str_repeat; @@ -24,40 +28,44 @@ class TypeTest extends BaseTestCase { - /** @param mixed $test */ #[DataProvider('provideTypes')] - public function testConversion(Type $type, $test): void + public function testConversion(string $typeName, mixed $phpValue, mixed $bsonValue = null): void { - self::assertEquals($test, $type->convertToPHPValue($type->convertToDatabaseValue($test))); + $bsonValue ??= $phpValue; + $type = Type::getType($typeName); + + self::assertSameTypeAndValue($phpValue, $type->convertToPHPValue($bsonValue)); + self::assertSameTypeAndValue($bsonValue, $type->convertToDatabaseValue($phpValue)); } public static function provideTypes(): array { return [ - 'id' => [Type::getType(Type::ID), '507f1f77bcf86cd799439011'], - 'intId' => [Type::getType(Type::INTID), 1], - 'customId' => [Type::getType(Type::CUSTOMID), (object) ['foo' => 'bar']], - 'bool' => [Type::getType(Type::BOOL), true], - 'boolean' => [Type::getType(Type::BOOLEAN), false], - 'int' => [Type::getType(Type::INT), 69], - 'integer' => [Type::getType(Type::INTEGER), 42], - 'float' => [Type::getType(Type::FLOAT), 3.14], - 'string' => [Type::getType(Type::STRING), 'ohai'], - 'minKey' => [Type::getType(Type::KEY), 0], - 'maxKey' => [Type::getType(Type::KEY), 1], - 'timestamp' => [Type::getType(Type::TIMESTAMP), time()], - 'binData' => [Type::getType(Type::BINDATA), 'foobarbaz'], - 'binDataFunc' => [Type::getType(Type::BINDATAFUNC), 'foobarbaz'], - 'binDataByteArray' => [Type::getType(Type::BINDATABYTEARRAY), 'foobarbaz'], - 'binDataUuid' => [Type::getType(Type::BINDATAUUID), 'testtesttesttest'], - 'binDataUuidRFC4122' => [Type::getType(Type::BINDATAUUIDRFC4122), str_repeat('a', 16)], - 'binDataMD5' => [Type::getType(Type::BINDATAMD5), md5('ODM')], - 'binDataCustom' => [Type::getType(Type::BINDATACUSTOM), 'foobarbaz'], - 'hash' => [Type::getType(Type::HASH), ['foo' => 'bar']], - 'collection' => [Type::getType(Type::COLLECTION), ['foo', 'bar']], - 'objectId' => [Type::getType(Type::OBJECTID), '507f1f77bcf86cd799439011'], - 'raw' => [Type::getType(Type::RAW), (object) ['foo' => 'bar']], - 'decimal128' => [Type::getType(Type::DECIMAL128), '4.20'], + 'id' => [Type::ID, '507f1f77bcf86cd799439011', new ObjectId('507f1f77bcf86cd799439011')], + 'intId' => [Type::INTID, 1], + 'customId' => [Type::CUSTOMID, (object) ['foo' => 'bar']], + 'bool' => [Type::BOOL, true], + 'boolean' => [Type::BOOLEAN, false], + 'int' => [Type::INT, 69], + 'integer' => [Type::INTEGER, 42], + 'int64' => [Type::INT64, 100, new Int64(100)], + 'float' => [Type::FLOAT, 3.14], + 'string' => [Type::STRING, 'ohai'], + 'minKey' => [Type::KEY, 0, new MinKey()], + 'maxKey' => [Type::KEY, 1, new MaxKey()], + 'timestamp' => [Type::TIMESTAMP, $t = time(), new Timestamp(0, $t)], + 'binData' => [Type::BINDATA, 'foobarbaz', new Binary('foobarbaz', Binary::TYPE_GENERIC)], + 'binDataFunc' => [Type::BINDATAFUNC, 'foobarbaz', new Binary('foobarbaz', Binary::TYPE_FUNCTION)], + 'binDataByteArray' => [Type::BINDATABYTEARRAY, 'foobarbaz', new Binary('foobarbaz', Binary::TYPE_OLD_BINARY)], + 'binDataUuid' => [Type::BINDATAUUID, 'testtesttesttest', new Binary('testtesttesttest', Binary::TYPE_OLD_UUID)], + 'binDataUuidRFC4122' => [Type::BINDATAUUIDRFC4122, str_repeat('a', 16), new Binary(str_repeat('a', 16), Binary::TYPE_UUID)], + 'binDataMD5' => [Type::BINDATAMD5, md5('ODM'), new Binary(md5('ODM'), Binary::TYPE_MD5)], + 'binDataCustom' => [Type::BINDATACUSTOM, 'foobarbaz', new Binary('foobarbaz', Binary::TYPE_USER_DEFINED)], + 'hash' => [Type::HASH, ['foo' => 'bar'], (object) ['foo' => 'bar']], + 'collection' => [Type::COLLECTION, ['foo', 'bar']], + 'objectId' => [Type::OBJECTID, '507f1f77bcf86cd799439011', new ObjectId('507f1f77bcf86cd799439011')], + 'raw' => [Type::RAW, (object) ['foo' => 'bar']], + 'decimal128' => [Type::DECIMAL128, '4.20', new Decimal128('4.20')], ]; } @@ -65,7 +73,7 @@ public static function provideTypes(): array #[DataProvider('provideTypesForIdempotent')] public function testConversionIsIdempotent(Type $type, $test): void { - self::assertEquals($test, $type->convertToDatabaseValue($test)); + self::assertSameTypeAndValue($test, $type->convertToDatabaseValue($test)); } public static function provideTypesForIdempotent(): array @@ -74,6 +82,7 @@ public static function provideTypesForIdempotent(): array 'id' => [Type::getType(Type::ID), new ObjectId()], 'date' => [Type::getType(Type::DATE), new UTCDateTime()], 'dateImmutable' => [Type::getType(Type::DATE_IMMUTABLE), new UTCDateTime()], + 'int64' => [Type::getType(Type::INT64), new Int64(100)], 'timestamp' => [Type::getType(Type::TIMESTAMP), new Timestamp(0, time())], 'binData' => [Type::getType(Type::BINDATA), new Binary('foobarbaz', Binary::TYPE_GENERIC)], 'binDataFunc' => [Type::getType(Type::BINDATAFUNC), new Binary('foobarbaz', Binary::TYPE_FUNCTION)], @@ -116,4 +125,10 @@ public function testConvertImmutableDate(): void self::assertInstanceOf(UTCDateTime::class, Type::convertPHPToDatabaseValue($date)); } + + private static function assertSameTypeAndValue(mixed $expected, mixed $actual): void + { + self::assertSame(get_debug_type($expected), get_debug_type($actual)); + self::assertEquals($expected, $actual); + } } diff --git a/tests/Documentation/Introduction/Manager.php b/tests/Documentation/Introduction/Manager.php index 4517998583..e6ec3c9cd4 100644 --- a/tests/Documentation/Introduction/Manager.php +++ b/tests/Documentation/Introduction/Manager.php @@ -14,7 +14,7 @@ class Manager extends BaseEmployee #[ODM\Id] public string $id; - /** @var Collection */ + /** @var Collection */ #[ODM\ReferenceMany(targetDocument: Project::class)] public Collection $projects; diff --git a/tests/Documentation/LookupRelations/Order.php b/tests/Documentation/LookupRelations/Order.php index c7467ec1eb..d256677b05 100644 --- a/tests/Documentation/LookupRelations/Order.php +++ b/tests/Documentation/LookupRelations/Order.php @@ -22,7 +22,7 @@ class Order #[Field(type: 'date_immutable')] public DateTimeImmutable $date; - /** @var Collection */ + /** @var Collection */ #[ReferenceMany( targetDocument: Item::class, cascade: 'all', diff --git a/tests/Documentation/LookupRelations/OrderResult.php b/tests/Documentation/LookupRelations/OrderResult.php index 0e9a4040bf..174c99521c 100644 --- a/tests/Documentation/LookupRelations/OrderResult.php +++ b/tests/Documentation/LookupRelations/OrderResult.php @@ -21,7 +21,7 @@ class OrderResult #[Field(type: 'date_immutable')] public DateTimeImmutable $date; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Item::class)] public Collection $items; diff --git a/tests/Documentation/LookupRelations/UserOrderResult.php b/tests/Documentation/LookupRelations/UserOrderResult.php index 63154a5297..b46fd6bec6 100644 --- a/tests/Documentation/LookupRelations/UserOrderResult.php +++ b/tests/Documentation/LookupRelations/UserOrderResult.php @@ -20,7 +20,7 @@ class UserOrderResult #[Field(type: 'date_immutable')] public DateTimeImmutable $date; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: Item::class)] public Collection $items; } diff --git a/tests/Documentation/LookupRelations/UserResult.php b/tests/Documentation/LookupRelations/UserResult.php index 279b58571f..312a7ed1e6 100644 --- a/tests/Documentation/LookupRelations/UserResult.php +++ b/tests/Documentation/LookupRelations/UserResult.php @@ -19,7 +19,7 @@ class UserResult #[Field(type: 'string')] public string $name; - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: UserOrderResult::class)] public Collection $orders; } diff --git a/tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php b/tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php index 5002d17eb5..ca82c0a1e5 100644 --- a/tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php +++ b/tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php @@ -6,6 +6,7 @@ use Doctrine\ODM\MongoDB\Repository\DocumentRepository; +/** @extends DocumentRepository */ final class OdmBlogPostRepository extends DocumentRepository implements BlogPostRepositoryInterface { public function findPostById(int $id): ?BlogPost diff --git a/tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php b/tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php index 58238b6822..626441f6ba 100644 --- a/tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php +++ b/tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php @@ -6,6 +6,7 @@ use Doctrine\ORM\EntityRepository; +/** @extends EntityRepository */ final class OrmBlogPostRepository extends EntityRepository implements BlogPostRepositoryInterface { public function findPostById(int $id): ?BlogPost diff --git a/tests/Documentation/Validation/Order.php b/tests/Documentation/Validation/Order.php index aff4c0eb95..2defa3e115 100644 --- a/tests/Documentation/Validation/Order.php +++ b/tests/Documentation/Validation/Order.php @@ -23,7 +23,7 @@ class Order public function __construct( #[ReferenceOne(targetDocument: Customer::class)] public Customer $customer, - /** @var Collection */ + /** @var Collection */ #[EmbedMany(targetDocument: OrderLine::class)] public Collection $orderLines = new ArrayCollection(), ) { diff --git a/tests/Documents/Encryption/Client.php b/tests/Documents/Encryption/Client.php new file mode 100644 index 0000000000..8c1be9eff9 --- /dev/null +++ b/tests/Documents/Encryption/Client.php @@ -0,0 +1,25 @@ +profileId = $profileId; + $this->profileId = (string) $profileId; } - /** @return ObjectId|string|null */ + /** @return string|null */ public function getProfileId() { return $this->profileId;