From 1b4905e698f64de491ff684308705be72e73add5 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Sat, 21 Sep 2024 20:42:38 +0200 Subject: [PATCH 01/28] Update supported branch config (#2678) --- .doctrine-project.json | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.doctrine-project.json b/.doctrine-project.json index c102e96d9..61c211eec 100644 --- a/.doctrine-project.json +++ b/.doctrine-project.json @@ -6,11 +6,22 @@ "docsSlug": "doctrine-mongodb-odm", "versions": [ { - "name": "2.9", - "branchName": "2.9.x", + "name": "2.10", + "branchName": "2.10.x", "slug": "latest", "upcoming": true, "aliases": [ + "2.10.x" + ] + }, + { + "name": "2.9", + "branchName": "2.9.x", + "slug": "2.9", + "current": true, + "aliases": [ + "current", + "stable", "2.9.x" ] }, @@ -18,10 +29,8 @@ "name": "2.8", "branchName": "2.8.x", "slug": "2.8", - "current": true, + "maintained": false, "aliases": [ - "current", - "stable", "2.8.x" ] }, From 6af86983ea46acee480eb9d80372c5e9402f1617 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 1 Oct 2024 10:07:00 +0200 Subject: [PATCH 02/28] Fix CI job (#2681) * Skip sharded setup due to errors when running queries * Skip upload of composer.lock --- .github/workflows/continuous-integration.yml | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index d2e179e36..cf9b10606 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -56,12 +56,13 @@ jobs: dependencies: "highest" symfony-version: "stable" # Test with a 5.0 sharded cluster - - topology: "sharded_cluster" - php-version: "8.2" - mongodb-version: "5.0" - driver-version: "stable" - dependencies: "highest" - symfony-version: "stable" + # Currently disabled due to a bug where MongoDB reports "sharding status unknown" +# - topology: "sharded_cluster" +# php-version: "8.2" +# mongodb-version: "5.0" +# driver-version: "stable" +# dependencies: "highest" +# symfony-version: "stable" steps: - name: "Checkout" @@ -117,13 +118,6 @@ jobs: dependency-versions: "${{ matrix.dependencies }}" composer-options: "--prefer-dist" - - name: "Upload composer.lock as build artifact" - uses: actions/upload-artifact@v4 - with: - name: "composer-lock-phpunit-${{ matrix.php-version }}-${{ matrix.dependencies }}-${{ matrix.mongodb-version }}" - path: composer.lock - overwrite: true - - id: setup-mongodb uses: mongodb-labs/drivers-evergreen-tools@master with: From db07f5399f6eaa3c34a89c2fff291f3dd1a81e92 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 1 Oct 2024 13:28:45 +0200 Subject: [PATCH 03/28] Remove usage of deprecated driver functionality (#2680) * Remove usages of deprecated BSON functions * Remove usages of deprecated read preference functionality * Update phpstan baseline --- .../MongoDB/Mapping/Driver/AttributeDriver.php | 7 +++---- .../ODM/MongoDB/Mapping/Driver/XmlDriver.php | 7 +++---- phpstan-baseline.neon | 4 ++-- .../Tests/Functional/ReadPreferenceTest.php | 18 +++++++++--------- .../Tests/Functional/ReferencePrimerTest.php | 2 +- .../Tests/Functional/ValidationTest.php | 15 ++++----------- .../Tests/Mapping/ClassMetadataTest.php | 7 +++---- .../Tests/Mapping/Driver/XmlDriverTest.php | 7 ++----- .../ODM/MongoDB/Tests/Query/BuilderTest.php | 2 +- .../ODM/MongoDB/Tests/SchemaManagerTest.php | 9 +++------ 10 files changed, 31 insertions(+), 47 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php index f0099c28e..6a48f5fc1 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php @@ -15,6 +15,7 @@ use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use MongoDB\BSON\Document; use MongoDB\Driver\Exception\UnexpectedValueException; use ReflectionClass; use ReflectionMethod; @@ -27,8 +28,6 @@ use function constant; use function count; use function is_array; -use function MongoDB\BSON\fromJSON; -use function MongoDB\BSON\toPHP; use function trigger_deprecation; /** @@ -133,12 +132,12 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad } elseif ($attribute instanceof ODM\Validation) { if (isset($attribute->validator)) { try { - $validatorBson = fromJSON($attribute->validator); + $validatorBson = Document::fromJSON($attribute->validator); } catch (UnexpectedValueException $e) { throw MappingException::schemaValidationError($e->getCode(), $e->getMessage(), $className, 'validator'); } - $validator = toPHP($validatorBson, []); + $validator = $validatorBson->toPHP(); $metadata->setValidator($validator); } diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php index 6c288a2d9..64f32867c 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php @@ -11,6 +11,7 @@ use DOMDocument; use InvalidArgumentException; use LibXMLError; +use MongoDB\BSON\Document; use MongoDB\Driver\Exception\UnexpectedValueException; use SimpleXMLElement; @@ -31,8 +32,6 @@ use function libxml_clear_errors; use function libxml_get_errors; use function libxml_use_internal_errors; -use function MongoDB\BSON\fromJSON; -use function MongoDB\BSON\toPHP; use function next; use function preg_match; use function simplexml_load_file; @@ -215,12 +214,12 @@ public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\C $validatorJson = (string) $xmlSchemaValidation; try { - $validatorBson = fromJSON($validatorJson); + $validatorBson = Document::fromJSON($validatorJson); } catch (UnexpectedValueException $e) { throw MappingException::schemaValidationError($e->getCode(), $e->getMessage(), $className, 'schema-validation'); } - $validator = toPHP($validatorBson, []); + $validator = $validatorBson->toPHP(); $metadata->setValidator($validator); } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ae58dfd95..6e6aca3be 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -956,12 +956,12 @@ parameters: path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataLoadEventTest.php - - message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents\\\\\\\\Card'\\} given\\.$#" + message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents\\\\\\\\Card'\\} given\\.$#" count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php - - message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents\\\\\\\\SuitNonBacked'\\} given\\.$#" + message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents\\\\\\\\SuitNonBacked'\\} given\\.$#" count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php index 8135cce28..1ad64db3a 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php @@ -46,7 +46,7 @@ public function testHintIsNotSetByDefault(): void /** @psalm-param ReadPreferenceTagShape[] $tags */ #[DataProvider('provideReadPreferenceHints')] - public function testHintIsSetOnQuery(int $readPreference, array $tags = []): void + public function testHintIsSetOnQuery(string $readPreference, array $tags = []): void { $this->skipTestIfSharded(User::class); @@ -68,9 +68,9 @@ public function testHintIsSetOnQuery(int $readPreference, array $tags = []): voi public static function provideReadPreferenceHints(): array { return [ - [ReadPreference::RP_PRIMARY, []], - [ReadPreference::RP_SECONDARY_PREFERRED, []], - [ReadPreference::RP_SECONDARY, [['dc' => 'east'], []]], + [ReadPreference::PRIMARY, []], + [ReadPreference::SECONDARY_PREFERRED, []], + [ReadPreference::SECONDARY, [['dc' => 'east'], []]], ]; } @@ -78,7 +78,7 @@ public function testDocumentLevelReadPreferenceIsSetInCollection(): void { $coll = $this->dm->getDocumentCollection(DocumentWithReadPreference::class); - self::assertSame(ReadPreference::RP_NEAREST, $coll->getReadPreference()->getMode()); + self::assertSame(ReadPreference::NEAREST, $coll->getReadPreference()->getModeString()); self::assertSame([['dc' => 'east']], $coll->getReadPreference()->getTagSets()); } @@ -88,7 +88,7 @@ public function testDocumentLevelReadPreferenceIsAppliedInQueryBuilder(): void ->createQueryBuilder() ->getQuery(); - $this->assertReadPreferenceHint(ReadPreference::RP_NEAREST, $query->getQuery()['readPreference'], [['dc' => 'east']]); + $this->assertReadPreferenceHint(ReadPreference::NEAREST, $query->getQuery()['readPreference'], [['dc' => 'east']]); } public function testDocumentLevelReadPreferenceCanBeOverriddenInQueryBuilder(): void @@ -98,14 +98,14 @@ public function testDocumentLevelReadPreferenceCanBeOverriddenInQueryBuilder(): ->setReadPreference(new ReadPreference('secondary', [])) ->getQuery(); - $this->assertReadPreferenceHint(ReadPreference::RP_SECONDARY, $query->getQuery()['readPreference']); + $this->assertReadPreferenceHint(ReadPreference::SECONDARY, $query->getQuery()['readPreference']); } /** @psalm-param ReadPreferenceTagShape[] $tags */ - private function assertReadPreferenceHint(int $mode, ReadPreference $readPreference, array $tags = []): void + private function assertReadPreferenceHint(string $mode, ReadPreference $readPreference, array $tags = []): void { self::assertInstanceOf(ReadPreference::class, $readPreference); - self::assertEquals($mode, $readPreference->getMode()); + self::assertEquals($mode, $readPreference->getModeString()); self::assertEquals($tags, $readPreference->getTagSets()); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php index 44c2c5c40..00468ea54 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php @@ -391,7 +391,7 @@ public function testPrimeReferencesInvokesPrimer(): void // Note: using a secondary read preference here can cause issues when using transactions // Using a primaryPreferred works just as well to check if the hint is passed on to the primer - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY_PREFERRED); + $readPreference = new ReadPreference(ReadPreference::PRIMARY_PREFERRED); $this->dm->createQueryBuilder(User::class) ->field('account')->prime($primer) ->field('groups')->prime($primer) diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ValidationTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ValidationTest.php index 042afd476..60f71fd1c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ValidationTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ValidationTest.php @@ -8,11 +8,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\SchemaValidated; - -use function MongoDB\BSON\fromJSON; -use function MongoDB\BSON\fromPHP; -use function MongoDB\BSON\toCanonicalExtendedJSON; -use function MongoDB\BSON\toPHP; +use MongoDB\BSON\Document; class ValidationTest extends BaseTestCase { @@ -41,15 +37,13 @@ public function testCreateUpdateValidatedDocument(): void ] } EOT; - $expectedValidatorBson = fromJSON($expectedValidatorJson); - $expectedValidator = toPHP($expectedValidatorBson, []); + $expectedValidator = Document::fromJSON($expectedValidatorJson)->toPHP(); $expectedOptions = [ 'validator' => $expectedValidator, 'validationLevel' => ClassMetadata::SCHEMA_VALIDATION_LEVEL_MODERATE, 'validationAction' => ClassMetadata::SCHEMA_VALIDATION_ACTION_WARN, ]; - $expectedOptionsBson = fromPHP($expectedOptions); - $expectedOptionsJson = toCanonicalExtendedJSON($expectedOptionsBson); + $expectedOptionsJson = Document::fromPHP($expectedOptions)->toCanonicalExtendedJSON(); $collections = $this->dm->getDocumentDatabase($cm->name)->listCollections(); $assertNb = 0; foreach ($collections as $collection) { @@ -58,8 +52,7 @@ public function testCreateUpdateValidatedDocument(): void } $assertNb++; - $collectionOptionsBson = fromPHP($collection->getOptions()); - $collectionOptionsJson = toCanonicalExtendedJSON($collectionOptionsBson); + $collectionOptionsJson = Document::fromPHP($collection->getOptions())->toCanonicalExtendedJSON(); self::assertJsonStringEqualsJsonString($expectedOptionsJson, $collectionOptionsJson); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php index adf80fb6a..976b83a1c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php @@ -35,6 +35,7 @@ use Documents\UserTyped; use Generator; use InvalidArgumentException; +use MongoDB\BSON\Document; use PHPUnit\Framework\Attributes\DataProvider; use ProxyManager\Proxy\GhostObjectInterface; use ReflectionClass; @@ -42,8 +43,6 @@ use stdClass; use function array_merge; -use function MongoDB\BSON\fromJSON; -use function MongoDB\BSON\toPHP; use function serialize; use function unserialize; @@ -78,7 +77,7 @@ public function testClassMetadataInstanceSerialization(): void $cm->setVersioned(true); $cm->setVersionField('version'); $validatorJson = '{ "$and": [ { "email": { "$regularExpression" : { "pattern": "@mongodb\\\\.com$", "options": "" } } } ] }'; - $cm->setValidator(toPHP(fromJSON($validatorJson))); + $cm->setValidator(Document::fromJSON($validatorJson)->toPHP()); $cm->setValidationAction(ClassMetadata::SCHEMA_VALIDATION_ACTION_WARN); $cm->setValidationLevel(ClassMetadata::SCHEMA_VALIDATION_LEVEL_OFF); self::assertIsArray($cm->getFieldMapping('phonenumbers')); @@ -110,7 +109,7 @@ public function testClassMetadataInstanceSerialization(): void self::assertEquals('lock', $cm->lockField); self::assertEquals(true, $cm->isVersioned); self::assertEquals('version', $cm->versionField); - self::assertEquals(toPHP(fromJSON($validatorJson)), $cm->getValidator()); + self::assertEquals(Document::fromJSON($validatorJson)->toPHP(), $cm->getValidator()); self::assertEquals(ClassMetadata::SCHEMA_VALIDATION_ACTION_WARN, $cm->getValidationAction()); self::assertEquals(ClassMetadata::SCHEMA_VALIDATION_LEVEL_OFF, $cm->getValidationLevel()); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/XmlDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/XmlDriverTest.php index 988414384..8280a1df1 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/XmlDriverTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/XmlDriverTest.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\Driver\XmlDriver; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use MongoDB\BSON\Document; use TestDocuments\AlsoLoadDocument; use TestDocuments\CustomIdGenerator; use TestDocuments\InvalidPartialFilterDocument; @@ -16,9 +17,6 @@ use TestDocuments\UserNonStringOptions; use TestDocuments\WildcardIndexDocument; -use function MongoDB\BSON\fromJSON; -use function MongoDB\BSON\toPHP; - class XmlDriverTest extends AbstractDriverTestCase { public function setUp(): void @@ -137,8 +135,7 @@ public function testValidationMapping(): void ] } EOT; - $expectedValidatorBson = fromJSON($expectedValidatorJson); - $expectedValidator = toPHP($expectedValidatorBson, []); + $expectedValidator = Document::fromJSON($expectedValidatorJson)->toPHP(); self::assertEquals($expectedValidator, $classMetadata->getValidator()); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php index 77252d76d..0f7d67308 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php @@ -675,7 +675,7 @@ public function testSetReadPreference(): void $readPreference = $qb->debug('readPreference'); self::assertInstanceOf(ReadPreference::class, $readPreference); - self::assertEquals(ReadPreference::RP_SECONDARY, $readPreference->getMode()); + self::assertEquals(ReadPreference::SECONDARY, $readPreference->getModeString()); self::assertEquals([['dc' => 'east']], $readPreference->getTagSets()); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php index 83f4dd559..a8dc4e656 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php @@ -23,6 +23,7 @@ use Documents\Tournament\Tournament; use Documents\UserName; use InvalidArgumentException; +use MongoDB\BSON\Document; use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; @@ -43,8 +44,6 @@ use function array_map; use function assert; use function in_array; -use function MongoDB\BSON\fromJSON; -use function MongoDB\BSON\toPHP; /** * @psalm-import-type IndexMapping from ClassMetadata @@ -601,8 +600,7 @@ public function testUpdateDocumentValidator(array $expectedWriteOptions, ?int $m ] } EOT; - $expectedValidatorBson = fromJSON($expectedValidatorJson); - $expectedValidator = toPHP($expectedValidatorBson, []); + $expectedValidator = Document::fromJSON($expectedValidatorJson)->toPHP(); $database ->expects($this->once()) ->method('command') @@ -707,8 +705,7 @@ public function testCreateDocumentCollectionWithValidator(array $expectedWriteOp ] } EOT; - $expectedValidatorBson = fromJSON($expectedValidatorJson); - $expectedValidator = toPHP($expectedValidatorBson, []); + $expectedValidator = Document::fromJSON($expectedValidatorJson)->toPHP(); $options = [ 'capped' => false, 'size' => null, From ff90e9ab519560ec8a09794e9778dcd7d59f10ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:25:28 +0200 Subject: [PATCH 04/28] Bump doctrine/.github from 5.0.1 to 5.1.0 (#2679) Bumps [doctrine/.github](https://github.com/doctrine/.github) from 5.0.1 to 5.1.0. - [Release notes](https://github.com/doctrine/.github/releases) - [Commits](https://github.com/doctrine/.github/compare/5.0.1...5.1.0) --- updated-dependencies: - dependency-name: doctrine/.github dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coding-standards.yml | 2 +- .github/workflows/release-on-milestone-closed.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index c5dd57290..486a54896 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@5.0.1" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.1.0" diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index 3cac620a3..e193d87b6 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@5.0.1" + uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.1.0" secrets: GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} From 8ac8b3afe00c74856fe0c87539e97b332c60c2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 14 Oct 2024 13:22:04 +0200 Subject: [PATCH 05/28] Fix dropping search index (#2689) Error "Cannot use object of type stdClass as array" --- lib/Doctrine/ODM/MongoDB/SchemaManager.php | 8 ++++++-- psalm-baseline.xml | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/SchemaManager.php b/lib/Doctrine/ODM/MongoDB/SchemaManager.php index b142f1902..160be2328 100644 --- a/lib/Doctrine/ODM/MongoDB/SchemaManager.php +++ b/lib/Doctrine/ODM/MongoDB/SchemaManager.php @@ -413,7 +413,9 @@ public function updateDocumentSearchIndexes(string $documentName): void $definedNames = array_column($searchIndexes, 'name'); try { - $existingNames = array_column(iterator_to_array($collection->listSearchIndexes()), 'name'); + /* The typeMap option can be removed when bug is fixed in the minimum required version. + * https://jira.mongodb.org/browse/PHPLIB-1548 */ + $existingNames = array_column(iterator_to_array($collection->listSearchIndexes(['typeMap' => ['root' => 'array']])), 'name'); } catch (CommandException $e) { /* If $listSearchIndexes doesn't exist, only throw if search indexes have been defined. * If no search indexes are defined and the server doesn't support search indexes, there's @@ -465,7 +467,9 @@ public function deleteDocumentSearchIndexes(string $documentName): void $collection = $this->dm->getDocumentCollection($class->name); try { - $searchIndexes = $collection->listSearchIndexes(); + /* The typeMap option can be removed when bug is fixed in the minimum required version. + * https://jira.mongodb.org/browse/PHPLIB-1548 */ + $searchIndexes = $collection->listSearchIndexes(['typeMap' => ['root' => 'array']]); } catch (CommandException $e) { // If the server does not support search indexes, there are no indexes to remove in any case if ($this->isSearchIndexCommandException($e)) { diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 110172f14..221a7a95f 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -184,6 +184,12 @@ + + + ['root' => 'array']]]]> + ['root' => 'array']]]]> + + From fe0f1ffa32fcbef231208ca9252ca43b98927446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 14 Oct 2024 15:00:42 +0200 Subject: [PATCH 06/28] Convert `@psalm-*` annotations to generic or `@phpstan-*` annotation (#2685) --- .github/workflows/continuous-integration.yml | 2 - .github/workflows/static-analysis.yml | 33 -- .gitignore | 1 - composer.json | 3 +- .../ODM/MongoDB/Aggregation/Aggregation.php | 4 +- .../ODM/MongoDB/Aggregation/Builder.php | 18 +- lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php | 2 +- .../ODM/MongoDB/Aggregation/Stage.php | 10 +- .../MongoDB/Aggregation/Stage/AddFields.php | 4 +- .../ODM/MongoDB/Aggregation/Stage/Bucket.php | 1 - .../MongoDB/Aggregation/Stage/CollStats.php | 4 +- .../ODM/MongoDB/Aggregation/Stage/Count.php | 4 +- .../ODM/MongoDB/Aggregation/Stage/Densify.php | 12 +- .../ODM/MongoDB/Aggregation/Stage/Facet.php | 5 +- .../ODM/MongoDB/Aggregation/Stage/Fill.php | 14 +- .../MongoDB/Aggregation/Stage/Fill/Output.php | 4 +- .../MongoDB/Aggregation/Stage/GraphLookup.php | 6 +- .../ODM/MongoDB/Aggregation/Stage/Group.php | 4 +- .../MongoDB/Aggregation/Stage/IndexStats.php | 4 +- .../ODM/MongoDB/Aggregation/Stage/Limit.php | 4 +- .../ODM/MongoDB/Aggregation/Stage/Lookup.php | 14 +- .../ODM/MongoDB/Aggregation/Stage/Merge.php | 22 +- .../ODM/MongoDB/Aggregation/Stage/Out.php | 8 +- .../ODM/MongoDB/Aggregation/Stage/Project.php | 6 +- .../ODM/MongoDB/Aggregation/Stage/Redact.php | 4 +- .../MongoDB/Aggregation/Stage/ReplaceRoot.php | 4 +- .../MongoDB/Aggregation/Stage/ReplaceWith.php | 4 +- .../ODM/MongoDB/Aggregation/Stage/Sample.php | 4 +- .../ODM/MongoDB/Aggregation/Stage/Search.php | 20 +- .../Stage/Search/AbstractSearchOperator.php | 17 +- .../ODM/MongoDB/Aggregation/Stage/Set.php | 6 +- .../Aggregation/Stage/SetWindowFields.php | 16 +- .../Stage/SetWindowFields/Output.php | 20 +- .../ODM/MongoDB/Aggregation/Stage/Skip.php | 2 +- .../ODM/MongoDB/Aggregation/Stage/Sort.php | 16 +- .../MongoDB/Aggregation/Stage/SortByCount.php | 4 +- .../MongoDB/Aggregation/Stage/UnionWith.php | 12 +- .../MongoDB/Aggregation/Stage/UnsetStage.php | 2 +- .../ODM/MongoDB/Aggregation/Stage/Unwind.php | 4 +- lib/Doctrine/ODM/MongoDB/Configuration.php | 32 +- lib/Doctrine/ODM/MongoDB/DocumentManager.php | 54 +- .../OnClassMetadataNotFoundEventArgs.php | 4 +- .../ODM/MongoDB/Event/PreUpdateEventArgs.php | 4 +- .../ODM/MongoDB/Hydrator/HydratorFactory.php | 8 +- .../MongoDB/Hydrator/HydratorInterface.php | 4 +- .../MongoDB/Iterator/HydratingIterator.php | 4 +- .../ODM/MongoDB/Iterator/Iterator.php | 2 +- .../ODM/MongoDB/Iterator/PrimingIterator.php | 4 +- .../Mapping/Annotations/SearchIndex.php | 4 +- .../ODM/MongoDB/Mapping/ClassMetadata.php | 153 +++--- .../ODM/MongoDB/Mapping/Driver/XmlDriver.php | 6 +- .../PersistentCollectionFactory.php | 8 +- .../PersistentCollectionInterface.php | 10 +- .../PersistentCollectionTrait.php | 32 +- .../MongoDB/Persisters/DocumentPersister.php | 48 +- .../MongoDB/Persisters/PersistenceBuilder.php | 8 +- .../MongoDB/Proxy/Factory/ProxyFactory.php | 4 +- .../Proxy/Factory/StaticProxyFactory.php | 6 +- .../Proxy/Resolver/ClassNameResolver.php | 2 +- .../ProxyManagerClassNameResolver.php | 6 +- lib/Doctrine/ODM/MongoDB/Query/Builder.php | 15 +- lib/Doctrine/ODM/MongoDB/Query/Expr.php | 2 +- lib/Doctrine/ODM/MongoDB/Query/Query.php | 10 +- .../MongoDB/Query/QueryExpressionVisitor.php | 2 +- .../ODM/MongoDB/Query/ReferencePrimer.php | 10 +- .../Repository/AbstractRepositoryFactory.php | 10 +- .../Repository/DefaultGridFSRepository.php | 2 +- .../MongoDB/Repository/DocumentRepository.php | 25 +- .../MongoDB/Repository/RepositoryFactory.php | 4 +- lib/Doctrine/ODM/MongoDB/SchemaManager.php | 58 +-- .../Command/Schema/AbstractCommand.php | 2 +- .../Console/Command/Schema/ShardCommand.php | 2 +- .../Tools/ResolveTargetDocumentListener.php | 10 +- lib/Doctrine/ODM/MongoDB/Types/Type.php | 11 +- lib/Doctrine/ODM/MongoDB/UnitOfWork.php | 182 ++++--- .../MongoDB/Utility/LifecycleEventManager.php | 14 +- psalm-baseline.xml | 473 ------------------ psalm.xml.dist | 41 -- .../Tests/Aggregation/Stage/FacetTest.php | 1 - .../Tests/Aggregation/Stage/SortTest.php | 6 +- .../ODM/MongoDB/Tests/BaseTestCase.php | 4 +- .../MongoDB/Tests/ClassMetadataTestUtil.php | 8 +- .../Tests/Events/LifecycleListenersTest.php | 2 +- .../Functional/DocumentPersisterTest.php | 12 +- .../Tests/Functional/ReadPreferenceTest.php | 6 +- .../Tests/Functional/Ticket/GH1152Test.php | 4 +- .../Tests/Mapping/ClassMetadataTest.php | 3 + .../Doctrine/ODM/MongoDB/Tests/QueryTest.php | 3 +- .../DefaultGridFSRepositoryTest.php | 1 - .../ODM/MongoDB/Tests/SchemaManagerTest.php | 58 +-- .../ODM/MongoDB/Tests/UnitOfWorkTest.php | 3 +- 91 files changed, 556 insertions(+), 1155 deletions(-) delete mode 100644 psalm-baseline.xml delete mode 100644 psalm.xml.dist diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index cf9b10606..bc54f2b01 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -105,8 +105,6 @@ jobs: if: "${{ matrix.symfony-version == '7' }}" run: | composer config minimum-stability dev - # not yet ready for v7 - composer remove --no-update --dev vimeo/psalm # update symfony deps composer require --no-update symfony/console:^7@dev composer require --no-update symfony/var-dumper:^7@dev diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 5cd5ac4e3..bcd44649c 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -59,36 +59,3 @@ jobs: - name: "Run a static analysis with phpstan/phpstan" run: "vendor/bin/phpstan analyse --error-format=github" - - static-analysis-psalm: - name: "Static Analysis with Psalm" - runs-on: "ubuntu-22.04" - - strategy: - matrix: - php-version: - - "8.2" - - steps: - - name: "Checkout code" - uses: "actions/checkout@v4" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - coverage: "none" - extensions: "mongodb" - php-version: "${{ matrix.php-version }}" - - - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@v3" - - - name: "Upload composer.lock as build artifact" - uses: actions/upload-artifact@v4 - with: - name: "composer-lock-static-analysis-psalm" - path: composer.lock - overwrite: true - - - name: "Run a static analysis with vimeo/psalm" - run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=$(nproc)" diff --git a/.gitignore b/.gitignore index ef4553f5d..68a397161 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,3 @@ vendor/ .phpunit.cache .phpunit.result.cache phpcs.xml -psalm.xml diff --git a/composer.json b/composer.json index fdcf7c9a2..1bdaf6c08 100644 --- a/composer.json +++ b/composer.json @@ -47,8 +47,7 @@ "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^10.4", "squizlabs/php_codesniffer": "^3.5", - "symfony/cache": "^5.4 || ^6.0 || ^7.0", - "vimeo/psalm": "~5.24.0" + "symfony/cache": "^5.4 || ^6.0 || ^7.0" }, "conflict": { "doctrine/annotations": "<1.12 || >=3.0" diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php index 06a5fc90c..cf722aa86 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php @@ -18,13 +18,13 @@ use function array_merge; use function assert; -/** @psalm-import-type PipelineExpression from Builder */ +/** @phpstan-import-type PipelineExpression from Builder */ final class Aggregation implements IteratorAggregate { /** * @param array $pipeline * @param array $options - * @psalm-param PipelineExpression $pipeline + * @phpstan-param PipelineExpression $pipeline */ public function __construct(private DocumentManager $dm, private ?ClassMetadata $classMetadata, private Collection $collection, private array $pipeline, private array $options = [], private bool $rewindable = true) { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 4631ae633..8ec6ad5cb 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -28,9 +28,9 @@ /** * Fluent interface for building aggregation pipelines. * - * @psalm-import-type SortShape from Sort - * @psalm-import-type StageExpression from Stage - * @psalm-type PipelineExpression = list + * @phpstan-import-type SortShape from Sort + * @phpstan-type StageExpression array + * @phpstan-type PipelineExpression list */ class Builder { @@ -44,7 +44,7 @@ class Builder */ private ClassMetadata $class; - /** @psalm-var class-string */ + /** @var class-string */ private ?string $hydrationClass = null; /** @@ -60,7 +60,7 @@ class Builder /** * Create a new aggregation builder. * - * @psalm-param class-string $documentName + * @param class-string $documentName */ public function __construct(DocumentManager $dm, string $documentName) { @@ -270,8 +270,8 @@ public function getAggregation(array $options = []): Aggregation * you should not apply filters as this may cause wrong results to be * given. * - * @return array> - * @psalm-return PipelineExpression + * @return list> + * @phpstan-return PipelineExpression */ // phpcs:enable Squiz.Commenting.FunctionComment.ExtraParamComment public function getPipeline(/* bool $applyFilters = true */): array @@ -609,7 +609,7 @@ public function skip(int $skip): Stage\Skip * * @param array>|string $fieldName Field name or array of field/order pairs * @param int|string|null $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName Field name or array of field/order pairs + * @phpstan-param SortShape|string $fieldName Field name or array of field/order pairs */ public function sort($fieldName, $order = null): Stage\Sort { @@ -699,7 +699,7 @@ public function addStage(Stage $stage): Stage */ private function applyFilters(array $query): array { - $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name); + $documentPersister = $this->getDocumentPersister(); $query = $documentPersister->addDiscriminatorToPreparedQuery($query); $query = $documentPersister->addFilterToPreparedQuery($query); diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php index 732c583c1..fe84d3107 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Expr.php @@ -45,7 +45,7 @@ /** * Fluent interface for building aggregation pipelines. * - * @psalm-type OperatorExpression = array|object + * @phpstan-type OperatorExpression array|object */ class Expr implements AccumulatorOperators, diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php index 15395cf52..9de942e1b 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage.php @@ -14,8 +14,8 @@ * * @internal * - * @psalm-import-type PipelineExpression from Builder - * @psalm-type StageExpression = array + * @phpstan-import-type PipelineExpression from Builder + * @phpstan-import-type StageExpression from Builder */ abstract class Stage { @@ -31,7 +31,7 @@ public function __construct(Builder $builder) * Assembles the aggregation stage * * @return array - * @psalm-return StageExpression + * @phpstan-return StageExpression */ abstract public function getExpression(): array; @@ -189,8 +189,8 @@ public function geoNear($x, $y = null): Stage\GeoNear /** * Returns the assembled aggregation pipeline * - * @return array> - * @psalm-return PipelineExpression + * @return list> + * @phpstan-return PipelineExpression */ public function getPipeline(): array { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php index b658656bb..3315c9ee4 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php @@ -9,8 +9,8 @@ /** * Fluent interface for adding a $addFields stage to an aggregation pipeline. * - * @psalm-import-type OperatorExpression from Expr - * @psalm-type AddFieldsStageExpression = array{'$addFields': array} + * @phpstan-import-type OperatorExpression from Expr + * @phpstan-type AddFieldsStageExpression array{'$addFields': array} */ final class AddFields extends Operator { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket.php index 67e9c80d3..47328371f 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Bucket.php @@ -63,7 +63,6 @@ public function output(): Bucket\BucketOutput /** @return array{boundaries: mixed[], default: mixed} */ protected function getExtraPipelineFields(): array { - /** @psalm-suppress RedundantPropertyInitializationCheck because the property might not be set yet */ $fields = ['boundaries' => $this->boundaries ?? null]; if ($this->default !== null) { $fields['default'] = $this->default; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/CollStats.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/CollStats.php index 688e40c61..881c06721 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/CollStats.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/CollStats.php @@ -10,7 +10,7 @@ /** * Fluent interface for adding a $collStats stage to an aggregation pipeline. * - * @psalm-type CollStatsStageExpression = array{ + * @phpstan-type CollStatsStageExpression array{ * '$collStats': array{ * latencyStats?: array{histograms?: bool}, * storageStats?: array{}, @@ -52,7 +52,7 @@ public function showStorageStats(): static return $this; } - /** @psalm-return CollStatsStageExpression */ + /** @phpstan-return CollStatsStageExpression */ public function getExpression(): array { $collStats = []; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Count.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Count.php index 7a9e55e3f..008e6a52f 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Count.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Count.php @@ -10,7 +10,7 @@ /** * Fluent interface for adding a $count stage to an aggregation pipeline. * - * @psalm-type CountStageExpression = array{'$count': string} + * @phpstan-type CountStageExpression array{'$count': string} */ class Count extends Stage { @@ -19,7 +19,7 @@ public function __construct(Builder $builder, private string $fieldName) parent::__construct($builder); } - /** @psalm-return CountStageExpression */ + /** @phpstan-return CountStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php index 0e054433d..50f01ac75 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Densify.php @@ -13,9 +13,9 @@ /** * Fluent interface for adding a $densify stage to an aggregation pipeline. * - * @psalm-type BoundsType = 'full'|'partition'|array{0: int|float|UTCDateTime, 1: int|float|UTCDateTime} - * @psalm-type UnitType = 'year'|'month'|'week'|'day'|'hour'|'minute'|'second'|'millisecond' - * @psalm-type DensifyStageExpression = array{ + * @phpstan-type BoundsType 'full'|'partition'|array{0: int|float|UTCDateTime, 1: int|float|UTCDateTime} + * @phpstan-type UnitType 'year'|'month'|'week'|'day'|'hour'|'minute'|'second'|'millisecond' + * @phpstan-type DensifyStageExpression array{ * '$densify': object{ * field: string, * partitionByFields?: list, @@ -54,8 +54,8 @@ public function partitionByFields(string ...$fields): static /** * @param array|string $bounds * @param int|float $step - * @psalm-param BoundsType $bounds - * @psalm-param ''|UnitType $unit + * @phpstan-param BoundsType $bounds + * @phpstan-param ''|UnitType $unit */ public function range($bounds, $step, string $unit = ''): static { @@ -71,7 +71,7 @@ public function range($bounds, $step, string $unit = ''): static return $this; } - /** @psalm-return DensifyStageExpression */ + /** @phpstan-return DensifyStageExpression */ public function getExpression(): array { $params = (object) [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php index 1096f0d04..a8a963ab0 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php @@ -14,8 +14,8 @@ /** * Fluent interface for adding a $facet stage to an aggregation pipeline. * - * @psalm-import-type PipelineExpression from Builder - * @psalm-type FacetStageExpression = array{'$facet': array} + * @phpstan-import-type PipelineExpression from Builder + * @phpstan-type FacetStageExpression array{'$facet': array} */ class Facet extends Stage { @@ -48,7 +48,6 @@ public function field(string $field): static */ public function pipeline($builder): static { - /** @psalm-suppress RedundantPropertyInitializationCheck because the property might not be set yet */ if (! isset($this->field)) { throw new LogicException(__METHOD__ . ' requires setting a current field using field().'); } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php index df1069166..211a5b9a4 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill.php @@ -17,11 +17,11 @@ /** * Fluent interface for adding a $fill stage to an aggregation pipeline. * - * @psalm-import-type SortDirectionKeywords from Sort - * @psalm-import-type OperatorExpression from Expr - * @psalm-type SortDirection = int|SortDirectionKeywords - * @psalm-type SortShape = array - * @psalm-type FillStageExpression = array{ + * @phpstan-import-type SortDirectionKeywords from Sort + * @phpstan-import-type OperatorExpression from Expr + * @phpstan-type SortDirection int|SortDirectionKeywords + * @phpstan-type SortShape array + * @phpstan-type FillStageExpression array{ * '$fill': array{ * partitionBy?: string|OperatorExpression, * partitionByFields?: list, @@ -67,8 +67,8 @@ public function partitionByFields(string ...$fields): static /** * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName - * @psalm-param SortDirection|null $order + * @phpstan-param SortShape|string $fieldName + * @phpstan-param SortDirection|null $order */ public function sortBy($fieldName, $order = null): static { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php index f12ce9f62..4e28d6015 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Fill/Output.php @@ -16,7 +16,7 @@ /** * Fluent builder for output param of $fill stage * - * @psalm-import-type SortShape from Fill + * @phpstan-import-type SortShape from Fill */ class Output extends Stage { @@ -44,7 +44,7 @@ public function partitionByFields(string ...$fields): Fill /** * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName + * @phpstan-param SortShape|string $fieldName */ public function sortBy($fieldName, $order = null): Fill { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php index 962da0b50..e58809f2a 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/GraphLookup.php @@ -20,7 +20,7 @@ use function is_string; use function substr; -/** @psalm-import-type FieldMapping from ClassMetadata */ +/** @phpstan-import-type FieldMapping from ClassMetadata */ class GraphLookup extends Stage { private ?string $from; @@ -130,7 +130,7 @@ public function depthField(string $depthField): static * Target collection for the $graphLookup operation to search, recursively * matching the connectFromField to the connectToField. * - * @psalm-param class-string|string $from + * @param class-string|string $from */ public function from(string $from): static { @@ -281,7 +281,7 @@ private function getDocumentPersister(ClassMetadata $class): DocumentPersister return $this->dm->getUnitOfWork()->getDocumentPersister($class->name); } - /** @psalm-param FieldMapping $mapping */ + /** @phpstan-param FieldMapping $mapping */ private function getReferencedFieldName(string $fieldName, array $mapping): string { if (! $this->targetClass) { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Group.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Group.php index f57ef9a32..b6bc71a5f 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Group.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Group.php @@ -12,7 +12,7 @@ /** * Fluent interface for adding a $group stage to an aggregation pipeline. * - * @psalm-type GroupStageExpression = array{'$group': array} + * @phpstan-type GroupStageExpression array{'$group': array} */ class Group extends Operator implements GroupAccumulatorOperators { @@ -28,7 +28,7 @@ public function __construct(Builder $builder) $this->expr = $builder->expr(); } - /** @psalm-return GroupStageExpression */ + /** @phpstan-return GroupStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/IndexStats.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/IndexStats.php index f59e7732f..f253ee462 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/IndexStats.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/IndexStats.php @@ -10,11 +10,11 @@ /** * Fluent interface for adding a $indexStats stage to an aggregation pipeline. * - * @psalm-type IndexStatsStageExpression = array{'$indexStats': object} + * @phpstan-type IndexStatsStageExpression array{'$indexStats': object} */ class IndexStats extends Stage { - /** @psalm-return IndexStatsStageExpression */ + /** @phpstan-return IndexStatsStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Limit.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Limit.php index 708174947..61df12b39 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Limit.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Limit.php @@ -10,7 +10,7 @@ /** * Fluent interface for adding a $limit stage to an aggregation pipeline. * - * @psalm-type LimitStageExpression = array{'$limit': int} + * @phpstan-type LimitStageExpression array{'$limit': int} */ class Limit extends Stage { @@ -19,7 +19,7 @@ public function __construct(Builder $builder, private int $limit) parent::__construct($builder); } - /** @psalm-return LimitStageExpression */ + /** @phpstan-return LimitStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php index 7b6fcde70..49a93c0fb 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php @@ -16,10 +16,10 @@ /** * Fluent interface for building aggregation pipelines. * - * @psalm-import-type PipelineExpression from Builder - * @psalm-type PipelineParamType = Builder|Stage|PipelineExpression - * @psalm-type LookupStageExpression = array{ - * $lookup: array{ + * @phpstan-import-type PipelineExpression from Builder + * @phpstan-type PipelineParamType Builder|Stage|PipelineExpression + * @phpstan-type LookupStageExpression array{ + * '$lookup': array{ * from: string, * 'as'?: string, * localField?: string, @@ -46,7 +46,7 @@ class Lookup extends Stage /** * @var Builder|array>|null - * @psalm-var Builder|PipelineExpression|null + * @phpstan-var Builder|PipelineExpression|null */ private Builder|array|null $pipeline = null; @@ -101,7 +101,7 @@ public function from(string $from): static return $this; } - /** @psalm-return LookupStageExpression */ + /** @phpstan-return LookupStageExpression */ public function getExpression(): array { $lookup = [ @@ -193,7 +193,7 @@ public function let(array $let): static * and then reference the variables in the pipeline stages. * * @param Builder|Stage|array> $pipeline - * @psalm-param PipelineParamType $pipeline + * @phpstan-param PipelineParamType $pipeline */ public function pipeline($pipeline): static { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php index 356efa3eb..36b4a0616 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php @@ -16,12 +16,12 @@ use function is_array; /** - * @psalm-import-type PipelineExpression from Builder - * @psalm-type OutputCollection = string|array{db: string, coll: string} - * @psalm-type WhenMatchedType = 'replace'|'keepExisting'|'merge'|'fail'|PipelineExpression - * @psalm-type WhenMatchedParamType = Builder|Stage|WhenMatchedType - * @psalm-type WhenNotMatchedType = 'insert'|'discard'|'fail' - * @psalm-type MergeStageExpression = array{ + * @phpstan-import-type PipelineExpression from Builder + * @phpstan-type OutputCollection string|array{db: string, coll: string} + * @phpstan-type WhenMatchedType 'replace'|'keepExisting'|'merge'|'fail'|PipelineExpression + * @phpstan-type WhenMatchedParamType Builder|Stage|WhenMatchedType + * @phpstan-type WhenNotMatchedType 'insert'|'discard'|'fail' + * @phpstan-type MergeStageExpression array{ * '$merge': object{ * into: OutputCollection, * on?: string|list, @@ -33,7 +33,7 @@ */ class Merge extends Stage { - /** @psalm-var OutputCollection */ + /** @phpstan-var OutputCollection */ private string|array $into; /** @var list */ @@ -42,7 +42,7 @@ class Merge extends Stage /** @var array */ private array $let = []; - /** @psalm-var WhenMatchedParamType */ + /** @phpstan-var WhenMatchedParamType */ private string|array|Builder|Stage|null $whenMatched = null; private ?string $whenNotMatched = null; @@ -52,7 +52,7 @@ public function __construct(Builder $builder, private DocumentManager $dm) parent::__construct($builder); } - /** @psalm-return MergeStageExpression */ + /** @phpstan-return MergeStageExpression */ public function getExpression(): array { $params = (object) [ @@ -82,7 +82,7 @@ public function getExpression(): array /** * @param string|array $collection - * @psalm-param OutputCollection $collection + * @phpstan-param OutputCollection $collection */ public function into($collection): static { @@ -126,7 +126,7 @@ public function on(string ...$fields): static /** * @param string|array|Builder|Stage $whenMatched - * @psalm-param WhenMatchedParamType $whenMatched + * @phpstan-param WhenMatchedParamType $whenMatched */ public function whenMatched($whenMatched): static { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php index aa6a33501..2021e6d1e 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Out.php @@ -14,12 +14,12 @@ use function is_array; /** - * @psalm-import-type OutputCollection from Merge - * @psalm-type OutStageExpression = array{'$out': OutputCollection} + * @phpstan-import-type OutputCollection from Merge + * @phpstan-type OutStageExpression array{'$out': OutputCollection} */ class Out extends Stage { - /** @psalm-var OutputCollection */ + /** @phpstan-var OutputCollection */ private array|string $out; public function __construct(Builder $builder, string $collection, private DocumentManager $dm) @@ -38,7 +38,7 @@ public function getExpression(): array /** * @param string|array $collection - * @psalm-param OutputCollection $collection + * @phpstan-param OutputCollection $collection */ public function out($collection): Stage\Out { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Project.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Project.php index c108e5f3e..8b77ec68e 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Project.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Project.php @@ -9,12 +9,12 @@ /** * Fluent interface for adding a $project stage to an aggregation pipeline. * - * @psalm-import-type OperatorExpression from Expr - * @psalm-type ProjectStageExpression = array{'$project': array} + * @phpstan-import-type OperatorExpression from Expr + * @phpstan-type ProjectStageExpression array{'$project': array} */ class Project extends Operator { - /** @psalm-return ProjectStageExpression */ + /** @phpstan-return ProjectStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Redact.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Redact.php index 2ac792bdb..c8b061407 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Redact.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Redact.php @@ -9,8 +9,8 @@ /** * Fluent interface for adding a $redact stage to an aggregation pipeline. * - * @psalm-import-type OperatorExpression from Expr - * @psalm-type SetStageExpression = array{'$redact': array} + * @phpstan-import-type OperatorExpression from Expr + * @phpstan-type SetStageExpression array{'$redact': array} */ class Redact extends Operator { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceRoot.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceRoot.php index 3d25a2a74..4ffa90f1f 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceRoot.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceRoot.php @@ -7,8 +7,8 @@ use Doctrine\ODM\MongoDB\Aggregation\Expr; /** - * @psalm-import-type OperatorExpression from Expr - * @psalm-type ReplaceRootStageExpression = array{ + * @phpstan-import-type OperatorExpression from Expr + * @phpstan-type ReplaceRootStageExpression array{ * '$replaceRoot': array{ * newRoot: OperatorExpression|string, * } diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php index 317cde3c2..c1341eed4 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/ReplaceWith.php @@ -7,8 +7,8 @@ use Doctrine\ODM\MongoDB\Aggregation\Expr; /** - * @psalm-import-type OperatorExpression from Expr - * @psalm-type ReplaceWithStageExpression = array{'$replaceWith': OperatorExpression|string} + * @phpstan-import-type OperatorExpression from Expr + * @phpstan-type ReplaceWithStageExpression array{'$replaceWith': OperatorExpression|string} */ class ReplaceWith extends AbstractReplace { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sample.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sample.php index 24c8d6b27..7d87bb1ec 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sample.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sample.php @@ -10,7 +10,7 @@ /** * Fluent interface for adding a $sample stage to an aggregation pipeline. * - * @psalm-type SampleStageExpression = array{'$sample': array{size: int}} + * @phpstan-type SampleStageExpression array{'$sample': array{size: int}} */ class Sample extends Stage { @@ -19,7 +19,7 @@ public function __construct(Builder $builder, private int $size) parent::__construct($builder); } - /** @psalm-return SampleStageExpression */ + /** @phpstan-return SampleStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php index 86cf1dc6b..e1eea694c 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search.php @@ -16,12 +16,12 @@ use function strtolower; /** - * @psalm-import-type SortDirectionKeywords from Sort - * @psalm-type CountType = 'lowerBound'|'total' - * @psalm-type SortMetaKeywords = 'searchScore' - * @psalm-type SortMeta = array{'$meta': SortMetaKeywords} - * @psalm-type SortShape = array - * @psalm-type SearchStageExpression = array{ + * @phpstan-import-type SortDirectionKeywords from Sort + * @phpstan-type CountType 'lowerBound'|'total' + * @phpstan-type SortMetaKeywords 'searchScore' + * @phpstan-type SortMeta array{'$meta': SortMetaKeywords} + * @phpstan-type SortShape array + * @phpstan-type SearchStageExpression array{ * '$search': object{ * index?: string, * count?: object{ @@ -71,7 +71,7 @@ public function __construct(Builder $builder) parent::__construct($builder); } - /** @psalm-return SearchStageExpression */ + /** @phpstan-return SearchStageExpression */ public function getExpression(): array { $params = (object) []; @@ -111,7 +111,7 @@ public function index(string $name): static return $this; } - /** @psalm-param CountType $type */ + /** @phpstan-param CountType $type */ public function countDocuments(string $type, ?int $threshold = null): static { $this->count = (object) ['type' => $type]; @@ -148,8 +148,8 @@ public function returnStoredSource(bool $returnStoredSource = true): static /** * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName - * @psalm-param int|SortMeta|SortDirectionKeywords|null $order + * @phpstan-param SortShape|string $fieldName + * @phpstan-param int|SortMeta|SortDirectionKeywords|null $order */ public function sort($fieldName, $order = null): static { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php index eb115ab9c..c7dede7c4 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php @@ -11,10 +11,10 @@ /** * @internal * - * @psalm-import-type SortDirectionKeywords from Sort - * @psalm-import-type SortMetaKeywords from Search - * @psalm-import-type SortMeta from Search - * @psalm-import-type SortShape from Search + * @phpstan-import-type SortDirectionKeywords from Sort + * @phpstan-import-type SortMetaKeywords from Search + * @phpstan-import-type SortMeta from Search + * @phpstan-import-type SortShape from Search */ abstract class AbstractSearchOperator extends Stage implements SearchOperator { @@ -46,18 +46,15 @@ public function returnStoredSource(bool $returnStoredSource): Search /** * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName - * @psalm-param int|SortMeta|SortDirectionKeywords|null $order + * @phpstan-param SortShape|string $fieldName + * @phpstan-param int|SortMeta|SortDirectionKeywords|null $order */ public function sort($fieldName, $order = null): Search { return $this->search->sort($fieldName, $order); } - /** - * @return array - * @psalm-return non-empty-array - */ + /** @return non-empty-array */ final public function getExpression(): array { return [$this->getOperatorName() => $this->getOperatorParams()]; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php index cca5aed34..09c1fe85b 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php @@ -9,12 +9,12 @@ /** * Fluent interface for adding a $set stage to an aggregation pipeline. * - * @psalm-import-type OperatorExpression from Expr - * @psalm-type SetStageExpression = array{'$set': array} + * @phpstan-import-type OperatorExpression from Expr + * @phpstan-type SetStageExpression array{'$set': array} */ final class Set extends Operator { - /** @psalm-return SetStageExpression */ + /** @phpstan-return SetStageExpression */ public function getExpression(): array { return ['$set' => $this->expr->getExpression()]; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SetWindowFields.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SetWindowFields.php index 9d8b43656..0d36a7eaa 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SetWindowFields.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SetWindowFields.php @@ -14,11 +14,11 @@ use function strtolower; /** - * @psalm-import-type SortDirectionKeywords from Sort - * @psalm-import-type OperatorExpression from Expr - * @psalm-type SortDirection = int|SortDirectionKeywords - * @psalm-type SortShape = array - * @psalm-type SetWindowFieldsStageExpression = array{ + * @phpstan-import-type SortDirectionKeywords from Sort + * @phpstan-import-type OperatorExpression from Expr + * @phpstan-type SortDirection int|SortDirectionKeywords + * @phpstan-type SortShape array + * @phpstan-type SetWindowFieldsStageExpression array{ * '$setWindowFields': object{ * partitionBy?: string|OperatorExpression, * sortBy?: SortShape, @@ -53,8 +53,8 @@ public function partitionBy($expression): static /** * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName - * @psalm-param SortDirection|null $order + * @phpstan-param SortShape|string $fieldName + * @phpstan-param SortDirection|null $order */ public function sortBy($fieldName, $order = null): static { @@ -76,7 +76,7 @@ public function output(): Output return $this->output; } - /** @psalm-return SetWindowFieldsStageExpression */ + /** @phpstan-return SetWindowFieldsStageExpression */ public function getExpression(): array { $params = (object) [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SetWindowFields/Output.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SetWindowFields/Output.php index 386d9ff30..640610ad1 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SetWindowFields/Output.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SetWindowFields/Output.php @@ -21,11 +21,11 @@ /** * Fluent builder for output param of $setWindowFields stage * - * @psalm-import-type SortShape from SetWindowFields - * @psalm-type WindowBound = 'current'|'unbounded'|int - * @psalm-type WindowBounds = array{0: WindowBound, 1: WindowBound} - * @psalm-type WindowUnit = 'year'|'quarter'|'month'|'week'|'day'|'hour'|'minute'|'second'|'millisecond' - * @psalm-type Window = object{ + * @phpstan-import-type SortShape from SetWindowFields + * @phpstan-type WindowBound 'current'|'unbounded'|int + * @phpstan-type WindowBounds array{0: WindowBound, 1: WindowBound} + * @phpstan-type WindowUnit 'year'|'quarter'|'month'|'week'|'day'|'hour'|'minute'|'second'|'millisecond' + * @phpstan-type Window object{ * document?: WindowBounds, * range?: WindowBounds, * unit?: WindowUnit, @@ -37,7 +37,7 @@ class Output extends Operator implements WindowOperators private string $currentField = ''; - /** @psalm-var array */ + /** @phpstan-var array */ private array $windows = []; public function __construct(Builder $builder, private SetWindowFields $setWindowFields) @@ -54,7 +54,7 @@ public function partitionBy($expression): SetWindowFields /** * @param array|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName + * @phpstan-param SortShape|string $fieldName */ public function sortBy($fieldName, $order = null): SetWindowFields { @@ -75,9 +75,9 @@ public function field(string $fieldName): static /** * Specifies the window boundaries and parameters. * - * @psalm-param WindowBounds|null $documents - * @psalm-param WindowBounds|null $range - * @psalm-param WindowUnit|null $unit + * @phpstan-param WindowBounds|null $documents + * @phpstan-param WindowBounds|null $range + * @phpstan-param WindowUnit|null $unit */ public function window(?array $documents = null, ?array $range = null, ?string $unit = null): static { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Skip.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Skip.php index a08fd1167..440b04ed9 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Skip.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Skip.php @@ -10,7 +10,7 @@ /** * Fluent interface for adding a $skip stage to an aggregation pipeline. * - * @psalm-type SkipStageExpression = array{'$skip': int} + * @phpstan-type SkipStageExpression array{'$skip': int} */ class Skip extends Stage { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sort.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sort.php index 235d753a6..952597b07 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sort.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Sort.php @@ -15,11 +15,11 @@ /** * Fluent interface for adding a $sort stage to an aggregation pipeline. * - * @psalm-type SortMetaKeywords = 'textScore'|'indexKey' - * @psalm-type SortDirectionKeywords = 'asc'|'desc' - * @psalm-type SortMeta = array{'$meta': SortMetaKeywords} - * @psalm-type SortShape = array - * @psalm-type SortStageExpression = array{ + * @phpstan-type SortMetaKeywords 'textScore'|'indexKey' + * @phpstan-type SortDirectionKeywords 'asc'|'desc' + * @phpstan-type SortMeta array{'$meta': SortMetaKeywords} + * @phpstan-type SortShape array + * @phpstan-type SortStageExpression array{ * '$sort': array * } */ @@ -31,8 +31,8 @@ class Sort extends Stage /** * @param array>|string $fieldName Field name or array of field/order pairs * @param int|string $order Field order (if one field is specified) - * @psalm-param SortShape|string $fieldName - * @psalm-param int|SortMeta|SortDirectionKeywords|null $order + * @phpstan-param SortShape|string $fieldName + * @phpstan-param int|SortMeta|SortDirectionKeywords|null $order */ public function __construct(Builder $builder, $fieldName, $order = null) { @@ -55,7 +55,7 @@ public function __construct(Builder $builder, $fieldName, $order = null) } } - /** @psalm-return SortStageExpression */ + /** @phpstan-return SortStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php index e37fd4668..76f1347de 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/SortByCount.php @@ -11,7 +11,7 @@ use function substr; -/** @psalm-type SortByCountStageExpression = array{'$sortByCount': string} */ +/** @phpstan-type SortByCountStageExpression array{'$sortByCount': string} */ class SortByCount extends Stage { private string $fieldName; @@ -29,7 +29,7 @@ public function __construct(Builder $builder, string $fieldName, DocumentManager $this->fieldName = '$' . $documentPersister->prepareFieldName(substr($fieldName, 1)); } - /** @psalm-return SortByCountStageExpression */ + /** @phpstan-return SortByCountStageExpression */ public function getExpression(): array { return [ diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php index 6c468c942..0f388a8f4 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php @@ -13,9 +13,9 @@ /** * Fluent interface for adding a $unionWith stage to an aggregation pipeline. * - * @psalm-import-type PipelineExpression from Builder - * @psalm-type PipelineParamType = array|Builder|Stage|PipelineExpression - * @psalm-type UnionWithStageExpression = array{ + * @phpstan-import-type PipelineExpression from Builder + * @phpstan-type PipelineParamType array|Builder|Stage|PipelineExpression + * @phpstan-type UnionWithStageExpression array{ * '$unionWith': object{ * coll: string, * pipeline?: PipelineExpression, @@ -24,7 +24,7 @@ */ class UnionWith extends Stage { - /** @psalm-var ?PipelineParamType */ + /** @phpstan-var ?PipelineParamType */ private array|Builder|Stage|null $pipeline = null; public function __construct(Builder $builder, private DocumentManager $dm, private string $collection) @@ -40,7 +40,7 @@ public function __construct(Builder $builder, private DocumentManager $dm, priva /** * @param array|Builder|Stage $pipeline - * @psalm-param PipelineParamType $pipeline + * @phpstan-param PipelineParamType $pipeline */ public function pipeline($pipeline): static { @@ -57,7 +57,7 @@ public function pipeline($pipeline): static return $this; } - /** @psalm-return UnionWithStageExpression */ + /** @phpstan-return UnionWithStageExpression */ public function getExpression(): array { $params = (object) ['coll' => $this->collection]; diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php index c6e6bd7ac..8e02c9059 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnsetStage.php @@ -14,7 +14,7 @@ /** * Fluent interface for adding an $unset stage to an aggregation pipeline. * - * @psalm-type UnsetStageExpression = array{'$unset': list} + * @phpstan-type UnsetStageExpression array{'$unset': list} */ class UnsetStage extends Stage { diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Unwind.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Unwind.php index 66596d398..060ab558e 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Unwind.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Unwind.php @@ -10,7 +10,7 @@ /** * Fluent interface for adding a $unwind stage to an aggregation pipeline. * - * @psalm-type UnwindStageExpression = array{ + * @phpstan-type UnwindStageExpression array{ * '$unwind': string|array{ * path: string, * includeArrayIndex?: string, @@ -29,7 +29,7 @@ public function __construct(Builder $builder, private string $fieldName) parent::__construct($builder); } - /** @psalm-return UnwindStageExpression */ + /** @phpstan-return UnwindStageExpression */ public function getExpression(): array { // Fallback behavior for MongoDB < 3.2 diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index aef7fa05b..adc486991 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -47,7 +47,7 @@ * $config = new Configuration(); * $dm = DocumentManager::create(new Connection(), $config); * - * @psalm-import-type CommitOptions from UnitOfWork + * @phpstan-import-type CommitOptions from UnitOfWork */ class Configuration { @@ -85,7 +85,7 @@ class Configuration /** * Array of attributes for this configuration instance. * - * @psalm-var array{ + * @phpstan-var array{ * autoGenerateHydratorClasses?: self::AUTOGENERATE_*, * autoGeneratePersistentCollectionClasses?: self::AUTOGENERATE_*, * classMetadataFactoryName?: class-string, @@ -326,7 +326,7 @@ public function getHydratorDir(): ?string * Gets an int flag that indicates whether hydrator classes should always be regenerated * during each script execution. * - * @psalm-return self::AUTOGENERATE_* + * @return self::AUTOGENERATE_* */ public function getAutoGenerateHydratorClasses(): int { @@ -337,7 +337,7 @@ public function getAutoGenerateHydratorClasses(): int * Sets an int flag that indicates whether hydrator classes should always be regenerated * during each script execution. * - * @psalm-param self::AUTOGENERATE_* $mode + * @param self::AUTOGENERATE_* $mode */ public function setAutoGenerateHydratorClasses(int $mode): void { @@ -368,7 +368,7 @@ public function getPersistentCollectionDir(): ?string * Gets a integer flag that indicates how and when persistent collection * classes should be generated. * - * @psalm-return self::AUTOGENERATE_* + * @return self::AUTOGENERATE_* */ public function getAutoGeneratePersistentCollectionClasses(): int { @@ -379,7 +379,7 @@ public function getAutoGeneratePersistentCollectionClasses(): int * Sets a integer flag that indicates how and when persistent collection * classes should be generated. * - * @psalm-param self::AUTOGENERATE_* $mode + * @param self::AUTOGENERATE_* $mode */ public function setAutoGeneratePersistentCollectionClasses(int $mode): void { @@ -414,7 +414,7 @@ public function getDefaultDB(): ?string } /** - * @psalm-param class-string $cmfName + * @param class-string $cmfName * * @throws MongoDBException If is not a ClassMetadataFactoryInterface. */ @@ -429,7 +429,7 @@ public function setClassMetadataFactoryName(string $cmfName): void $this->attributes['classMetadataFactoryName'] = $cmfName; } - /** @psalm-return class-string */ + /** @return class-string */ public function getClassMetadataFactoryName(): string { if (! isset($this->attributes['classMetadataFactoryName'])) { @@ -439,7 +439,7 @@ public function getClassMetadataFactoryName(): string return $this->attributes['classMetadataFactoryName']; } - /** @psalm-return CommitOptions */ + /** @phpstan-return CommitOptions */ public function getDefaultCommitOptions(): array { if (! isset($this->attributes['defaultCommitOptions'])) { @@ -449,7 +449,7 @@ public function getDefaultCommitOptions(): array return $this->attributes['defaultCommitOptions']; } - /** @psalm-param CommitOptions $defaultCommitOptions */ + /** @phpstan-param CommitOptions $defaultCommitOptions */ public function setDefaultCommitOptions(array $defaultCommitOptions): void { foreach (UnitOfWork::DEPRECATED_WRITE_OPTIONS as $deprecatedOption) { @@ -470,7 +470,7 @@ public function setDefaultCommitOptions(array $defaultCommitOptions): void * Add a filter to the list of possible filters. * * @param array $parameters - * @psalm-param class-string $className + * @param class-string $className */ public function addFilter(string $name, string $className, array $parameters = []): void { @@ -480,7 +480,7 @@ public function addFilter(string $name, string $className, array $parameters = [ ]; } - /** @psalm-return class-string|null */ + /** @return class-string|null */ public function getFilterClassName(string $name): ?string { return isset($this->attributes['filters'][$name]) @@ -497,7 +497,7 @@ public function getFilterParameters(string $name): array } /** - * @psalm-param class-string> $className + * @param class-string> $className * * @throws MongoDBException If is not an ObjectRepository. */ @@ -512,14 +512,14 @@ public function setDefaultDocumentRepositoryClassName(string $className): void $this->attributes['defaultDocumentRepositoryClassName'] = $className; } - /** @psalm-return class-string> */ + /** @return class-string> */ public function getDefaultDocumentRepositoryClassName(): string { return $this->attributes['defaultDocumentRepositoryClassName'] ?? DocumentRepository::class; } /** - * @psalm-param class-string> $className + * @param class-string> $className * * @throws MongoDBException If the class does not implement the GridFSRepository interface. */ @@ -534,7 +534,7 @@ public function setDefaultGridFSRepositoryClassName(string $className): void $this->attributes['defaultGridFSRepositoryClassName'] = $className; } - /** @psalm-return class-string> */ + /** @return class-string> */ public function getDefaultGridFSRepositoryClassName(): string { return $this->attributes['defaultGridFSRepositoryClassName'] ?? DefaultGridFSRepository::class; diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index 706b23ed8..b8c40aea3 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -49,8 +49,8 @@ * $config = new Configuration(); * $dm = DocumentManager::create(new Connection(), $config); * - * @psalm-import-type CommitOptions from UnitOfWork - * @psalm-import-type FieldMapping from ClassMetadata + * @phpstan-import-type CommitOptions from UnitOfWork + * @phpstan-import-type FieldMapping from ClassMetadata */ class DocumentManager implements ObjectManager { @@ -286,14 +286,11 @@ public function getClassNameResolver(): ClassNameResolver /** * Returns the metadata for a class. * - * @param string $className The class name. - * @psalm-param class-string $className + * @param class-string $className The class name. * - * @psalm-return ClassMetadata + * @return ClassMetadata * * @template T of object - * - * @psalm-suppress InvalidReturnType, InvalidReturnStatement see https://github.com/vimeo/psalm/issues/5788 */ public function getClassMetadata($className): ClassMetadata { @@ -303,7 +300,7 @@ public function getClassMetadata($className): ClassMetadata /** * Returns the MongoDB instance for a class. * - * @psalm-param class-string $className + * @param class-string $className */ public function getDocumentDatabase(string $className): Database { @@ -553,11 +550,9 @@ public function unlock(object $document): void /** * Gets the repository for a document class. * - * @param string $className The name of the Document. - * @psalm-param class-string $className + * @param class-string $className The name of the Document. * - * @return DocumentRepository|GridFSRepository|ViewRepository The repository. - * @psalm-return DocumentRepository|GridFSRepository|ViewRepository + * @return DocumentRepository|GridFSRepository|ViewRepository The repository. * * @template T of object */ @@ -572,7 +567,7 @@ public function getRepository($className) * database. * * @param array $options Array of options to be used with batchInsert(), update() and remove() - * @psalm-param CommitOptions $options + * @phpstan-param CommitOptions $options * * @throws MongoDBException * @throws Throwable From event listeners. @@ -591,19 +586,19 @@ public function flush(array $options = []) * has its identifier populated. Otherwise a proxy is returned that automatically * loads itself on first access. * - * @param mixed $identifier - * @psalm-param class-string $documentName + * @param mixed $identifier + * @param class-string $documentName * - * @psalm-return T|(T&GhostObjectInterface) + * @return T|(T&GhostObjectInterface) * * @template T of object */ public function getReference(string $documentName, $identifier): object { - /** @psalm-var ClassMetadata $class */ + /** @var ClassMetadata $class */ $class = $this->metadataFactory->getMetadataFor(ltrim($documentName, '\\')); assert($class instanceof ClassMetadata); - /** @psalm-var T|false $document */ + /** @phpstan-var T|false $document */ $document = $this->unitOfWork->tryGetById($identifier, $class); // Check identity map first, if its already in there just return it. @@ -611,7 +606,7 @@ public function getReference(string $documentName, $identifier): object return $document; } - /** @psalm-var T&GhostObjectInterface $document */ + /** @var T&GhostObjectInterface $document */ $document = $this->proxyFactory->getProxy($class, $identifier); $this->unitOfWork->registerManaged($document, $identifier, []); @@ -658,13 +653,12 @@ public function getPartialReference(string $documentName, $identifier): object * * This is just a convenient shortcut for getRepository($documentName)->find($id). * - * @param string $className - * @param mixed $id - * @param int $lockMode - * @param int $lockVersion - * @psalm-param class-string $className + * @param class-string $className + * @param mixed $id + * @param int $lockMode + * @param int $lockVersion * - * @psalm-return T|null + * @return T|null * * @template T of object */ @@ -672,7 +666,6 @@ public function find($className, $id, $lockMode = LockMode::NONE, $lockVersion = { $repository = $this->getRepository($className); if ($repository instanceof DocumentRepository) { - /** @psalm-var DocumentRepository $repository */ return $repository->find($id, $lockMode, $lockVersion); } @@ -745,7 +738,7 @@ public function getConfiguration(): Configuration /** * Returns a reference to the supplied document. * - * @psalm-param FieldMapping $referenceMapping + * @phpstan-param FieldMapping $referenceMapping * * @return mixed The reference for the document in question, according to the desired mapping * @@ -803,10 +796,9 @@ public function createReference(object $document, array $referenceMapping) * * @param array $referenceMapping Mappings of reference for which discriminator data is created. * @param ClassMetadata $class Metadata of reference document class. - * @psalm-param FieldMapping $referenceMapping + * @phpstan-param FieldMapping $referenceMapping * - * @return array with next structure [{discriminator field} => {discriminator value}] - * @psalm-return array + * @return array with next structure [{discriminator field} => {discriminator value}] * * @throws MappingException When discriminator map is present and reference class in not registered in it. */ @@ -892,7 +884,7 @@ public function getFilterCollection(): FilterCollection * @param FieldMapping $mapping * @param array|null $data * - * @psalm-return class-string + * @return class-string */ public function getClassNameForAssociation(array $mapping, $data): string { diff --git a/lib/Doctrine/ODM/MongoDB/Event/OnClassMetadataNotFoundEventArgs.php b/lib/Doctrine/ODM/MongoDB/Event/OnClassMetadataNotFoundEventArgs.php index 811909714..f90f2079b 100644 --- a/lib/Doctrine/ODM/MongoDB/Event/OnClassMetadataNotFoundEventArgs.php +++ b/lib/Doctrine/ODM/MongoDB/Event/OnClassMetadataNotFoundEventArgs.php @@ -18,7 +18,7 @@ final class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs /** @var ClassMetadata|null */ private ?ClassMetadata $foundMetadata = null; - /** @psalm-param class-string $className */ + /** @param class-string $className */ public function __construct(private string $className, DocumentManager $dm) { parent::__construct($dm); @@ -39,7 +39,7 @@ public function getFoundMetadata(): ?ClassMetadata /** * Retrieve class name for which a failed metadata fetch attempt was executed * - * @psalm-return class-string + * @return class-string */ public function getClassName(): string { diff --git a/lib/Doctrine/ODM/MongoDB/Event/PreUpdateEventArgs.php b/lib/Doctrine/ODM/MongoDB/Event/PreUpdateEventArgs.php index f01dff1a5..a82a0fb1e 100644 --- a/lib/Doctrine/ODM/MongoDB/Event/PreUpdateEventArgs.php +++ b/lib/Doctrine/ODM/MongoDB/Event/PreUpdateEventArgs.php @@ -15,11 +15,11 @@ /** * Class that holds event arguments for a preUpdate event. * - * @psalm-import-type ChangeSet from UnitOfWork + * @phpstan-import-type ChangeSet from UnitOfWork */ final class PreUpdateEventArgs extends LifecycleEventArgs { - /** @psalm-param array $changeSet */ + /** @param array $changeSet */ public function __construct( object $document, DocumentManager $dm, diff --git a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php index 52c1f3943..2b91220d5 100644 --- a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php @@ -37,7 +37,7 @@ * The HydratorFactory class is responsible for instantiating a correct hydrator * type based on document's ClassMetadata * - * @psalm-import-type Hints from UnitOfWork + * @phpstan-import-type Hints from UnitOfWork */ final class HydratorFactory { @@ -69,7 +69,7 @@ final class HydratorFactory /** * Array of instantiated document hydrators. * - * @psalm-var array + * @var array */ private array $hydrators = []; @@ -94,7 +94,7 @@ public function __construct(DocumentManager $dm, EventManager $evm, ?string $hyd /** * Gets the hydrator object for the given document class. * - * @psalm-param class-string $className + * @param class-string $className */ public function getHydratorFor(string $className): HydratorInterface { @@ -420,7 +420,7 @@ public function hydrate(object \$document, array \$data, array \$hints = []): ar * Hydrate array of MongoDB document data into the given document object. * * @param array $data - * @psalm-param Hints $hints Any hints to account for during reconstitution/lookup of the document. + * @phpstan-param Hints $hints Any hints to account for during reconstitution/lookup of the document. * * @return array */ diff --git a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorInterface.php b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorInterface.php index 58fc21c07..f804b783d 100644 --- a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorInterface.php +++ b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorInterface.php @@ -9,7 +9,7 @@ /** * The HydratorInterface defines methods all hydrator need to implement * - * @psalm-import-type Hints from UnitOfWork + * @phpstan-import-type Hints from UnitOfWork */ interface HydratorInterface { @@ -17,7 +17,7 @@ interface HydratorInterface * Hydrate array of MongoDB document data into the given document object. * * @param array $data - * @psalm-param Hints $hints + * @phpstan-param Hints $hints * * @return array */ diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php index 94f5f219f..827b4cf4b 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php @@ -17,7 +17,7 @@ * * @internal * - * @psalm-import-type Hints from UnitOfWork + * @phpstan-import-type Hints from UnitOfWork * * @template TDocument of object * @template-implements Iterator @@ -30,7 +30,7 @@ final class HydratingIterator implements Iterator /** * @param Traversable> $traversable * @param ClassMetadata $class - * @psalm-param Hints $unitOfWorkHints + * @phpstan-param Hints $unitOfWorkHints */ public function __construct(Traversable $traversable, private UnitOfWork $unitOfWork, private ClassMetadata $class, private array $unitOfWorkHints = []) { diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/Iterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/Iterator.php index 59561a2f6..bedeacd49 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/Iterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/Iterator.php @@ -10,6 +10,6 @@ */ interface Iterator extends \Iterator { - /** @psalm-return array */ + /** @return array */ public function toArray(): array; } diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/PrimingIterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/PrimingIterator.php index 43c21f278..86c4920b8 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/PrimingIterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/PrimingIterator.php @@ -13,7 +13,7 @@ use function iterator_to_array; /** - * @psalm-import-type Hints from UnitOfWork + * @phpstan-import-type Hints from UnitOfWork * @template TValue * @template TDocument of object * @template-implements Iterator @@ -26,7 +26,7 @@ final class PrimingIterator implements Iterator * @param \Iterator $iterator * @param ClassMetadata $class * @param array $primers - * @psalm-param Hints $unitOfWorkHints + * @phpstan-param Hints $unitOfWorkHints */ public function __construct(private \Iterator $iterator, private ClassMetadata $class, private ReferencePrimer $referencePrimer, private array $primers, private array $unitOfWorkHints = []) { diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/SearchIndex.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/SearchIndex.php index cbf3ab52a..cb5bc2a11 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/SearchIndex.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/SearchIndex.php @@ -13,8 +13,8 @@ * * @Annotation * @NamedArgumentConstructor - * @psalm-import-type SearchIndexStoredSource from ClassMetadata - * @psalm-import-type SearchIndexSynonym from ClassMetadata + * @phpstan-import-type SearchIndexStoredSource from ClassMetadata + * @phpstan-import-type SearchIndexSynonym from ClassMetadata */ #[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] class SearchIndex implements Annotation diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php index 34c13622d..558ab6a86 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php @@ -65,7 +65,7 @@ * get the whole class name, namespace inclusive, prepended to every property in * the serialized representation). * - * @psalm-type FieldMappingConfig = array{ + * @phpstan-type FieldMappingConfig array{ * type?: string, * fieldName?: string, * name?: string, @@ -106,7 +106,7 @@ * background?: bool, * enumType?: class-string, * } - * @psalm-type FieldMapping = array{ + * @phpstan-type FieldMapping array{ * type: string, * fieldName: string, * name: string, @@ -152,7 +152,7 @@ * enumType?: class-string, * storeEmptyArray?: bool, * } - * @psalm-type AssociationFieldMapping = array{ + * @phpstan-type AssociationFieldMapping array{ * type?: string, * fieldName: string, * name: string, @@ -197,8 +197,8 @@ * alsoLoadFields?: list, * storeEmptyArray?: bool, * } - * @psalm-type IndexKeys = array - * @psalm-type IndexOptions = array{ + * @phpstan-type IndexKeys array + * @phpstan-type IndexOptions array{ * background?: bool, * bits?: int, * default_language?: string, @@ -214,21 +214,21 @@ * unique?: bool, * weights?: array{string, int}, * } - * @psalm-type IndexMapping = array{ + * @phpstan-type IndexMapping array{ * keys: IndexKeys, * options: IndexOptions * } - * @psalm-type SearchIndexStoredSourceInclude = array{include: list} - * @psalm-type SearchIndexStoredSourceExclude = array{exclude: list} - * @psalm-type SearchIndexStoredSource = bool|SearchIndexStoredSourceInclude|SearchIndexStoredSourceExclude - * @psalm-type SearchIndexSynonym = array{ + * @phpstan-type SearchIndexStoredSourceInclude array{include: list} + * @phpstan-type SearchIndexStoredSourceExclude array{exclude: list} + * @phpstan-type SearchIndexStoredSource bool|SearchIndexStoredSourceInclude|SearchIndexStoredSourceExclude + * @phpstan-type SearchIndexSynonym array{ * analyzer: string, * name: string, * source: array{ * collection: string, * }, * } - * @psalm-type SearchIndexDefinition = array{ + * @phpstan-type SearchIndexDefinition array{ * mappings: array{ * dynamic?: bool, * fields?: array, @@ -239,13 +239,13 @@ * storedSource?: SearchIndexStoredSource, * synonyms?: list, * } - * @psalm-type SearchIndexMapping = array{ + * @phpstan-type SearchIndexMapping array{ * name: string, * definition: SearchIndexDefinition * } - * @psalm-type ShardKeys = array - * @psalm-type ShardOptions = array - * @psalm-type ShardKey = array{ + * @phpstan-type ShardKeys array + * @phpstan-type ShardOptions array + * @phpstan-type ShardKey array{ * keys?: ShardKeys, * options?: ShardOptions * } @@ -517,7 +517,7 @@ * READ-ONLY: The array of indexes for the document collection. * * @var array> - * @psalm-var array + * @phpstan-var array */ public $indexes = []; @@ -532,14 +532,14 @@ * READ-ONLY: Keys and options describing shard key. Only for sharded collections. * * @var array - * @psalm-var ShardKey + * @phpstan-var ShardKey */ public $shardKey = []; /** * Allows users to specify a validation schema for the collection. * - * @psalm-var array|object|null + * @phpstan-var array|object|null */ private array|object|null $validator = null; @@ -556,8 +556,7 @@ /** * READ-ONLY: The name of the document class. * - * @var string - * @psalm-var class-string + * @var class-string */ public $name; @@ -566,8 +565,7 @@ * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same * as {@link $documentName}. * - * @var string - * @psalm-var class-string + * @var class-string */ public $rootDocumentName; @@ -575,24 +573,21 @@ * The name of the custom repository class used for the document class. * (Optional). * - * @var string|null - * @psalm-var class-string|null + * @var class-string|null */ public $customRepositoryClassName; /** * READ-ONLY: The names of the parent classes (ancestors). * - * @var array - * @psalm-var list + * @var list */ public $parentClasses = []; /** * READ-ONLY: The names of all subclasses (descendants). * - * @var array - * @psalm-var list + * @var list */ public $subClasses = []; @@ -645,7 +640,7 @@ * document can have the id attribute, forming a composite key. * * @var array - * @psalm-var array + * @phpstan-var array */ public $fieldMappings = []; @@ -654,7 +649,7 @@ * Keys are field names and values are mapping definitions. * * @var array - * @psalm-var array + * @phpstan-var array */ public $associationMappings = []; @@ -680,8 +675,7 @@ * * @see discriminatorField * - * @var string|null - * @psalm-var class-string|null + * @var class-string|null */ public $discriminatorValue; @@ -693,7 +687,7 @@ * * @see discriminatorField * - * @psalm-var array + * @var array */ public $discriminatorMap = []; @@ -794,8 +788,7 @@ /** * The ReflectionClass instance of the mapped class. * - * @var ReflectionClass - * @psalm-var ReflectionClass + * @var ReflectionClass */ public $reflClass; @@ -817,7 +810,7 @@ * Initializes a new ClassMetadata instance that will hold the object-document mapping * metadata of the class with the given name. * - * @psalm-param class-string $documentName + * @param class-string $documentName */ public function __construct(string $documentName) { @@ -939,7 +932,7 @@ public function isInheritedField(string $fieldName): bool /** * Registers a custom repository class for the document class. * - * @psalm-param class-string|null $repositoryClassName + * @param class-string|null $repositoryClassName */ public function setCustomRepositoryClass(?string $repositoryClassName): void { @@ -1058,8 +1051,7 @@ public function setAlsoLoadMethods(array $methods): void * are only used to discern the hydration class and are not mapped to class * properties. * - * @param array|string|null $discriminatorField - * @psalm-param array{name?: string, fieldName?: string}|string|null $discriminatorField + * @param array{name?: string, fieldName?: string}|string|null $discriminatorField * * @throws MappingException If the discriminator field conflicts with the * "name" attribute of a mapped field. @@ -1175,8 +1167,8 @@ public function setDiscriminatorValue(string $value): void * Add a index for this Document. * * @param array $keys - * @psalm-param IndexKeys $keys - * @psalm-param IndexOptions $options + * @phpstan-param IndexKeys $keys + * @phpstan-param IndexOptions $options */ public function addIndex(array $keys, array $options = []): void { @@ -1206,7 +1198,7 @@ public function addIndex(array $keys, array $options = []): void /** * Returns the array of indexes for this Document. * - * @psalm-return array + * @phpstan-return array */ public function getIndexes(): array { @@ -1224,7 +1216,7 @@ public function hasIndexes(): bool /** * Add a search index for this Document. * - * @psalm-param SearchIndexDefinition $definition + * @phpstan-param SearchIndexDefinition $definition */ public function addSearchIndex(array $definition, ?string $name = null): void { @@ -1237,7 +1229,7 @@ public function addSearchIndex(array $definition, ?string $name = null): void /** * Returns the array of search indexes for this Document. * - * @psalm-return list + * @phpstan-return list */ public function getSearchIndexes(): array { @@ -1257,8 +1249,8 @@ public function hasSearchIndexes(): bool * * @param array $keys * @param array $options - * @psalm-param ShardKeys $keys - * @psalm-param ShardOptions $options + * @phpstan-param ShardKeys $keys + * @phpstan-param ShardOptions $options * * @throws MappingException */ @@ -1309,7 +1301,7 @@ public function setShardKey(array $keys, array $options = []): void ]; } - /** @psalm-return ShardKey */ + /** @phpstan-return ShardKey */ public function getShardKey(): array { return $this->shardKey; @@ -1323,19 +1315,13 @@ public function isSharded(): bool return $this->shardKey !== []; } - /** - * @return array|object|null - * @psalm-return array|object|null - */ + /** @return array|object|null */ public function getValidator() { return $this->validator; } - /** - * @param array|object|null $validator - * @psalm-param array|object|null $validator - */ + /** @param array|object|null $validator */ public function setValidator($validator): void { $this->validator = $validator; @@ -1449,7 +1435,7 @@ public function getReflectionProperty(string $name): ReflectionProperty return $this->reflFields[$name]; } - /** @psalm-return class-string */ + /** @return class-string */ public function getName(): string { return $this->name; @@ -1482,8 +1468,7 @@ public function getCollection(): string /** * Sets the collection this Document is mapped to. * - * @param array|string $name - * @psalm-param array{name: string, capped?: bool, size?: int, max?: int}|string $name + * @param array{name: string, capped?: bool, size?: int, max?: int}|string $name * * @throws InvalidArgumentException */ @@ -1583,7 +1568,7 @@ public function isMappedToCollection(): bool /** * Validates the storage strategy of a mapping for consistency * - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping * * @throws MappingException */ @@ -1639,7 +1624,7 @@ private function applyStorageStrategy(array &$mapping): void /** * Map a single embedded document. * - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping */ public function mapOneEmbedded(array $mapping): void { @@ -1652,7 +1637,7 @@ public function mapOneEmbedded(array $mapping): void /** * Map a collection of embedded documents. * - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping */ public function mapManyEmbedded(array $mapping): void { @@ -1665,7 +1650,7 @@ public function mapManyEmbedded(array $mapping): void /** * Map a single document reference. * - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping */ public function mapOneReference(array $mapping): void { @@ -1678,7 +1663,7 @@ public function mapOneReference(array $mapping): void /** * Map a collection of document references. * - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping */ public function mapManyReference(array $mapping): void { @@ -1694,7 +1679,7 @@ public function mapManyReference(array $mapping): void * * @internal * - * @psalm-param FieldMapping $fieldMapping + * @phpstan-param FieldMapping $fieldMapping */ public function addInheritedFieldMapping(array $fieldMapping): void { @@ -1713,7 +1698,7 @@ public function addInheritedFieldMapping(array $fieldMapping): void * * @internal * - * @psalm-param AssociationFieldMapping $mapping + * @phpstan-param AssociationFieldMapping $mapping * * @throws MappingException */ @@ -1924,7 +1909,7 @@ public function getFieldValue(object $document, string $field) /** * Gets the mapping of a field. * - * @psalm-return FieldMapping + * @phpstan-return FieldMapping * * @throws MappingException If the $fieldName is not found in the fieldMappings array. */ @@ -1940,7 +1925,7 @@ public function getFieldMapping(string $fieldName): array /** * Gets mappings of fields holding embedded document(s). * - * @psalm-return array + * @return array */ public function getEmbeddedFieldsMappings(): array { @@ -1954,7 +1939,7 @@ public function getEmbeddedFieldsMappings(): array * Gets the field mapping by its DB name. * E.g. it returns identifier's mapping when called with _id. * - * @psalm-return FieldMapping + * @phpstan-return FieldMapping * * @throws MappingException */ @@ -2029,8 +2014,7 @@ public function isInheritanceTypeCollectionPerClass(): bool /** * Sets the mapped subclasses of this class. * - * @param string[] $subclasses The names of all mapped subclasses. - * @psalm-param class-string[] $subclasses + * @param class-string[] $subclasses The names of all mapped subclasses. */ public function setSubclasses(array $subclasses): void { @@ -2044,8 +2028,7 @@ public function setSubclasses(array $subclasses): void * Assumes that the class names in the passed array are in the order: * directParent -> directParentParent -> directParentParentParent ... -> root. * - * @param string[] $classNames - * @psalm-param list $classNames + * @param list $classNames */ public function setParentClasses(array $classNames): void { @@ -2094,7 +2077,7 @@ public function isIdGeneratorNone(): bool * Sets the version field mapping used for versioning. Sets the default * value to use depending on the column type. * - * @psalm-param FieldMapping $mapping + * @phpstan-param FieldMapping $mapping * * @throws LockException */ @@ -2129,7 +2112,7 @@ public function setVersionField(?string $versionField): void * Sets the version field mapping used for versioning. Sets the default * value to use depending on the column type. * - * @psalm-param FieldMapping $mapping + * @phpstan-param FieldMapping $mapping * * @throws LockException */ @@ -2178,7 +2161,7 @@ public function isView(): bool return $this->isView; } - /** @psalm-param class-string $rootClass */ + /** @param class-string $rootClass */ public function markViewOf(string $rootClass): void { $this->isView = true; @@ -2205,7 +2188,7 @@ public function getTypeOfField($fieldName): ?string /** * @param string $assocName * - * @psalm-return class-string|null + * @return class-string|null */ public function getAssociationTargetClass($assocName): ?string { @@ -2219,7 +2202,7 @@ public function getAssociationTargetClass($assocName): ?string /** * Retrieve the collectionClass associated with an association * - * @psalm-return class-string + * @return class-string */ public function getAssociationCollectionClass(string $assocName): string { @@ -2249,9 +2232,9 @@ public function getAssociationMappedByTargetField($assocName) /** * Map a field. * - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping * - * @psalm-return FieldMapping + * @phpstan-return FieldMapping * * @throws MappingException */ @@ -2640,11 +2623,11 @@ public function __wakeup() /** * Creates a new instance of the mapped class, without invoking the constructor. * - * @psalm-return T + * @phpstan-return T */ public function newInstance(): object { - /** @psalm-var T */ + /** @phpstan-var T */ return $this->instantiator->instantiate($this->name); } @@ -2653,7 +2636,7 @@ private function isAllowedGridFSField(string $name): bool return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true); } - /** @psalm-param FieldMapping $mapping */ + /** @phpstan-param FieldMapping $mapping */ private function typeRequirementsAreMet(array $mapping): void { if ($mapping['type'] === Type::DECIMAL128 && ! extension_loaded('bcmath')) { @@ -2661,7 +2644,7 @@ private function typeRequirementsAreMet(array $mapping): void } } - /** @psalm-param FieldMapping $mapping */ + /** @phpstan-param FieldMapping $mapping */ private function checkDuplicateMapping(array $mapping): void { if ($mapping['notSaved'] ?? false) { @@ -2697,7 +2680,7 @@ private function isTypedProperty(string $name): bool /** * Validates & completes the given field mapping based on typed property. * - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping * * @return FieldMappingConfig */ @@ -2751,7 +2734,7 @@ private function validateAndCompleteTypedFieldMapping(array $mapping): array /** * Validates & completes the basic mapping information based on typed property. * - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping * * @return FieldMappingConfig */ diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php index 64f32867c..9035101f9 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php @@ -42,7 +42,7 @@ /** * XmlDriver is a metadata driver that enables mapping through XML files. * - * @psalm-import-type FieldMappingConfig from ClassMetadata + * @phpstan-import-type FieldMappingConfig from ClassMetadata * @template-extends FileDriver */ class XmlDriver extends FileDriver @@ -346,7 +346,7 @@ public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\C /** * @param ClassMetadata $class - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping */ private function addFieldMapping(ClassMetadata $class, array $mapping): void { @@ -800,7 +800,7 @@ private function setShardKey(ClassMetadata $class, SimpleXMLElement $xmlShardkey * * list($readPreference, $tags) = $this->transformReadPreference($xml->{read-preference}); * - * @psalm-return array{string, array>|null} + * @return array{string, array>|null} */ private function transformReadPreference(SimpleXMLElement $xmlReadPreference): array { diff --git a/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionFactory.php b/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionFactory.php index 39b163132..672859788 100644 --- a/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionFactory.php +++ b/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionFactory.php @@ -11,17 +11,17 @@ /** * Interface for persistent collection classes factory. * - * @psalm-import-type FieldMapping from ClassMetadata + * @phpstan-import-type FieldMapping from ClassMetadata */ interface PersistentCollectionFactory { /** * Creates specified persistent collection to work with given collection class. * - * @psalm-param FieldMapping $mapping - * @psalm-param BaseCollection|null $coll + * @param BaseCollection|null $coll + * @phpstan-param FieldMapping $mapping * - * @psalm-return PersistentCollectionInterface + * @return PersistentCollectionInterface */ public function create(DocumentManager $dm, array $mapping, ?BaseCollection $coll = null): PersistentCollectionInterface; } diff --git a/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionInterface.php b/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionInterface.php index e7012cbfd..c71b5d17b 100644 --- a/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionInterface.php +++ b/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionInterface.php @@ -15,8 +15,8 @@ * * @internal * - * @psalm-import-type FieldMapping from \Doctrine\ODM\MongoDB\Mapping\ClassMetadata - * @psalm-import-type Hints from UnitOfWork + * @phpstan-import-type FieldMapping from \Doctrine\ODM\MongoDB\Mapping\ClassMetadata + * @phpstan-import-type Hints from UnitOfWork * * @template TKey of array-key * @template T of object @@ -92,7 +92,7 @@ public function setDirty($dirty); * Sets the collection's owning document together with the AssociationMapping that * describes the association between the owner and the elements of the collection. * - * @psalm-param FieldMapping $mapping + * @phpstan-param FieldMapping $mapping * * @return void */ @@ -150,13 +150,13 @@ public function getOwner(): ?object; /** * @return array - * @psalm-return FieldMapping + * @phpstan-return FieldMapping */ public function getMapping(); /** * @return ClassMetadata - * @psalm-return ClassMetadata + * @phpstan-return ClassMetadata * * @throws MongoDBException */ diff --git a/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionTrait.php b/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionTrait.php index 1ccb2aab0..79dae2956 100644 --- a/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionTrait.php +++ b/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionTrait.php @@ -28,8 +28,8 @@ /** * Trait with methods needed to implement PersistentCollectionInterface. * - * @psalm-import-type Hints from UnitOfWork - * @psalm-import-type FieldMapping from ClassMetadata + * @phpstan-import-type Hints from UnitOfWork + * @phpstan-import-type FieldMapping from ClassMetadata * @template TKey of array-key * @template T of object */ @@ -50,7 +50,7 @@ trait PersistentCollectionTrait /** * @var array|null - * @psalm-var FieldMapping|null + * @phpstan-var FieldMapping|null */ private ?array $mapping = null; @@ -93,7 +93,7 @@ trait PersistentCollectionTrait * Any hints to account for during reconstitution/lookup of the documents. * * @var array - * @psalm-var Hints + * @phpstan-var Hints */ private array $hints = []; @@ -129,7 +129,7 @@ public function initialize() return; } - /** @psalm-var array $newObjects */ + /** @var array $newObjects */ $newObjects = []; if ($this->isDirty) { @@ -363,7 +363,7 @@ public function exists(Closure $p) } /** - * @psalm-return (TMaybeContained is T ? TKey|false : false) + * @phpstan-return (TMaybeContained is T ? TKey|false : false) * * @template TMaybeContained */ @@ -422,7 +422,7 @@ public function isEmpty() /** * @return Traversable - * @psalm-return Traversable + * @phpstan-return Traversable */ #[ReturnTypeWillChange] public function getIterator() @@ -535,7 +535,7 @@ public function offsetExists($offset) * @param mixed $offset * * @return mixed - * @psalm-return T|null + * @phpstan-return T|null */ #[ReturnTypeWillChange] public function offsetGet($offset) @@ -656,7 +656,7 @@ private function doAdd($value, $arrayAccess) * @param mixed $offset * * @return bool|T|null - * @psalm-return ( + * @phpstan-return ( * $arrayAccess is false * ? T|null * : T|true|null @@ -728,9 +728,9 @@ private function needsSchedulingForSynchronization(): bool } /** - * @psalm-param Closure(TKey, T):bool $p + * @phpstan-param Closure(TKey, T):bool $p * - * @psalm-return T|null + * @phpstan-return T|null */ public function findFirst(Closure $p) { @@ -742,13 +742,13 @@ public function findFirst(Closure $p) } /** - * @psalm-param Closure(TReturn|TInitial|null, T):(TInitial|TReturn) $func - * @psalm-param TInitial|null $initial + * @phpstan-param Closure(TReturn|TInitial|null, T):(TInitial|TReturn) $func + * @phpstan-param TInitial|null $initial * - * @psalm-return TReturn|TInitial|null + * @phpstan-return TReturn|TInitial|null * - * @psalm-template TReturn - * @psalm-template TInitial + * @phpstan-template TReturn + * @phpstan-template TInitial */ public function reduce(Closure $func, $initial = null) { diff --git a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php index 780c0c600..e2ddfcca7 100644 --- a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php +++ b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php @@ -74,7 +74,7 @@ * * @template T of object * - * @psalm-type CommitOptions array{ + * @phpstan-type CommitOptions array{ * fsync?: bool, * safe?: int, * session?: ?Session, @@ -82,10 +82,10 @@ * withTransaction?: bool, * writeConcern?: WriteConcern * } - * @psalm-import-type Hints from UnitOfWork - * @psalm-import-type FieldMapping from ClassMetadata - * @psalm-import-type SortMeta from Sort - * @psalm-import-type SortShape from Sort + * @phpstan-import-type Hints from UnitOfWork + * @phpstan-import-type FieldMapping from ClassMetadata + * @phpstan-import-type SortMeta from Sort + * @phpstan-import-type SortShape from Sort */ final class DocumentPersister { @@ -111,7 +111,7 @@ final class DocumentPersister private CollectionPersister $cp; - /** @psalm-param ClassMetadata $class */ + /** @phpstan-param ClassMetadata $class */ public function __construct( private PersistenceBuilder $pb, private DocumentManager $dm, @@ -193,7 +193,7 @@ public function getClassMetadata(): ClassMetadata * * If no inserts are queued, invoking this method is a NOOP. * - * @psalm-param CommitOptions $options + * @phpstan-param CommitOptions $options * * @throws DriverException */ @@ -252,7 +252,7 @@ public function executeInserts(array $options = []): void * * If no upserts are queued, invoking this method is a NOOP. * - * @psalm-param CommitOptions $options + * @phpstan-param CommitOptions $options */ public function executeUpserts(array $options = []): void { @@ -351,7 +351,7 @@ private function executeUpsert(object $document, array $options): void /** * Updates the already persisted document if it has any new changesets. * - * @psalm-param CommitOptions $options + * @phpstan-param CommitOptions $options * * @throws LockException */ @@ -416,7 +416,7 @@ public function update(object $document, array $options = []): void /** * Removes document from mongo * - * @psalm-param CommitOptions $options + * @phpstan-param CommitOptions $options * * @throws LockException */ @@ -471,11 +471,11 @@ public function refresh(object $document): void * * @param array|scalar|ObjectId|null $criteria Query criteria * @param array>|null $sort - * @psalm-param SortShape|null $sort - * @psalm-param Hints $hints - * @psalm-param T|null $document + * @param T|null $document + * @phpstan-param SortShape|null $sort + * @phpstan-param Hints $hints * - * @psalm-return T|null + * @return T|null * * @throws LockException * @@ -638,11 +638,11 @@ public function unlock(object $document): void * @param array $result The query result. * @param object|null $document The document object to fill, if any. * @param array $hints Hints for document creation. - * @psalm-param Hints $hints - * @psalm-param T|null $document + * @phpstan-param Hints $hints + * @phpstan-param T|null $document * * @return object The filled and managed document object. - * @psalm-return T + * @phpstan-return T */ private function createDocument(array $result, ?object $document = null, array $hints = []): object { @@ -947,9 +947,9 @@ private function getSortDirection($sort) * names and changing direction strings to int. * * @param array> $fields - * @psalm-param SortShape $fields + * @phpstan-param SortShape $fields * - * @psalm-return array + * @phpstan-return array */ public function prepareSort(array $fields): array { @@ -1458,7 +1458,7 @@ private function hasQueryOperators($value): bool /** * Returns the list of discriminator values for the given ClassMetadata * - * @psalm-return list + * @return list */ private function getClassDiscriminatorValues(ClassMetadata $metadata): array { @@ -1561,9 +1561,9 @@ private function getQueryForDocument(object $document): array } /** - * @psalm-param CommitOptions $options + * @phpstan-param CommitOptions $options * - * @psalm-return CommitOptions + * @phpstan-return CommitOptions */ private function getWriteOptions(array $options = []): array { @@ -1605,9 +1605,9 @@ private function isInTransaction(array $options): bool } /** - * @psalm-param FieldMapping $mapping + * @phpstan-param FieldMapping $mapping * - * @psalm-return array * }> diff --git a/lib/Doctrine/ODM/MongoDB/Persisters/PersistenceBuilder.php b/lib/Doctrine/ODM/MongoDB/Persisters/PersistenceBuilder.php index 6ceaae37d..80c257d66 100644 --- a/lib/Doctrine/ODM/MongoDB/Persisters/PersistenceBuilder.php +++ b/lib/Doctrine/ODM/MongoDB/Persisters/PersistenceBuilder.php @@ -26,7 +26,7 @@ * * @internal * - * @psalm-import-type FieldMapping from ClassMetadata + * @phpstan-import-type FieldMapping from ClassMetadata */ final class PersistenceBuilder { @@ -322,7 +322,7 @@ public function prepareUpsertData($document) * simple reference, null may be returned. * * @param object $document - * @psalm-param FieldMapping $referenceMapping + * @phpstan-param FieldMapping $referenceMapping * * @return array|null */ @@ -347,7 +347,7 @@ public function prepareReferencedDocumentValue(array $referenceMapping, $documen * * @param object $embeddedDocument * @param bool $includeNestedCollections - * @psalm-param FieldMapping $embeddedMapping + * @phpstan-param FieldMapping $embeddedMapping * * @return array|object * @@ -462,7 +462,7 @@ public function prepareEmbeddedDocumentValue(array $embeddedMapping, $embeddedDo * * @param object $document * @param bool $includeNestedCollections - * @psalm-param FieldMapping $mapping + * @phpstan-param FieldMapping $mapping * * @return mixed[]|object|null * diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php index 35b292281..1540e40e9 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php @@ -17,9 +17,9 @@ public function generateProxyClasses(array $classes): int; * the given identifier. * * @param mixed $identifier - * @psalm-param ClassMetadata $metadata + * @phpstan-param ClassMetadata $metadata * - * @psalm-return T&GhostObjectInterface + * @return T&GhostObjectInterface * * @template T of object */ diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php index 737795fe4..13fd4bd3e 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php @@ -38,9 +38,9 @@ public function __construct(DocumentManager $documentManager) /** * @param mixed $identifier - * @psalm-param ClassMetadata $metadata + * @phpstan-param ClassMetadata $metadata * - * @psalm-return T&GhostObjectInterface + * @return T&GhostObjectInterface * * @template T of object */ @@ -86,7 +86,7 @@ public function generateProxyClasses(array $classes): int * @param ClassMetadata $metadata * @param DocumentPersister $documentPersister * - * @psalm-return Closure( + * @phpstan-return Closure( * TDocument&GhostObjectInterface=, * string=, * array=, diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ClassNameResolver.php b/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ClassNameResolver.php index 36ffaafc5..f869de0eb 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ClassNameResolver.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ClassNameResolver.php @@ -10,7 +10,7 @@ interface ClassNameResolver /** * Gets the real class name of a class name that could be a proxy. * - * @psalm-param class-string $class + * @param class-string $class */ public function getRealClass(string $class): string; } diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ProxyManagerClassNameResolver.php b/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ProxyManagerClassNameResolver.php index c281cfc12..08c9f704f 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ProxyManagerClassNameResolver.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ProxyManagerClassNameResolver.php @@ -22,11 +22,11 @@ public function getRealClass(string $class): string } /** - * @psalm-param class-string|class-string> $className + * @param class-string|class-string> $className * - * @psalm-return class-string + * @return class-string * - * @psalm-template RealClassName of object + * @phpstan-template RealClassName of object */ public function resolveClassName(string $className): string { diff --git a/lib/Doctrine/ODM/MongoDB/Query/Builder.php b/lib/Doctrine/ODM/MongoDB/Query/Builder.php index 7e249a5a9..7be346ea8 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Query/Builder.php @@ -30,8 +30,8 @@ /** * Query builder for ODM. * - * @psalm-import-type QueryShape from Query - * @psalm-import-type SortMetaKeywords from Sort + * @phpstan-import-type QueryShape from Query + * @phpstan-import-type SortMetaKeywords from Sort */ class Builder { @@ -85,7 +85,7 @@ class Builder /** * Array containing the query data. * - * @psalm-var QueryShape + * @phpstan-var QueryShape */ private array $query = ['type' => Query::TYPE_FIND]; @@ -1488,7 +1488,7 @@ public function sort($fieldName, $order = 1): self * * @see https://docs.mongodb.com/manual/reference/operator/projection/meta/#sort * - * @psalm-param SortMetaKeywords $metaDataKeyword + * @phpstan-param SortMetaKeywords $metaDataKeyword */ public function sortMeta(string $fieldName, string $metaDataKeyword): self { @@ -1596,7 +1596,7 @@ public function where($javascript): self /** * Get Discriminator Values * - * @psalm-param class-string[] $classNames + * @param class-string[] $classNames * * @return array * @@ -1620,10 +1620,7 @@ private function getDiscriminatorValues(array $classNames): array return $discriminatorValues; } - /** - * @param string[]|string|null $documentName an array of document names or just one. - * @psalm-param class-string[]|class-string|null $documentName - */ + /** @param class-string[]|class-string|null $documentName an array of document names or just one. */ private function setDocumentName($documentName): void { if (is_array($documentName)) { diff --git a/lib/Doctrine/ODM/MongoDB/Query/Expr.php b/lib/Doctrine/ODM/MongoDB/Query/Expr.php index ca96ca4fe..f2e6bbb3e 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/Expr.php +++ b/lib/Doctrine/ODM/MongoDB/Query/Expr.php @@ -34,7 +34,7 @@ /** * Query expression builder for ODM. * - * @psalm-import-type FieldMapping from ClassMetadata + * @phpstan-import-type FieldMapping from ClassMetadata */ class Expr { diff --git a/lib/Doctrine/ODM/MongoDB/Query/Query.php b/lib/Doctrine/ODM/MongoDB/Query/Query.php index 267fb1134..bf3372bb6 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/Query.php +++ b/lib/Doctrine/ODM/MongoDB/Query/Query.php @@ -43,7 +43,7 @@ * ODM Query wraps the raw Doctrine MongoDB queries to add additional functionality * and to hydrate the raw arrays of data to Doctrine document objects. * - * @psalm-type QueryShape = array{ + * @phpstan-type QueryShape array{ * distinct?: string, * hint?: string|array, * limit?: int, @@ -60,8 +60,8 @@ * type: Query::TYPE_*, * upsert?: bool, * } - * @psalm-import-type Hints from UnitOfWork - * @psalm-import-type SortMeta from Sort + * @phpstan-import-type Hints from UnitOfWork + * @phpstan-import-type SortMeta from Sort */ final class Query implements IteratorAggregate { @@ -106,7 +106,7 @@ final class Query implements IteratorAggregate /** * Hints for UnitOfWork behavior. * - * @psalm-var Hints + * @phpstan-var Hints */ private array $unitOfWorkHints = []; @@ -118,7 +118,7 @@ final class Query implements IteratorAggregate /** * Query structure generated by the Builder class. * - * @psalm-var QueryShape + * @phpstan-var QueryShape */ private array $query; diff --git a/lib/Doctrine/ODM/MongoDB/Query/QueryExpressionVisitor.php b/lib/Doctrine/ODM/MongoDB/Query/QueryExpressionVisitor.php index 599ebb0ba..83ff34182 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/QueryExpressionVisitor.php +++ b/lib/Doctrine/ODM/MongoDB/Query/QueryExpressionVisitor.php @@ -38,7 +38,7 @@ final class QueryExpressionVisitor extends ExpressionVisitor /** * Map Criteria API composite types to query builder methods * - * @psalm-var array + * @var array */ private static array $compositeMethods = [ CompositeExpression::TYPE_AND => 'addAnd', diff --git a/lib/Doctrine/ODM/MongoDB/Query/ReferencePrimer.php b/lib/Doctrine/ODM/MongoDB/Query/ReferencePrimer.php index ab39bff8a..ff3478cf8 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/ReferencePrimer.php +++ b/lib/Doctrine/ODM/MongoDB/Query/ReferencePrimer.php @@ -37,8 +37,8 @@ * * @internal * - * @psalm-import-type FieldMapping from ClassMetadata - * @psalm-import-type Hints from UnitOfWork + * @phpstan-import-type FieldMapping from ClassMetadata + * @phpstan-import-type Hints from UnitOfWork */ final class ReferencePrimer { @@ -70,7 +70,7 @@ public function __construct(DocumentManager $dm, UnitOfWork $uow) * @param string $fieldName Field name containing references to prime * @param array $hints UnitOfWork hints for priming queries * @param callable|null $primer Optional primer callable - * @psalm-param Hints $hints + * @phpstan-param Hints $hints * * @throws InvalidArgumentException If the mapped field is not the owning * side of a reference relationship. @@ -121,7 +121,7 @@ public function primeReferences(ClassMetadata $class, $documents, string $fieldN } } - /** @psalm-var class-string $className */ + /** @var class-string $className */ foreach ($groupedIds as $className => $ids) { $refClass = $this->dm->getClassMetadata($className); call_user_func($primer, $this->dm, $refClass, array_values($ids), $hints); @@ -216,7 +216,7 @@ private function parseDotSyntaxForPrimer(string $fieldName, ClassMetadata $class * infer the class of the referenced documents. * * @param PersistentCollectionInterface $persistentCollection - * @psalm-param array> $groupedIds + * @param array> $groupedIds */ private function addManyReferences(PersistentCollectionInterface $persistentCollection, array &$groupedIds): void { diff --git a/lib/Doctrine/ODM/MongoDB/Repository/AbstractRepositoryFactory.php b/lib/Doctrine/ODM/MongoDB/Repository/AbstractRepositoryFactory.php index 065ba2635..9d500dcd7 100644 --- a/lib/Doctrine/ODM/MongoDB/Repository/AbstractRepositoryFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Repository/AbstractRepositoryFactory.php @@ -26,9 +26,9 @@ abstract class AbstractRepositoryFactory implements RepositoryFactory private array $repositoryList = []; /** - * @psalm-param class-string $documentName + * @param class-string $documentName * - * @psalm-return DocumentRepository|GridFSRepository|ViewRepository + * @phpstan-return DocumentRepository|GridFSRepository|ViewRepository * * @template T of object */ @@ -51,10 +51,10 @@ public function getRepository(DocumentManager $documentManager, string $document /** * Create a new repository instance for a document class. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @return DocumentRepository|GridFSRepository|ViewRepository - * @psalm-return DocumentRepository|GridFSRepository|ViewRepository + * @phpstan-return DocumentRepository|GridFSRepository|ViewRepository * * @template T of object */ @@ -104,7 +104,7 @@ protected function createRepository(DocumentManager $documentManager, string $do * Instantiates requested repository. * * @param ClassMetadata $metadata - * @psalm-param class-string $repositoryClassName + * @param class-string $repositoryClassName * * @return ObjectRepository * diff --git a/lib/Doctrine/ODM/MongoDB/Repository/DefaultGridFSRepository.php b/lib/Doctrine/ODM/MongoDB/Repository/DefaultGridFSRepository.php index 586c56ab1..030fb112f 100644 --- a/lib/Doctrine/ODM/MongoDB/Repository/DefaultGridFSRepository.php +++ b/lib/Doctrine/ODM/MongoDB/Repository/DefaultGridFSRepository.php @@ -86,7 +86,7 @@ private function getDocumentBucket(): Bucket } /** - * @psalm-return array{ + * @return array{ * _id?: mixed, * chunkSizeBytes?: int, * metadata?: object diff --git a/lib/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php b/lib/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php index 93cf4e453..7fcc37429 100644 --- a/lib/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php +++ b/lib/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php @@ -38,10 +38,7 @@ */ class DocumentRepository implements ObjectRepository, Selectable { - /** - * @var string - * @psalm-var class-string - */ + /** @var class-string */ protected $documentName; /** @var DocumentManager */ @@ -50,10 +47,7 @@ class DocumentRepository implements ObjectRepository, Selectable /** @var UnitOfWork */ protected $uow; - /** - * @var ClassMetadata - * @psalm-var ClassMetadata - */ + /** @var ClassMetadata */ protected $class; /** @@ -62,7 +56,7 @@ class DocumentRepository implements ObjectRepository, Selectable * @param DocumentManager $dm The DocumentManager to use. * @param UnitOfWork $uow The UnitOfWork to use. * @param ClassMetadata $classMetadata The class metadata. - * @psalm-param ClassMetadata $classMetadata The class metadata. + * @phpstan-param ClassMetadata $classMetadata The class metadata. */ public function __construct(DocumentManager $dm, UnitOfWork $uow, ClassMetadata $classMetadata) { @@ -111,7 +105,7 @@ public function clear(): void * * @param mixed $id Identifier. * - * @psalm-return T|null + * @return T|null * * @throws MappingException * @throws LockException @@ -190,15 +184,14 @@ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $ * @param array|null $sort * @param array $criteria * - * @return object|null The object. - * @psalm-return T|null + * @return T|null The object. */ public function findOneBy(array $criteria, ?array $sort = null): ?object { return $this->getDocumentPersister()->load($criteria, null, [], 0, $sort); } - /** @psalm-return class-string */ + /** @return class-string */ public function getDocumentName(): string { return $this->documentName; @@ -209,13 +202,13 @@ public function getDocumentManager(): DocumentManager return $this->dm; } - /** @psalm-return ClassMetadata */ + /** @return ClassMetadata */ public function getClassMetadata(): ClassMetadata { return $this->class; } - /** @psalm-return class-string */ + /** @return class-string */ public function getClassName(): string { return $this->getDocumentName(); @@ -257,7 +250,7 @@ public function matching(Criteria $criteria): ArrayCollection return new ArrayCollection($iterator->toArray()); } - /** @psalm-return DocumentPersister */ + /** @return DocumentPersister */ protected function getDocumentPersister(): DocumentPersister { return $this->uow->getDocumentPersister($this->documentName); diff --git a/lib/Doctrine/ODM/MongoDB/Repository/RepositoryFactory.php b/lib/Doctrine/ODM/MongoDB/Repository/RepositoryFactory.php index ee5e681ff..24036aa77 100644 --- a/lib/Doctrine/ODM/MongoDB/Repository/RepositoryFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Repository/RepositoryFactory.php @@ -15,9 +15,9 @@ interface RepositoryFactory /** * Gets the repository for a document class. * - * @psalm-param class-string $documentName + * @param class-string $documentName * - * @psalm-return ObjectRepository + * @return ObjectRepository * * @template T of object */ diff --git a/lib/Doctrine/ODM/MongoDB/SchemaManager.php b/lib/Doctrine/ODM/MongoDB/SchemaManager.php index b142f1902..63a7bd7c7 100644 --- a/lib/Doctrine/ODM/MongoDB/SchemaManager.php +++ b/lib/Doctrine/ODM/MongoDB/SchemaManager.php @@ -36,8 +36,8 @@ use function str_contains; /** - * @psalm-import-type IndexMapping from ClassMetadata - * @psalm-import-type IndexOptions from ClassMetadata + * @phpstan-import-type IndexMapping from ClassMetadata + * @phpstan-import-type IndexOptions from ClassMetadata */ final class SchemaManager { @@ -103,7 +103,7 @@ public function updateIndexes(?int $maxTimeMs = null, ?WriteConcern $writeConcer * Indexes that exist in MongoDB but not the document metadata will be * deleted. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -150,9 +150,9 @@ public function updateDocumentIndexes(string $documentName, ?int $maxTimeMs = nu } /** - * @psalm-param class-string $documentName + * @param class-string $documentName * - * @psalm-return IndexMapping[] + * @phpstan-return IndexMapping[] */ public function getDocumentIndexes(string $documentName): array { @@ -162,10 +162,10 @@ public function getDocumentIndexes(string $documentName): array } /** - * @psalm-param class-string $documentName - * @psalm-param array $visited + * @param class-string $documentName + * @param array $visited * - * @psalm-return IndexMapping[] + * @phpstan-return IndexMapping[] */ private function doGetDocumentIndexes(string $documentName, array &$visited): array { @@ -233,7 +233,7 @@ private function doGetDocumentIndexes(string $documentName, array &$visited): ar /** * @param ClassMetadata $class * - * @psalm-return IndexMapping[] + * @phpstan-return IndexMapping[] */ private function prepareIndexes(ClassMetadata $class): array { @@ -266,7 +266,7 @@ private function prepareIndexes(ClassMetadata $class): array /** * Ensure the given document's indexes are created. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -310,7 +310,7 @@ public function deleteIndexes(?int $maxTimeMs = null, ?WriteConcern $writeConcer /** * Delete the given document's indexes. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -341,7 +341,7 @@ public function createSearchIndexes(): void /** * Create search indexes for the given document class. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -396,7 +396,7 @@ public function updateSearchIndexes(): void * Search indexes will be updated using the definitions in the document * metadata. Search indexes not defined in the metadata will be deleted. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -451,7 +451,7 @@ public function deleteSearchIndexes(): void /** * Delete search indexes for the given document class. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -497,7 +497,7 @@ public function updateValidators(?int $maxTimeMs = null, ?WriteConcern $writeCon /** * Ensure collection validators are up to date for the mapped document class. * - * @psalm-param class-string $documentName + * @param class-string $documentName */ public function updateDocumentValidator(string $documentName, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null): void { @@ -566,7 +566,7 @@ public function createCollections(?int $maxTimeMs = null, ?WriteConcern $writeCo /** * Create the document collection for a mapped class. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -644,7 +644,7 @@ public function dropCollections(?int $maxTimeMs = null, ?WriteConcern $writeConc /** * Drop the document collection for a mapped class. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -683,7 +683,7 @@ public function dropDatabases(?int $maxTimeMs = null, ?WriteConcern $writeConcer /** * Drop the document database for a mapped class. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws InvalidArgumentException */ @@ -697,7 +697,7 @@ public function dropDocumentDatabase(string $documentName, ?int $maxTimeMs = nul $this->dm->getDocumentDatabase($documentName)->drop($this->getWriteOptions($maxTimeMs, $writeConcern)); } - /** @psalm-param IndexMapping $documentIndex */ + /** @phpstan-param IndexMapping $documentIndex */ public function isMongoIndexEquivalentToDocumentIndex(IndexInfo $mongoIndex, array $documentIndex): bool { return $this->isEquivalentIndexKeys($mongoIndex, $documentIndex) && $this->isEquivalentIndexOptions($mongoIndex, $documentIndex); @@ -707,7 +707,7 @@ public function isMongoIndexEquivalentToDocumentIndex(IndexInfo $mongoIndex, arr * Determine if the keys for a MongoDB index can be considered equivalent to * those for an index in class metadata. * - * @psalm-param IndexMapping $documentIndex + * @phpstan-param IndexMapping $documentIndex */ private function isEquivalentIndexKeys(IndexInfo $mongoIndex, array $documentIndex): bool { @@ -735,7 +735,7 @@ private function isEquivalentIndexKeys(IndexInfo $mongoIndex, array $documentInd $mongoIndexKeys == $documentIndexKeys; } - /** @psalm-param IndexMapping $documentIndex */ + /** @phpstan-param IndexMapping $documentIndex */ private function hasTextIndexesAtSamePosition(IndexInfo $mongoIndex, array $documentIndex): bool { $mongoIndexKeys = $mongoIndex['key']; @@ -771,7 +771,7 @@ private function hasTextIndexesAtSamePosition(IndexInfo $mongoIndex, array $docu * The background option is only relevant to index creation and is not * considered. * - * @psalm-param IndexMapping $documentIndex + * @phpstan-param IndexMapping $documentIndex */ private function isEquivalentIndexOptions(IndexInfo $mongoIndex, array $documentIndex): bool { @@ -836,8 +836,8 @@ private function isEquivalentIndexOptions(IndexInfo $mongoIndex, array $document * Options added to the ALLOWED_MISSING_INDEX_OPTIONS constant are ignored * and are expected to be checked later * - * @psalm-param IndexOptions $mongoIndexOptions - * @psalm-param IndexOptions $documentIndexOptions + * @phpstan-param IndexOptions $mongoIndexOptions + * @phpstan-param IndexOptions $documentIndexOptions */ private function indexOptionsAreMissing(array $mongoIndexOptions, array $documentIndexOptions): bool { @@ -852,7 +852,7 @@ private function indexOptionsAreMissing(array $mongoIndexOptions, array $documen * Determine if the text index weights for a MongoDB index can be considered * equivalent to those for an index in class metadata. * - * @psalm-param IndexMapping $documentIndex + * @phpstan-param IndexMapping $documentIndex */ private function isEquivalentTextIndexWeights(IndexInfo $mongoIndex, array $documentIndex): bool { @@ -900,7 +900,7 @@ public function ensureSharding(?WriteConcern $writeConcern = null): void /** * Ensure sharding for collection by document name. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws MongoDBException */ @@ -927,7 +927,7 @@ public function ensureDocumentSharding(string $documentName, ?WriteConcern $writ /** * Enable sharding for database which contains documents with given name. * - * @psalm-param class-string $documentName + * @param class-string $documentName * * @throws MongoDBException */ @@ -948,7 +948,7 @@ public function enableShardingForDbByDocumentName(string $documentName): void } } - /** @psalm-param class-string $documentName */ + /** @param class-string $documentName */ private function runShardCollectionCommand(string $documentName, ?WriteConcern $writeConcern = null): void { $class = $this->dm->getClassMetadata($documentName); @@ -1019,7 +1019,7 @@ private function ensureFilesIndex(ClassMetadata $class, ?int $maxTimeMs = null, $filesCollection->createIndex(self::GRIDFS_CHUNKS_COLLECTION_INDEX, $this->getWriteOptions($maxTimeMs, $writeConcern, ['background' => $background])); } - /** @psalm-param class-string $documentName */ + /** @param class-string $documentName */ private function collectionIsSharded(string $documentName): bool { $class = $this->dm->getClassMetadata($documentName); diff --git a/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/AbstractCommand.php b/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/AbstractCommand.php index 2bd59a14e..8b6481b80 100644 --- a/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/AbstractCommand.php +++ b/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/AbstractCommand.php @@ -103,7 +103,7 @@ protected function processSearchIndex(SchemaManager $sm): void } /** - * @psalm-param class-string $document + * @param class-string $document * * @throws BadMethodCallException */ diff --git a/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/ShardCommand.php b/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/ShardCommand.php index 6394064d4..25bc6ba07 100644 --- a/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/ShardCommand.php +++ b/lib/Doctrine/ODM/MongoDB/Tools/Console/Command/Schema/ShardCommand.php @@ -53,7 +53,7 @@ private function doExecute(InputInterface $input, OutputInterface $output): int return $isErrored ? 255 : 0; } - /** @psalm-param class-string $document */ + /** @param class-string $document */ private function processDocumentSharding(SchemaManager $sm, string $document, ?WriteConcern $writeConcern = null): void { $sm->ensureDocumentSharding($document, $writeConcern); diff --git a/lib/Doctrine/ODM/MongoDB/Tools/ResolveTargetDocumentListener.php b/lib/Doctrine/ODM/MongoDB/Tools/ResolveTargetDocumentListener.php index 45269dcc6..d2e74fcee 100644 --- a/lib/Doctrine/ODM/MongoDB/Tools/ResolveTargetDocumentListener.php +++ b/lib/Doctrine/ODM/MongoDB/Tools/ResolveTargetDocumentListener.php @@ -20,11 +20,11 @@ * * Mechanism to overwrite document interfaces or classes specified as association targets. * - * @psalm-import-type AssociationFieldMapping from ClassMetadata + * @phpstan-import-type AssociationFieldMapping from ClassMetadata */ class ResolveTargetDocumentListener implements EventSubscriber { - /** @psalm-var array */ + /** @var array */ private array $resolveTargetDocuments = []; public function getSubscribedEvents() @@ -38,7 +38,7 @@ public function getSubscribedEvents() /** * Add a target-document class name to resolve to a new class name. * - * @psalm-param array{targetDocument?: class-string} $mapping + * @param array{targetDocument?: class-string} $mapping */ public function addResolveTargetDocument(string $originalDocument, string $newDocument, array $mapping): void { @@ -46,7 +46,7 @@ public function addResolveTargetDocument(string $originalDocument, string $newDo $this->resolveTargetDocuments[$this->getRealClassName($originalDocument)] = $mapping; } - /** @psalm-return class-string */ + /** @return class-string */ private function getRealClassName(string $className): string { return ltrim($className, '\\'); @@ -88,7 +88,7 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $args): void /** * @param ClassMetadata $classMetadata - * @psalm-param AssociationFieldMapping $mapping + * @phpstan-param AssociationFieldMapping $mapping */ private function remapAssociation(ClassMetadata $classMetadata, array $mapping): void { diff --git a/lib/Doctrine/ODM/MongoDB/Types/Type.php b/lib/Doctrine/ODM/MongoDB/Types/Type.php index 90a795bbe..b8b308c11 100644 --- a/lib/Doctrine/ODM/MongoDB/Types/Type.php +++ b/lib/Doctrine/ODM/MongoDB/Types/Type.php @@ -57,10 +57,7 @@ abstract class Type /** @var Type[] Map of already instantiated type objects. One instance per type (flyweight). */ private static array $typeObjects = []; - /** - * @var string[] The map of supported doctrine mapping types. - * @psalm-var array - */ + /** @var array The map of supported doctrine mapping types. */ private static array $typesMap = [ self::ID => Types\IdType::class, self::INTID => Types\IntIdType::class, @@ -203,7 +200,7 @@ public static function convertPHPToDatabaseValue($value) /** * Adds a custom type to the type map. * - * @psalm-param class-string $className + * @param class-string $className * * @throws MappingException * @@ -231,7 +228,7 @@ public static function hasType(string $name): bool /** * Overrides an already defined type to use a different implementation. * - * @psalm-param class-string $className + * @param class-string $className * * @throws MappingException * @@ -250,7 +247,7 @@ public static function overrideType(string $name, string $className): void * Get the types array map which holds all registered types and the corresponding * type class * - * @psalm-return array + * @phpstan-return array */ public static function getTypesMap(): array { diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index eb9591f99..f7a728693 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -56,14 +56,14 @@ * "object-level" transaction and for writing out changes to the database * in the correct order. * - * @psalm-import-type FieldMapping from ClassMetadata - * @psalm-import-type AssociationFieldMapping from ClassMetadata - * @psalm-type ChangeSet = array{ + * @phpstan-import-type FieldMapping from ClassMetadata + * @phpstan-import-type AssociationFieldMapping from ClassMetadata + * @phpstan-type ChangeSet array{ * 0: mixed, * 1: mixed * } - * @psalm-type Hints = array - * @psalm-type CommitOptions array{ + * @phpstan-type Hints array + * @phpstan-type CommitOptions array{ * fsync?: bool, * safe?: int, * w?: int, @@ -118,7 +118,7 @@ final class UnitOfWork implements PropertyChangedListener * Since all classes in a hierarchy must share the same identifier set, * we always take the root class name of the hierarchy. * - * @psalm-var array> + * @var array> */ private array $identityMap = []; @@ -147,7 +147,7 @@ final class UnitOfWork implements PropertyChangedListener * Map of document changes. Keys are object ids (spl_object_hash). * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end. * - * @psalm-var array> + * @var array> */ private array $documentChangeSets = []; @@ -155,7 +155,7 @@ final class UnitOfWork implements PropertyChangedListener * The (cached) states of any known documents. * Keys are object ids (spl_object_hash). * - * @psalm-var array + * @var array */ private array $documentStates = []; @@ -166,7 +166,7 @@ final class UnitOfWork implements PropertyChangedListener * object hash. This is only used for documents with a change tracking * policy of DEFERRED_EXPLICIT. * - * @psalm-var array> + * @var array> */ private array $scheduledForSynchronization = []; @@ -201,21 +201,21 @@ final class UnitOfWork implements PropertyChangedListener /** * All pending collection deletions. * - * @psalm-var array> + * @var array> */ private array $scheduledCollectionDeletions = []; /** * All pending collection updates. * - * @psalm-var array> + * @var array> */ private array $scheduledCollectionUpdates = []; /** * A list of documents related to collections scheduled for update or deletion * - * @psalm-var array>> + * @var array>> */ private array $hasScheduledCollections = []; @@ -224,7 +224,7 @@ final class UnitOfWork implements PropertyChangedListener * At the end of the UnitOfWork all these collections will make new snapshots * of their data. * - * @psalm-var array>> + * @var array>> */ private array $visitedCollections = []; @@ -253,7 +253,7 @@ final class UnitOfWork implements PropertyChangedListener /** * The document persister instances used to persist document instances. * - * @psalm-var array + * @var array */ private array $persisters = []; @@ -270,7 +270,7 @@ final class UnitOfWork implements PropertyChangedListener /** * Array of parent associations between embedded documents. * - * @psalm-var array + * @var array */ private array $parentAssociations = []; @@ -321,7 +321,7 @@ public function getPersistenceBuilder(): PersistenceBuilder * * @internal * - * @psalm-param FieldMapping $mapping + * @phpstan-param FieldMapping $mapping */ public function setParentAssociation(object $document, array $mapping, ?object $parent, string $propertyPath): void { @@ -337,7 +337,7 @@ public function setParentAssociation(object $document, array $mapping, ?object $ * list($mapping, $parent, $propertyPath) = $this->getParentAssociation($embeddedDocument); * * - * @psalm-return array{0: AssociationFieldMapping, 1: object|null, 2: string}|null + * @phpstan-return array{0: AssociationFieldMapping, 1: object|null, 2: string}|null */ public function getParentAssociation(object $document): ?array { @@ -349,9 +349,9 @@ public function getParentAssociation(object $document): ?array /** * Get the document persister instance for the given document name * - * @psalm-param class-string $documentName + * @param class-string $documentName * - * @psalm-return Persisters\DocumentPersister + * @return Persisters\DocumentPersister * * @template T of object */ @@ -363,7 +363,6 @@ public function getDocumentPersister(string $documentName): Persisters\DocumentP $this->persisters[$documentName] = new Persisters\DocumentPersister($pb, $this->dm, $this, $this->hydratorFactory, $class); } - /** @psalm-var Persisters\DocumentPersister */ return $this->persisters[$documentName]; } @@ -385,8 +384,8 @@ public function getCollectionPersister(): CollectionPersister * * @internal * - * @psalm-param class-string $documentName - * @psalm-param Persisters\DocumentPersister $persister + * @param class-string $documentName + * @phpstan-param Persisters\DocumentPersister $persister * * @template T of object */ @@ -407,7 +406,7 @@ public function setDocumentPersister(string $documentName, Persisters\DocumentPe * 3) All document deletions * * @param array $options Array of options to be used with batchInsert(), update() and remove() - * @psalm-param CommitOptions $options + * @phpstan-param CommitOptions $options */ public function commit(array $options = []): void { @@ -504,7 +503,7 @@ function (Session $session) use ($options): void { * * @param array $documents * - * @psalm-return array, 1: array}> + * @phpstan-return array, 1: array}> */ private function getClassesForCommitAction(array $documents, bool $includeEmbedded = false): array { @@ -583,7 +582,7 @@ private function computeScheduleUpsertsChangeSets(): void * Gets the changeset for a document. * * @return array array('property' => array(0 => mixed, 1 => mixed)) - * @psalm-return array + * @phpstan-return array */ public function getDocumentChangeSet(object $document): array { @@ -597,7 +596,7 @@ public function getDocumentChangeSet(object $document): array * * @internal * - * @psalm-param array $changeset + * @param array $changeset */ public function setDocumentChangeSet(object $document, array $changeset): void { @@ -667,8 +666,8 @@ public function getDocumentActualData(object $document): array * and any changes to its properties are detected, then a reference to the document is stored * there to mark it for an update. * - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @template T of object */ @@ -689,8 +688,8 @@ public function computeChangeSet(ClassMetadata $class, object $document): void /** * Used to do the common work of computeChangeSet and recomputeSingleDocumentChangeSet * - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @template T of object */ @@ -961,7 +960,7 @@ public function computeChangeSets(): void * Computes the changes of an association. * * @param mixed $value The value of the association. - * @psalm-param AssociationFieldMapping $assoc + * @phpstan-param AssociationFieldMapping $assoc * * @throws InvalidArgumentException */ @@ -1083,8 +1082,8 @@ private function computeAssociationChanges(object $parentDocument, array $assoc, * because this method is invoked during a commit cycle then the change sets are added. * whereby changes detected in this method prevail. * - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @throws InvalidArgumentException If the passed document is not MANAGED. * @@ -1111,8 +1110,8 @@ public function recomputeSingleDocumentChangeSet(ClassMetadata $class, object $d } /** - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @throws InvalidArgumentException If there is something wrong with document's identifier. * @@ -1165,9 +1164,9 @@ private function persistNew(ClassMetadata $class, object $document): void /** * Executes all document insertions for documents of the specified type. * - * @psalm-param ClassMetadata $class - * @psalm-param T[] $documents - * @psalm-param CommitOptions $options + * @phpstan-param ClassMetadata $class + * @phpstan-param T[] $documents + * @phpstan-param CommitOptions $options * * @template T of object */ @@ -1189,9 +1188,9 @@ private function executeInserts(ClassMetadata $class, array $documents, array $o /** * Executes all document upserts for documents of the specified type. * - * @psalm-param ClassMetadata $class - * @psalm-param T[] $documents - * @psalm-param CommitOptions $options + * @phpstan-param ClassMetadata $class + * @phpstan-param T[] $documents + * @phpstan-param CommitOptions $options * * @template T of object */ @@ -1213,9 +1212,9 @@ private function executeUpserts(ClassMetadata $class, array $documents, array $o /** * Executes all document updates for documents of the specified type. * - * @psalm-param ClassMetadata $class - * @psalm-param T[] $documents - * @psalm-param CommitOptions $options + * @phpstan-param ClassMetadata $class + * @phpstan-param T[] $documents + * @phpstan-param CommitOptions $options * * @template T of object */ @@ -1242,9 +1241,9 @@ private function executeUpdates(ClassMetadata $class, array $documents, array $o /** * Executes all document deletions for documents of the specified type. * - * @psalm-param ClassMetadata $class - * @psalm-param T[] $documents - * @psalm-param CommitOptions $options + * @phpstan-param ClassMetadata $class + * @phpstan-param T[] $documents + * @phpstan-param CommitOptions $options * * @template T of object */ @@ -1288,8 +1287,8 @@ private function executeDeletions(ClassMetadata $class, array $documents, array * * @internal * - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @throws InvalidArgumentException * @@ -1326,8 +1325,8 @@ public function scheduleForInsert(ClassMetadata $class, object $document): void * * @internal * - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @throws InvalidArgumentException * @@ -1614,15 +1613,13 @@ public function removeFromIdentityMap(object $document): bool * @internal * * @param mixed $id Document identifier - * @psalm-param ClassMetadata $class + * @phpstan-param ClassMetadata $class * - * @psalm-return T + * @phpstan-return T * * @throws InvalidArgumentException If the class does not have an identifier. * * @template T of object - * - * @psalm-suppress InvalidReturnStatement, InvalidReturnType because of the inability of defining a generic property map */ public function getById($id, ClassMetadata $class): object { @@ -1642,16 +1639,16 @@ public function getById($id, ClassMetadata $class): object * @internal * * @param mixed $id Document identifier - * @psalm-param ClassMetadata $class + * @phpstan-param ClassMetadata $class * * @return mixed The found document or FALSE. - * @psalm-return T|false + * @phpstan-return T|false * * @throws InvalidArgumentException If the class does not have an identifier. * * @template T of object * - * @psalm-suppress InvalidReturnStatement, InvalidReturnType because of the inability of defining a generic property map + * @ phpstan-suppress InvalidReturnStatement, InvalidReturnType because of the inability of defining a generic property map */ public function tryGetById($id, ClassMetadata $class) { @@ -1874,7 +1871,7 @@ public function merge(object $document): object * Executes a merge operation on a document. * * @param array $visited - * @psalm-param AssociationFieldMapping|null $assoc + * @phpstan-param AssociationFieldMapping|null $assoc * * @throws InvalidArgumentException If the entity instance is NEW. * @throws LockException If the document uses optimistic locking through a @@ -2463,11 +2460,11 @@ public function unscheduleOrphanRemoval(object $document): void * 3) NOP if state is OK * Returned collection should be used from now on (only important with 2nd point) * - * @psalm-param PersistentCollectionInterface $coll - * @psalm-param T $document - * @psalm-param ClassMetadata $class + * @phpstan-param PersistentCollectionInterface $coll + * @phpstan-param T $document + * @phpstan-param ClassMetadata $class * - * @psalm-return PersistentCollectionInterface + * @phpstan-return PersistentCollectionInterface * * @template T of object */ @@ -2497,7 +2494,7 @@ private function fixPersistentCollectionOwnership(PersistentCollectionInterface * * @internal * - * @psalm-param PersistentCollectionInterface $coll + * @phpstan-param PersistentCollectionInterface $coll */ public function scheduleCollectionDeletion(PersistentCollectionInterface $coll): void { @@ -2516,7 +2513,7 @@ public function scheduleCollectionDeletion(PersistentCollectionInterface $coll): * * @internal * - * @psalm-param PersistentCollectionInterface $coll + * @phpstan-param PersistentCollectionInterface $coll */ public function isCollectionScheduledForDeletion(PersistentCollectionInterface $coll): bool { @@ -2528,7 +2525,7 @@ public function isCollectionScheduledForDeletion(PersistentCollectionInterface $ * * @internal * - * @psalm-param PersistentCollectionInterface $coll + * @phpstan-param PersistentCollectionInterface $coll */ public function unscheduleCollectionDeletion(PersistentCollectionInterface $coll): void { @@ -2551,7 +2548,7 @@ public function unscheduleCollectionDeletion(PersistentCollectionInterface $coll * * @internal * - * @psalm-param PersistentCollectionInterface $coll + * @phpstan-param PersistentCollectionInterface $coll */ public function scheduleCollectionUpdate(PersistentCollectionInterface $coll): void { @@ -2577,7 +2574,7 @@ public function scheduleCollectionUpdate(PersistentCollectionInterface $coll): v * * @internal * - * @psalm-param PersistentCollectionInterface $coll + * @phpstan-param PersistentCollectionInterface $coll */ public function unscheduleCollectionUpdate(PersistentCollectionInterface $coll): void { @@ -2600,7 +2597,7 @@ public function unscheduleCollectionUpdate(PersistentCollectionInterface $coll): * * @internal * - * @psalm-param PersistentCollectionInterface $coll + * @phpstan-param PersistentCollectionInterface $coll */ public function isCollectionScheduledForUpdate(PersistentCollectionInterface $coll): bool { @@ -2614,7 +2611,7 @@ public function isCollectionScheduledForUpdate(PersistentCollectionInterface $co * @internal * * @return PersistentCollectionInterface[] - * @psalm-return array> + * @phpstan-return array> */ public function getVisitedCollections(object $document): array { @@ -2628,8 +2625,7 @@ public function getVisitedCollections(object $document): array * * @internal * - * @return PersistentCollectionInterface[] - * @psalm-return array> + * @return array> */ public function getScheduledCollections(object $document): array { @@ -2660,7 +2656,7 @@ public function hasScheduledCollections(object $document): bool * unscheduled and atomic one is scheduled for update instead. This makes * calculating update data way easier. * - * @psalm-param PersistentCollectionInterface $coll + * @phpstan-param PersistentCollectionInterface $coll */ private function scheduleCollectionOwner(PersistentCollectionInterface $coll): void { @@ -2728,12 +2724,12 @@ public function getOwningDocument(object $document): object /** * Creates a document. Used for reconstitution of documents during hydration. * - * @psalm-param class-string $className - * @psalm-param array $data - * @psalm-param T|null $document - * @psalm-param Hints $hints + * @param class-string $className + * @param array $data + * @param T|null $document + * @phpstan-param Hints $hints * - * @psalm-return T + * @return T * * @template T of object */ @@ -2750,7 +2746,7 @@ public function getOrCreateDocument(string $className, array $data, array &$hint } if ($discriminatorValue !== null) { - /** @psalm-var class-string $className */ + /** @var class-string $className */ $className = $class->discriminatorMap[$discriminatorValue] ?? $discriminatorValue; $class = $this->dm->getClassMetadata($className); @@ -2759,7 +2755,7 @@ public function getOrCreateDocument(string $className, array $data, array &$hint } if (! empty($hints[Query::HINT_READ_ONLY])) { - /** @psalm-var T $document */ + /** @phpstan-var T $document */ $document = $class->newInstance(); $this->hydratorFactory->hydrate($document, $data, $hints); @@ -2777,7 +2773,7 @@ public function getOrCreateDocument(string $className, array $data, array &$hint $oid = null; if ($isManagedObject) { - /** @psalm-var T $document */ + /** @phpstan-var T $document */ $document = $this->identityMap[$class->name][$serializedId]; $oid = spl_object_hash($document); if ($this->isUninitializedObject($document)) { @@ -2796,7 +2792,7 @@ public function getOrCreateDocument(string $className, array $data, array &$hint } } else { if ($document === null) { - /** @psalm-var T $document */ + /** @phpstan-var T $document */ $document = $class->newInstance(); } @@ -2822,7 +2818,7 @@ public function getOrCreateDocument(string $className, array $data, array &$hint * * @internal * - * @psalm-param PersistentCollectionInterface $collection + * @phpstan-param PersistentCollectionInterface $collection */ public function loadCollection(PersistentCollectionInterface $collection): void { @@ -2839,7 +2835,7 @@ public function loadCollection(PersistentCollectionInterface $collection): void * * @internal * - * @psalm-return array> + * @return array> */ public function getIdentityMap(): array { @@ -2993,7 +2989,7 @@ public function propertyChanged($sender, $propertyName, $oldValue, $newValue) /** * Gets the currently scheduled document insertions in this UnitOfWork. * - * @psalm-return array + * @return array */ public function getScheduledDocumentInsertions(): array { @@ -3003,7 +2999,7 @@ public function getScheduledDocumentInsertions(): array /** * Gets the currently scheduled document upserts in this UnitOfWork. * - * @psalm-return array + * @return array */ public function getScheduledDocumentUpserts(): array { @@ -3013,7 +3009,7 @@ public function getScheduledDocumentUpserts(): array /** * Gets the currently scheduled document updates in this UnitOfWork. * - * @psalm-return array + * @return array */ public function getScheduledDocumentUpdates(): array { @@ -3023,7 +3019,7 @@ public function getScheduledDocumentUpdates(): array /** * Gets the currently scheduled document deletions in this UnitOfWork. * - * @psalm-return array + * @return array */ public function getScheduledDocumentDeletions(): array { @@ -3035,7 +3031,7 @@ public function getScheduledDocumentDeletions(): array * * @internal * - * @psalm-return array> + * @return array> */ public function getScheduledCollectionDeletions(): array { @@ -3047,7 +3043,7 @@ public function getScheduledCollectionDeletions(): array * * @internal * - * @psalm-return array> + * @return array> */ public function getScheduledCollectionUpdates(): array { @@ -3096,7 +3092,7 @@ private function objToStr(object $obj): string return method_exists($obj, '__toString') ? (string) $obj : $obj::class . '@' . spl_object_hash($obj); } - /** @psalm-param CommitOptions $options */ + /** @phpstan-param CommitOptions $options */ private function doCommit(array $options): void { foreach ($this->getClassesForCommitAction($this->scheduledDocumentUpserts) as $classAndDocuments) { @@ -3120,7 +3116,7 @@ private function doCommit(array $options): void } } - /** @psalm-param CommitOptions $options */ + /** @phpstan-param CommitOptions $options */ private function useTransaction(array $options): bool { if (isset($options['withTransaction'])) { @@ -3130,7 +3126,7 @@ private function useTransaction(array $options): bool return $this->dm->getConfiguration()->isTransactionalFlushEnabled(); } - /** @psalm-param CommitOptions $options */ + /** @phpstan-param CommitOptions $options */ private function getTransactionOptions(array $options): array { return array_intersect_key( diff --git a/lib/Doctrine/ODM/MongoDB/Utility/LifecycleEventManager.php b/lib/Doctrine/ODM/MongoDB/Utility/LifecycleEventManager.php index a44ff8f25..a6d26cc2f 100644 --- a/lib/Doctrine/ODM/MongoDB/Utility/LifecycleEventManager.php +++ b/lib/Doctrine/ODM/MongoDB/Utility/LifecycleEventManager.php @@ -63,7 +63,7 @@ public function documentNotFound(object $proxy, $id): bool /** * Dispatches postCollectionLoad event. * - * @psalm-param PersistentCollectionInterface $coll + * @phpstan-param PersistentCollectionInterface $coll */ public function postCollectionLoad(PersistentCollectionInterface $coll): void { @@ -74,8 +74,8 @@ public function postCollectionLoad(PersistentCollectionInterface $coll): void /** * Invokes postPersist callbacks and events for given document cascading them to embedded documents as well. * - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @template T of object */ @@ -95,8 +95,8 @@ public function postPersist(ClassMetadata $class, object $document, ?Session $se /** * Invokes postRemove callbacks and events for given document. * - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @template T of object */ @@ -116,8 +116,8 @@ public function postRemove(ClassMetadata $class, object $document, ?Session $ses * Invokes postUpdate callbacks and events for given document. The same will be done for embedded documents owned * by given document unless they were new in which case postPersist callbacks and events will be dispatched. * - * @psalm-param ClassMetadata $class - * @psalm-param T $document + * @phpstan-param ClassMetadata $class + * @phpstan-param T $document * * @template T of object */ diff --git a/psalm-baseline.xml b/psalm-baseline.xml deleted file mode 100644 index 110172f14..000000000 --- a/psalm-baseline.xml +++ /dev/null @@ -1,473 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - implementsInterface(ClassMetadataFactoryInterface::class)]]> - implementsInterface(GridFSRepository::class)]]> - implementsInterface(ObjectRepository::class)]]> - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - - - - - identifier => $this->getIdentifierValue($object)]]]> - - - associationMappings]]> - associationMappings]]> - fieldMappings]]> - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - {'default-discriminator-value'})]]> - {'discriminator-field'})]]> - {'discriminator-map'})]]> - {'generator-option'})]]> - and)]]> - field)]]> - criteria)]]> - prime)]]> - sort)]]> - {'default-discriminator-value'})]]> - {'discriminator-field'})]]> - {'discriminator-map'})]]> - option)]]> - {'partial-filter-expression'})]]> - {'tag-set'})]]> - field)]]> - id)]]> - indexes)]]> - {'default-discriminator-value'})]]> - {'discriminator-field'})]]> - {'discriminator-map'})]]> - {'embed-many'})]]> - {'embed-one'})]]> - {'lifecycle-callbacks'})]]> - {'read-preference'})]]> - {'reference-many'})]]> - {'reference-one'})]]> - {'schema-validation'})]]> - {'search-indexes'})]]> - {'shard-key'})]]> - option)]]> - - - metadata)]]> - {'also-load-methods'})]]> - - - - - getUnitOfWork())]]> - getUnitOfWork())]]> - - - - - coll)]]> - uow) && $this->isOrphanRemovalEnabled() && $value !== null]]> - uow) && $this->isOrphanRemovalEnabled() && $value !== null]]> - - - owner && isset($this->dm)]]> - dm)]]> - uow)]]> - uow)]]> - - - - - - - - - - - - - - - query]]> - - - - - newObj]]> - query]]> - query[$this->currentField]]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - parentAssociations]]> - - - - - - , 1: array}>]]> - - - - - - - - - - - - - - - - - - associationMappings['ref']]]> - associationMappings['ref1']]]> - associationMappings['ref2']]]> - associationMappings['ref3']]]> - associationMappings['ref4']]]> - ClassMetadata::REFERENCE_STORE_AS_DB_REF]]]> - - - - - - - - - - getData()]]> - - - - - categories[0]->children, $user->categories[1]->children]]]> - categories[0]->children[0]->children, $user->categories[0]->children[1]->children]]]> - categories[0]->children[0]->children, $user->categories[0]->children[1]->children]]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true]]]> - - - - - 'Doctrine\ODM\MongoDB\Tests\Functional\SomeInvalidClass']]]> - - - - - embeds]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - getData()]]> - - - - - - - - dm]]> - - - - - - - 'assoc', - 'reference' => true, - 'type' => 'many', - 'targetDocument' => 'stdClass', - 'repositoryMethod' => 'fetch', - $prop => $value, - ]]]> - 'enum', - 'enumType' => Card::class, - ]]]> - 'enum', - 'enumType' => SuitNonBacked::class, - ]]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/psalm.xml.dist b/psalm.xml.dist deleted file mode 100644 index 63e41ba00..000000000 --- a/psalm.xml.dist +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php index d2b0ee050..a25183060 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/FacetTest.php @@ -66,7 +66,6 @@ public function testThrowsExceptionWithoutFieldName(): void $facetStage->pipeline($this->getTestAggregationBuilder()); } - /** @psalm-suppress InvalidArgument on purpose to throw exception */ public function testThrowsExceptionOnInvalidPipeline(): void { $facetStage = new Facet($this->getTestAggregationBuilder()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SortTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SortTest.php index 76331a095..e56dddcea 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SortTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Aggregation/Stage/SortTest.php @@ -9,14 +9,14 @@ use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use PHPUnit\Framework\Attributes\DataProvider; -/** @psalm-import-type SortShape from Sort */ +/** @phpstan-import-type SortShape from Sort */ class SortTest extends BaseTestCase { use AggregationTestTrait; /** * @param string|array $field - * @psalm-param SortShape $expectedSort + * @phpstan-param SortShape $expectedSort */ #[DataProvider('provideSortOptions')] public function testStage(array $expectedSort, $field, ?string $order = null): void @@ -28,7 +28,7 @@ public function testStage(array $expectedSort, $field, ?string $order = null): v /** * @param string|array $field - * @psalm-param SortShape $expectedSort + * @phpstan-param SortShape $expectedSort */ #[DataProvider('provideSortOptions')] public function testFromBuilder(array $expectedSort, $field, ?string $order = null): void diff --git a/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php index 7806d9cf4..b24c54c3f 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php @@ -160,7 +160,7 @@ protected function skipTestIfTransactionalFlushEnabled(): void } } - /** @psalm-param class-string $className */ + /** @param class-string $className */ protected function skipTestIfNotSharded(string $className): void { $result = $this->dm->getDocumentDatabase($className)->command(['listCommands' => true], ['typeMap' => DocumentManager::CLIENT_TYPEMAP])->toArray()[0]; @@ -172,7 +172,7 @@ protected function skipTestIfNotSharded(string $className): void $this->markTestSkipped('Test skipped because server does not support sharding'); } - /** @psalm-param class-string $className */ + /** @param class-string $className */ protected function skipTestIfSharded(string $className): void { $result = $this->dm->getDocumentDatabase($className)->command(['listCommands' => true], ['typeMap' => DocumentManager::CLIENT_TYPEMAP])->toArray()[0]; diff --git a/tests/Doctrine/ODM/MongoDB/Tests/ClassMetadataTestUtil.php b/tests/Doctrine/ODM/MongoDB/Tests/ClassMetadataTestUtil.php index 5783c7e9e..40b3ffd05 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/ClassMetadataTestUtil.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/ClassMetadataTestUtil.php @@ -8,15 +8,15 @@ use Doctrine\ODM\MongoDB\Types\Type; /** - * @psalm-import-type FieldMapping from ClassMetadata - * @psalm-import-type FieldMappingConfig from ClassMetadata + * @phpstan-import-type FieldMapping from ClassMetadata + * @phpstan-import-type FieldMappingConfig from ClassMetadata */ class ClassMetadataTestUtil { /** - * @psalm-param FieldMappingConfig $mapping + * @phpstan-param FieldMappingConfig $mapping * - * @psalm-return FieldMapping + * @phpstan-return FieldMapping */ public static function getFieldMapping(array $mapping): array { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Events/LifecycleListenersTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Events/LifecycleListenersTest.php index 7a5ea7e83..088040c91 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Events/LifecycleListenersTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Events/LifecycleListenersTest.php @@ -220,7 +220,7 @@ public function testPostCollectionLoad(): void class MyEventListener { - /** @psalm-var array> */ + /** @var array> */ public array $called = []; /** @param array{LifecycleEventArgs} $args */ diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/DocumentPersisterTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/DocumentPersisterTest.php index 03f2ef3d7..232c35343 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/DocumentPersisterTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/DocumentPersisterTest.php @@ -624,7 +624,7 @@ public static function dataProviderTestWriteConcern(): array ]; } - /** @psalm-param class-string $class */ + /** @param class-string $class */ #[DataProvider('dataProviderTestWriteConcern')] public function testExecuteInsertsRespectsWriteConcern(string $class, string|int $writeConcern): void { @@ -646,7 +646,7 @@ public function testExecuteInsertsRespectsWriteConcern(string $class, string|int $this->dm->flush(); } - /** @psalm-param class-string $class */ + /** @param class-string $class */ #[DataProvider('dataProviderTestWriteConcern')] public function testExecuteInsertsOmitsWriteConcernInTransaction(string $class, string|int $writeConcern): void { @@ -668,7 +668,7 @@ public function testExecuteInsertsOmitsWriteConcernInTransaction(string $class, $this->dm->flush(); } - /** @psalm-param class-string $class */ + /** @param class-string $class */ #[DataProvider('dataProviderTestWriteConcern')] public function testExecuteUpsertsRespectsWriteConcern(string $class, string|int $writeConcern): void { @@ -691,7 +691,7 @@ public function testExecuteUpsertsRespectsWriteConcern(string $class, string|int $this->dm->flush(); } - /** @psalm-param class-string $class */ + /** @param class-string $class */ #[DataProvider('dataProviderTestWriteConcern')] public function testExecuteUpsertsDoesNotUseWriteConcernInTransaction(string $class, string|int $writeConcern): void { @@ -714,7 +714,7 @@ public function testExecuteUpsertsDoesNotUseWriteConcernInTransaction(string $cl $this->dm->flush(); } - /** @psalm-param class-string $class */ + /** @param class-string $class */ #[DataProvider('dataProviderTestWriteConcern')] public function testRemoveRespectsWriteConcern(string $class, string|int $writeConcern): void { @@ -739,7 +739,7 @@ public function testRemoveRespectsWriteConcern(string $class, string|int $writeC $this->dm->flush(); } - /** @psalm-param class-string $class */ + /** @param class-string $class */ #[DataProvider('dataProviderTestWriteConcern')] public function testRemoveDoesNotUseWriteConcernInTransaction(string $class, string|int $writeConcern): void { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php index 1ad64db3a..c1f2b70e9 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php @@ -14,7 +14,7 @@ use MongoDB\Driver\WriteConcern; use PHPUnit\Framework\Attributes\DataProvider; -/** @psalm-type ReadPreferenceTagShape = array{dc?: string, usage?: string} */ +/** @phpstan-type ReadPreferenceTagShape array{dc?: string, usage?: string} */ class ReadPreferenceTest extends BaseTestCase { public function setUp(): void @@ -44,7 +44,7 @@ public function testHintIsNotSetByDefault(): void self::assertArrayNotHasKey(Query::HINT_READ_PREFERENCE, $groups->getHints()); } - /** @psalm-param ReadPreferenceTagShape[] $tags */ + /** @phpstan-param ReadPreferenceTagShape[] $tags */ #[DataProvider('provideReadPreferenceHints')] public function testHintIsSetOnQuery(string $readPreference, array $tags = []): void { @@ -101,7 +101,7 @@ public function testDocumentLevelReadPreferenceCanBeOverriddenInQueryBuilder(): $this->assertReadPreferenceHint(ReadPreference::SECONDARY, $query->getQuery()['readPreference']); } - /** @psalm-param ReadPreferenceTagShape[] $tags */ + /** @phpstan-param ReadPreferenceTagShape[] $tags */ private function assertReadPreferenceHint(string $mode, ReadPreference $readPreference, array $tags = []): void { self::assertInstanceOf(ReadPreference::class, $readPreference); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1152Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1152Test.php index cfee336c6..050a36bcd 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1152Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1152Test.php @@ -49,11 +49,11 @@ class GH1152Parent public $child; } -/** @psalm-import-type AssociationFieldMapping from ClassMetadata */ +/** @phpstan-import-type AssociationFieldMapping from ClassMetadata */ #[ODM\EmbeddedDocument] class GH1152Child { - /** @psalm-var array{0: AssociationFieldMapping, 1: object|null, 2: string}|null */ + /** @var array{0: AssociationFieldMapping, 1: object|null, 2: string}|null */ public $parentAssociation; } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php index 976b83a1c..cb905e9ac 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php @@ -238,6 +238,7 @@ public function testEnumTypeMustPointToAnEnum(): void $this->expectExceptionMessage( 'Attempting to map a non-enum type Documents\Card as an enum: ', ); + // @phpstan-ignore-next-line $cm->mapField([ 'fieldName' => 'enum', 'enumType' => Card::class, @@ -257,6 +258,7 @@ public function testEnumTypeMustPointToABackedEnum(): void $this->expectExceptionMessage( 'Attempting to map a non-backed enum Documents\SuitNonBacked: ', ); + // @phpstan-ignore-next-line $cm->mapField([ 'fieldName' => 'enum', 'enumType' => SuitNonBacked::class, @@ -539,6 +541,7 @@ public function testSetFieldValueWithProxy(): void $proxy = $this->dm->getReference(Album::class, $document->getId()); self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertInstanceOf(Album::class, $proxy); self::assertEquals('nevermind', $proxy->getName()); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php index 0d7e2c1e0..b8d3a6df4 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/QueryTest.php @@ -420,11 +420,10 @@ public function testConstructorShouldThrowExceptionForInvalidType(): void { $this->expectException(InvalidArgumentException::class); - /** @psalm-suppress InvalidArgument */ new Query($this->dm, new ClassMetadata(User::class), $this->getMockCollection(), ['type' => -1], []); } - /** @psalm-param Query::TYPE_* $type */ + /** @param Query::TYPE_* $type */ #[DataProvider('provideQueryTypesThatDoNotReturnAnIterator')] public function testGetIteratorShouldThrowExceptionWithoutExecutingForTypesThatDoNotReturnAnIterator(int $type, string $method): void { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Repository/DefaultGridFSRepositoryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Repository/DefaultGridFSRepositoryTest.php index 51ba3d519..6a65706c4 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Repository/DefaultGridFSRepositoryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Repository/DefaultGridFSRepositoryTest.php @@ -307,7 +307,6 @@ private function getRepository(string $className = File::class): GridFSRepositor assert($repository instanceof GridFSRepository); - /** @psalm-var GridFSRepository */ return $repository; } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php index a8dc4e656..cdd3cdf8e 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php @@ -46,12 +46,12 @@ use function in_array; /** - * @psalm-import-type IndexMapping from ClassMetadata - * @psalm-import-type IndexOptions from ClassMetadata + * @phpstan-import-type IndexMapping from ClassMetadata + * @phpstan-import-type IndexOptions from ClassMetadata */ class SchemaManagerTest extends BaseTestCase { - /** @psalm-var list */ + /** @var list */ private array $indexedClasses = [ CmsAddress::class, CmsArticle::class, @@ -63,13 +63,13 @@ class SchemaManagerTest extends BaseTestCase ShardedOneWithDifferentKey::class, ]; - /** @psalm-var list */ + /** @var list */ private array $searchIndexedClasses = [ CmsAddress::class, CmsArticle::class, ]; - /** @psalm-var list */ + /** @var list */ private array $views = [ UserName::class, ]; @@ -167,7 +167,7 @@ public static function getIndexCreationWriteOptions(): array ]; } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getIndexCreationWriteOptions')] public function testEnsureIndexes(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern, bool $background = false): void { @@ -213,7 +213,7 @@ public function testEnsureIndexes(array $expectedWriteOptions, ?int $maxTimeMs, $this->schemaManager->ensureIndexes($maxTimeMs, $writeConcern, $background); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getIndexCreationWriteOptions')] public function testEnsureDocumentIndexes(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern, bool $background = false): void { @@ -232,7 +232,7 @@ public function testEnsureDocumentIndexes(array $expectedWriteOptions, ?int $max $this->schemaManager->ensureDocumentIndexes(CmsArticle::class, $maxTimeMs, $writeConcern, $background); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getIndexCreationWriteOptions')] public function testEnsureDocumentIndexesForGridFSFile(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern, bool $background = false): void { @@ -273,7 +273,7 @@ public function testEnsureDocumentIndexesForGridFSFile(array $expectedWriteOptio $this->schemaManager->ensureDocumentIndexes(File::class, $maxTimeMs, $writeConcern, $background); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getIndexCreationWriteOptions')] public function testEnsureDocumentIndexesWithTwoLevelInheritance(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern, bool $background = false): void { @@ -287,7 +287,7 @@ public function testEnsureDocumentIndexesWithTwoLevelInheritance(array $expected $this->schemaManager->ensureDocumentIndexes(CmsProduct::class, $maxTimeMs, $writeConcern, $background); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testUpdateDocumentIndexesShouldCreateMappedIndexes(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -309,7 +309,7 @@ public function testUpdateDocumentIndexesShouldCreateMappedIndexes(array $expect $this->schemaManager->updateDocumentIndexes(CmsArticle::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testUpdateDocumentIndexesShouldDeleteUnmappedIndexesBeforeCreatingMappedIndexes(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -339,7 +339,7 @@ public function testUpdateDocumentIndexesShouldDeleteUnmappedIndexesBeforeCreati $this->schemaManager->updateDocumentIndexes(CmsArticle::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testDeleteIndexes(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -362,7 +362,7 @@ public function testDeleteIndexes(array $expectedWriteOptions, ?int $maxTimeMs, $this->schemaManager->deleteIndexes($maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testDeleteDocumentIndexes(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -576,7 +576,7 @@ public function testUpdateValidators(): void $this->schemaManager->updateValidators(); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testUpdateDocumentValidator(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -623,7 +623,7 @@ public function testUpdateDocumentValidatorShouldThrowExceptionForMappedSupercla $this->schemaManager->updateDocumentValidator($class->name); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testUpdateDocumentValidatorReset(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -644,7 +644,7 @@ public function testUpdateDocumentValidatorReset(array $expectedWriteOptions, ?i $this->schemaManager->updateDocumentValidator($class->name, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testCreateDocumentCollection(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -670,7 +670,7 @@ public function testCreateDocumentCollection(array $expectedWriteOptions, ?int $ $this->schemaManager->createDocumentCollection(CmsArticle::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testCreateDocumentCollectionForFile(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -683,7 +683,7 @@ public function testCreateDocumentCollectionForFile(array $expectedWriteOptions, $this->schemaManager->createDocumentCollection(File::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testCreateDocumentCollectionWithValidator(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -724,7 +724,7 @@ public function testCreateDocumentCollectionWithValidator(array $expectedWriteOp $this->schemaManager->createDocumentCollection($cm->name, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testCreateView(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -760,7 +760,7 @@ public function testCreateView(array $expectedWriteOptions, ?int $maxTimeMs, ?Wr $this->schemaManager->createDocumentCollection(UserName::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testCreateCollections(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -787,7 +787,7 @@ public function testCreateCollections(array $expectedWriteOptions, ?int $maxTime self::assertSame(1, array_count_values($createdCollections)['Tournament']); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testDropCollections(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -800,7 +800,7 @@ public function testDropCollections(array $expectedWriteOptions, ?int $maxTimeMs $this->schemaManager->dropCollections($maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testDropDocumentCollection(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -818,7 +818,7 @@ public function testDropDocumentCollection(array $expectedWriteOptions, ?int $ma $this->schemaManager->dropDocumentCollection(CmsArticle::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testDropDocumentCollectionForGridFSFile(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -852,7 +852,7 @@ public function testDropDocumentCollectionForGridFSFile(array $expectedWriteOpti $this->schemaManager->dropDocumentCollection(File::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testDropView(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -870,7 +870,7 @@ public function testDropView(array $expectedWriteOptions, ?int $maxTimeMs, ?Writ $this->schemaManager->dropDocumentCollection(UserName::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testDropDocumentDatabase(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -889,7 +889,7 @@ public function testDropDocumentDatabase(array $expectedWriteOptions, ?int $maxT $this->schemaManager->dropDocumentDatabase(CmsArticle::class, $maxTimeMs, $writeConcern); } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] public function testDropDatabases(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { @@ -905,7 +905,7 @@ public function testDropDatabases(array $expectedWriteOptions, ?int $maxTimeMs, /** * @param array $mongoIndex - * @psalm-param IndexMapping $documentIndex + * @phpstan-param IndexMapping $documentIndex */ #[DataProvider('dataIsMongoIndexEquivalentToDocumentIndex')] public function testIsMongoIndexEquivalentToDocumentIndex(bool $expected, array $mongoIndex, array $documentIndex): void @@ -1123,7 +1123,7 @@ public static function dataIsMongoIndexEquivalentToDocumentIndex(): array /** * @param array $mongoIndex - * @psalm-param IndexMapping $documentIndex + * @phpstan-param IndexMapping $documentIndex */ #[DataProvider('dataIsMongoTextIndexEquivalentToDocumentIndex')] public function testIsMongoIndexEquivalentToDocumentIndexWithTextIndexes(bool $expected, array $mongoIndex, array $documentIndex): void @@ -1316,7 +1316,7 @@ private function getMockDatabase() return $db; } - /** @psalm-param IndexOptions $expectedWriteOptions */ + /** @phpstan-param IndexOptions $expectedWriteOptions */ private function writeOptions(array $expectedWriteOptions): Constraint { return new Callback(static function (array $value) use ($expectedWriteOptions) { diff --git a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php index 81fac9bea..e79c0ef93 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php @@ -511,8 +511,7 @@ public function testCommitsInProgressIsUpdatedOnException(): void try { $this->dm->flush(); } catch (Throwable) { - $getCommitsInProgress = Closure::bind(fn (UnitOfWork $unitOfWork) => /** @psalm-suppress InaccessibleProperty */ -$unitOfWork->commitsInProgress, $this->dm->getUnitOfWork(), UnitOfWork::class); + $getCommitsInProgress = Closure::bind(fn (UnitOfWork $unitOfWork) => $unitOfWork->commitsInProgress, $this->dm->getUnitOfWork(), UnitOfWork::class); self::assertSame(0, $getCommitsInProgress($this->dm->getUnitOfWork())); From da17214b8ff77b9314d8c8ab122c32ee303128cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 17 Oct 2024 20:34:52 +0200 Subject: [PATCH 07/28] Search index mapping must be dynamic or specify a field mapping. (#2690) --- lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php | 8 +++++++- lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php | 5 +++++ .../ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php index 558ab6a86..335add4fe 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php @@ -1220,9 +1220,15 @@ public function hasIndexes(): bool */ public function addSearchIndex(array $definition, ?string $name = null): void { + $name ??= self::DEFAULT_SEARCH_INDEX_NAME; + + if (empty($definition['mappings']['dynamic']) && empty($definition['mappings']['fields'])) { + throw MappingException::emptySearchIndexDefinition($this->name, $name); + } + $this->searchIndexes[] = [ 'definition' => $definition, - 'name' => $name ?? self::DEFAULT_SEARCH_INDEX_NAME, + 'name' => $name, ]; } diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php index af7d22a28..68438848c 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php @@ -291,4 +291,9 @@ public static function nonBackedEnumMapped(string $className, string $fieldName, $fieldName, )); } + + public static function emptySearchIndexDefinition(string $className, string $indexName): self + { + return new self(sprintf('%s search index "%s" must be dynamic or specify a field mapping', $className, $indexName)); + } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php index cb905e9ac..cbf6d7fa7 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php @@ -971,6 +971,15 @@ public function testDefaultValueForValidationLevel(): void $cm = new ClassMetadata('stdClass'); self::assertEquals(ClassMetadata::SCHEMA_VALIDATION_LEVEL_STRICT, $cm->getValidationLevel()); } + + public function testEmptySearchIndexDefinition(): void + { + $cm = new ClassMetadata('stdClass'); + + $this->expectException(MappingException::class); + $this->expectExceptionMessage('stdClass search index "default" must be dynamic or specify a field mapping'); + $cm->addSearchIndex(['mappings' => []]); + } } /** @template-extends DocumentRepository */ From e9a8e78af5db540f4f43f7035d6ff73f03eede54 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Mon, 21 Oct 2024 08:25:50 +0200 Subject: [PATCH 08/28] Support mapping time series collections (#2687) * Store metadata information for time series collections * Specify time series options when creating collection * Use named arguments for attributes * Don't skip empty metadata field names * Leave encoding of enum values to the MongoDB driver * Remove unused isTimeSeries option * Disable early exit requirement in XML driver * Use explicit closure instead of empty() checks * Support bucketMaxSpanSeconds and bucketRoundingSeconds in time series collections * Read bucket options for time series in XML driver * Add attribute documentation for time series collections * Add cookbook entry for time series data * Update documentation links * Expand time series cookbook to use multiple measurements * Simplify markAsTimeSeries tests with granularity and bucket options * Apply wording suggestions from code review Co-authored-by: Jeremy Mikola --------- Co-authored-by: Jeremy Mikola --- docs/en/cookbook/time-series-data.rst | 132 ++++++++++++++++++ docs/en/reference/attributes-reference.rst | 58 ++++++++ doctrine-mongo-mapping.xsd | 25 ++++ .../Mapping/Annotations/TimeSeries.php | 29 ++++ .../ODM/MongoDB/Mapping/ClassMetadata.php | 23 +++ .../Mapping/Driver/AttributeDriver.php | 7 + .../ODM/MongoDB/Mapping/Driver/XmlDriver.php | 30 +++- .../ODM/MongoDB/Mapping/MappingException.php | 10 ++ .../Mapping/TimeSeries/Granularity.php | 12 ++ lib/Doctrine/ODM/MongoDB/SchemaManager.php | 18 +++ phpstan-baseline.neon | 4 +- .../Mapping/AbstractMappingDriverTestCase.php | 71 ++++++++++ .../Tests/Mapping/ClassMetadataTest.php | 72 ++++++++++ ...DriverTimeSeriesDocumentWithBucket.dcm.xml | 15 ++ ...rTimeSeriesDocumentWithGranularity.dcm.xml | 15 ++ .../ODM/MongoDB/Tests/SchemaManagerTest.php | 29 ++++ .../TimeSeries/TimeSeriesDocument.php | 26 ++++ 17 files changed, 570 insertions(+), 6 deletions(-) create mode 100644 docs/en/cookbook/time-series-data.rst create mode 100644 lib/Doctrine/ODM/MongoDB/Mapping/Annotations/TimeSeries.php create mode 100644 lib/Doctrine/ODM/MongoDB/Mapping/TimeSeries/Granularity.php create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverTimeSeriesDocumentWithBucket.dcm.xml create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverTimeSeriesDocumentWithGranularity.dcm.xml create mode 100644 tests/Documents/TimeSeries/TimeSeriesDocument.php diff --git a/docs/en/cookbook/time-series-data.rst b/docs/en/cookbook/time-series-data.rst new file mode 100644 index 000000000..4bc32b43b --- /dev/null +++ b/docs/en/cookbook/time-series-data.rst @@ -0,0 +1,132 @@ +Storing Time Series Data +======================== + +.. note:: + + Support for mapping time series data was added in ODM 2.10. + +`time-series data `__ +is a sequence of data points in which insights are gained by analyzing changes +over time. + +Time series data is generally composed of these components: + +- + Time when the data point was recorded + +- + Metadata, which is a label, tag, or other data that identifies a data series + and rarely changes + +- + Measurements, which are the data points tracked at increments in time. + +A time series document always contains a time value, and one or more measurement +fields. Metadata is optional, but cannot be added to a time series collection +after creating it. When using an embedded document for metadata, fields can be +added to this document after creating the collection. + +.. note:: + + Support for time series collections was added in MongoDB 5.0. Attempting to + use this functionality on older server versions will result in an error on + schema creation. + +Creating The Model +------------------ + +For this example, we'll be storing data from multiple sensors measuring +temperature and humidity. Other examples for time series include stock data, +price information, website visitors, or vehicle telemetry (speed, position, +etc.). + +First, we define the model for our data: + +.. code-block:: php + + id = (string) new ObjectId(); + } + } + +Note that we defined the entire model as readonly. While we could theoretically +change values in the document, in this example we'll assume that the data will +not change. + +Now we can mark the document as a time series document. To do so, we use the +``TimeSeries`` attribute, configuring appropriate values for the time and +metadata field, which in our case stores the ID of the sensor reporting the +measurement: + +.. code-block:: php + + persist($measurement); + $documentManager->flush(); + +Note that other functionality such as querying, using aggregation pipelines, or +removing data works the same as with other collections. + +Considerations +-------------- + +With the mapping above, data is stored with a granularity of seconds. Depending +on how often measurements come in, we can reduce the granularity to minutes or +hours. This changes how the data is stored internally by changing the bucket +size. This affects storage requirements and query performance. + +For example, with the default ``seconds`` granularity, each bucket groups +documents for one hour. If each sensor only reports data every few minutes, we'd +do well to configure ``minute`` granularity. This reduces the +number of buckets created, reducing storage and making queries more efficient. +However, if we were to choose ``hours`` for granularity, readings for a whole +month would be grouped into one bucket, resulting in slower queries as more +entries have to be traversed when reading data. + +More details on granularity and other consideration scan be found in the +`MongoDB documentation `__. diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst index b6d8a616e..dedaf906a 100644 --- a/docs/en/reference/attributes-reference.rst +++ b/docs/en/reference/attributes-reference.rst @@ -1144,6 +1144,64 @@ for sharding the document collection. //... } +#[TimeSeries] +------------- + +This attribute may be used at the class level to mark a collection as containing +:doc:`time-series data <../cookbook/time-series-data>`. + +.. code-block:: php + + + @@ -634,4 +635,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/TimeSeries.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/TimeSeries.php new file mode 100644 index 000000000..e4fe593eb --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/TimeSeries.php @@ -0,0 +1,29 @@ +rootClass = $rootClass; } + public function markAsTimeSeries(TimeSeries $options): void + { + $this->validateTimeSeriesOptions($options); + + $this->timeSeriesOptions = $options; + } + public function getFieldNames(): array { return array_keys($this->fieldMappings); @@ -2527,6 +2538,7 @@ public function __sleep() 'idGenerator', 'indexes', 'shardKey', + 'timeSeriesOptions', ]; // The rest of the metadata is only serialized if necessary. @@ -2758,4 +2770,15 @@ private function validateAndCompleteTypedManyAssociationMapping(array $mapping): return $mapping; } + + private function validateTimeSeriesOptions(TimeSeries $options): void + { + if (! $this->hasField($options->timeField)) { + throw MappingException::timeSeriesFieldNotFound($this->name, $options->timeField, 'time'); + } + + if ($options->metaField !== null && ! $this->hasField($options->metaField)) { + throw MappingException::timeSeriesFieldNotFound($this->name, $options->metaField, 'metadata'); + } + } } diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php index 6a48f5fc1..fb3b00543 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php @@ -10,6 +10,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations\AbstractIndex; use Doctrine\ODM\MongoDB\Mapping\Annotations\SearchIndex; use Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey; +use Doctrine\ODM\MongoDB\Mapping\Annotations\TimeSeries; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\MappingException; use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; @@ -288,6 +289,12 @@ public function loadMetadataForClass($className, PersistenceClassMetadata $metad $this->setShardKey($metadata, $classAttributes[ShardKey::class]); } + // Mark as time series only after mapping all fields + if (isset($classAttributes[TimeSeries::class])) { + assert($classAttributes[TimeSeries::class] instanceof TimeSeries); + $metadata->markAsTimeSeries($classAttributes[TimeSeries::class]); + } + foreach ($reflClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { /* Filter for the declaring class only. Callbacks from parent * classes will already be registered. diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php index 9035101f9..8b093dd2b 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php @@ -4,8 +4,10 @@ namespace Doctrine\ODM\MongoDB\Mapping\Driver; +use Doctrine\ODM\MongoDB\Mapping\Annotations\TimeSeries; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\ODM\MongoDB\Mapping\TimeSeries\Granularity; use Doctrine\ODM\MongoDB\Utility\CollectionHelper; use Doctrine\Persistence\Mapping\Driver\FileDriver; use DOMDocument; @@ -78,6 +80,7 @@ public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENS parent::__construct($locator, $fileExtension); } + // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\ClassMetadata $metadata) { assert($metadata instanceof ClassMetadata); @@ -335,15 +338,34 @@ public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\C } } - if (! isset($xmlRoot->{'also-load-methods'})) { - return; + if (isset($xmlRoot->{'also-load-methods'})) { + foreach ($xmlRoot->{'also-load-methods'}->{'also-load-method'} as $alsoLoadMethod) { + $metadata->registerAlsoLoadMethod((string) $alsoLoadMethod['method'], (string) $alsoLoadMethod['field']); + } } - foreach ($xmlRoot->{'also-load-methods'}->{'also-load-method'} as $alsoLoadMethod) { - $metadata->registerAlsoLoadMethod((string) $alsoLoadMethod['method'], (string) $alsoLoadMethod['field']); + if (isset($xmlRoot->{'time-series'})) { + $attributes = $xmlRoot->{'time-series'}->attributes(); + + $metaField = isset($attributes['meta-field']) ? (string) $attributes['meta-field'] : null; + $granularity = isset($attributes['granularity']) ? Granularity::from((string) $attributes['granularity']) : null; + $expireAfterSeconds = isset($attributes['expire-after-seconds']) ? (int) $attributes['expire-after-seconds'] : null; + $bucketMaxSpanSeconds = isset($attributes['bucket-max-span-seconds']) ? (int) $attributes['bucket-max-span-seconds'] : null; + $bucketRoundingSeconds = isset($attributes['bucket-rounding-seconds']) ? (int) $attributes['bucket-rounding-seconds'] : null; + + $metadata->markAsTimeSeries(new TimeSeries( + timeField: (string) $attributes['time-field'], + metaField: $metaField, + granularity: $granularity, + expireAfterSeconds: $expireAfterSeconds, + bucketMaxSpanSeconds: $bucketMaxSpanSeconds, + bucketRoundingSeconds: $bucketRoundingSeconds, + )); } } + // phpcs:enable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed + /** * @param ClassMetadata $class * @phpstan-param FieldMappingConfig $mapping diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php index 68438848c..e51e21c0a 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php @@ -296,4 +296,14 @@ public static function emptySearchIndexDefinition(string $className, string $ind { return new self(sprintf('%s search index "%s" must be dynamic or specify a field mapping', $className, $indexName)); } + + public static function timeSeriesFieldNotFound(string $className, string $fieldName, string $field): self + { + return new self(sprintf( + 'The %s field %s::%s was not found', + $field, + $className, + $fieldName, + )); + } } diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/TimeSeries/Granularity.php b/lib/Doctrine/ODM/MongoDB/Mapping/TimeSeries/Granularity.php new file mode 100644 index 000000000..3727686f6 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Mapping/TimeSeries/Granularity.php @@ -0,0 +1,12 @@ +getValidationLevel(); } + if ($class->timeSeriesOptions !== null) { + $options['timeseries'] = array_filter( + [ + 'timeField' => $class->timeSeriesOptions->timeField, + 'metaField' => $class->timeSeriesOptions->metaField, + // ext-mongodb will automatically encode backed enums, so we can use the value directly here + 'granularity' => $class->timeSeriesOptions->granularity, + 'bucketMaxSpanSeconds' => $class->timeSeriesOptions->bucketMaxSpanSeconds, + 'bucketRoundingSeconds' => $class->timeSeriesOptions->bucketRoundingSeconds, + ], + static fn (mixed $value): bool => $value !== null, + ); + + if ($class->timeSeriesOptions->expireAfterSeconds) { + $options['expireAfterSeconds'] = $class->timeSeriesOptions->expireAfterSeconds; + } + } + $this->dm->getDocumentDatabase($documentName)->createCollection( $class->getCollection(), $this->getWriteOptions($maxTimeMs, $writeConcern, $options), diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6e6aca3be..e2c6ab2d8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -956,12 +956,12 @@ parameters: path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataLoadEventTest.php - - message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents\\\\\\\\Card'\\} given\\.$#" + message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents\\\\\\\\Card'\\} given\\.$#" count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php - - message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents\\\\\\\\SuitNonBacked'\\} given\\.$#" + message: "#^Parameter \\#1 \\$mapping of method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\\\:\\:mapField\\(\\) expects array\\{type\\?\\: string, fieldName\\?\\: string, name\\?\\: string, strategy\\?\\: string, association\\?\\: int, id\\?\\: bool, isOwningSide\\?\\: bool, collectionClass\\?\\: class\\-string, \\.\\.\\.\\}, array\\{fieldName\\: 'enum', enumType\\: 'Documents\\\\\\\\SuitNonBacked'\\} given\\.$#" count: 1 path: tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTestCase.php index 1a20c1745..db287a771 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTestCase.php @@ -11,6 +11,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\ODM\MongoDB\Mapping\TimeSeries\Granularity; use Doctrine\ODM\MongoDB\Repository\DefaultGridFSRepository; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\ODM\MongoDB\Repository\ViewRepository; @@ -677,6 +678,26 @@ public function testEnumType(): void self::assertTrue($metadata->fieldMappings['nullableSuit']['nullable']); self::assertInstanceOf(EnumReflectionProperty::class, $metadata->reflFields['nullableSuit']); } + + public function testTimeSeriesDocumentWithGranularity(): void + { + $metadata = $this->dm->getClassMetadata(AbstractMappingDriverTimeSeriesDocumentWithGranularity::class); + + self::assertEquals( + new ODM\TimeSeries('time', 'metadata', Granularity::Seconds, 86400), + $metadata->timeSeriesOptions, + ); + } + + public function testTimeSeriesDocumentWithBucket(): void + { + $metadata = $this->dm->getClassMetadata(AbstractMappingDriverTimeSeriesDocumentWithBucket::class); + + self::assertEquals( + new ODM\TimeSeries('time', 'metadata', expireAfterSeconds: 86400, bucketMaxSpanSeconds: 10, bucketRoundingSeconds: 15), + $metadata->timeSeriesOptions, + ); + } } /** @@ -1296,3 +1317,53 @@ public function createViewAggregation(Builder $builder): void ->includeFields(['name']); } } + +/** + * @ODM\Document(collection="cms_users", writeConcern=1, readOnly=true) + * @ODM\TimeSeries(timeField="time", metaField="metadata", granularity=Granularity::Seconds, expireAfterSeconds=86400) + */ +#[ODM\Document] +#[ODM\TimeSeries(timeField: 'time', metaField: 'metadata', granularity: Granularity::Seconds, expireAfterSeconds: 86400)] +class AbstractMappingDriverTimeSeriesDocumentWithGranularity +{ + /** @ODM\Id */ + #[ODM\Id] + public ?string $id = null; + + /** @ODM\Field(type="date") */ + #[ODM\Field(type: 'date')] + public DateTime $time; + + /** @ODM\Field */ + #[ODM\Field] + public string $metadata; + + /** @ODM\Field(type="int") */ + #[ODM\Field(type: 'int')] + public int $value; +} + +/** + * @ODM\Document(collection="cms_users", writeConcern=1, readOnly=true) + * @ODM\TimeSeries(timeField="time", metaField="metadata", expireAfterSeconds=86400, bucketMaxSpanSeconds=10, bucketRoundingSeconds=15) + */ +#[ODM\Document] +#[ODM\TimeSeries(timeField: 'time', metaField: 'metadata', expireAfterSeconds: 86400, bucketMaxSpanSeconds: 10, bucketRoundingSeconds: 15)] +class AbstractMappingDriverTimeSeriesDocumentWithBucket +{ + /** @ODM\Id */ + #[ODM\Id] + public ?string $id = null; + + /** @ODM\Field(type="date") */ + #[ODM\Field(type: 'date')] + public DateTime $time; + + /** @ODM\Field */ + #[ODM\Field] + public string $metadata; + + /** @ODM\Field(type="int") */ + #[ODM\Field(type: 'int')] + public int $value; +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php index cbf6d7fa7..c8f74769d 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php @@ -4,10 +4,12 @@ namespace Doctrine\ODM\MongoDB\Tests\Mapping; +use DateTime; use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\ODM\MongoDB\Mapping\TimeSeries\Granularity; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Doctrine\ODM\MongoDB\Tests\ClassMetadataTestUtil; @@ -980,6 +982,63 @@ public function testEmptySearchIndexDefinition(): void $this->expectExceptionMessage('stdClass search index "default" must be dynamic or specify a field mapping'); $cm->addSearchIndex(['mappings' => []]); } + + public function testTimeSeriesMappingOnlyWithTimeField(): void + { + $metadata = $this->dm->getClassMetadata(TimeSeriesTestDocument::class); + $metadata->markAsTimeSeries(new ODM\TimeSeries('time')); + + self::assertNotNull($metadata->timeSeriesOptions); + self::assertSame('time', $metadata->timeSeriesOptions->timeField); + } + + public function testTimeSeriesMappingWithMissingTimeField(): void + { + $metadata = $this->dm->getClassMetadata(TimeSeriesTestDocument::class); + + self::expectExceptionObject(MappingException::timeSeriesFieldNotFound(TimeSeriesTestDocument::class, 'foo', 'time')); + $metadata->markAsTimeSeries(new ODM\TimeSeries('foo')); + } + + public function testTimeSeriesMappingWithMetadataField(): void + { + $metadata = $this->dm->getClassMetadata(TimeSeriesTestDocument::class); + $metadata->markAsTimeSeries(new ODM\TimeSeries('time', 'metadata')); + + self::assertNotNull($metadata->timeSeriesOptions); + self::assertSame('metadata', $metadata->timeSeriesOptions->metaField); + } + + public function testTimeSeriesMappingWithMissingMetadataField(): void + { + $metadata = $this->dm->getClassMetadata(TimeSeriesTestDocument::class); + + self::expectExceptionObject(MappingException::timeSeriesFieldNotFound(TimeSeriesTestDocument::class, 'foo', 'metadata')); + $metadata->markAsTimeSeries(new ODM\TimeSeries('time', 'foo')); + } + + public function testTimeSeriesMappingWithExpireAfterSeconds(): void + { + $metadata = $this->dm->getClassMetadata(TimeSeriesTestDocument::class); + $metadata->markAsTimeSeries(new ODM\TimeSeries('time', expireAfterSeconds: 10)); + + self::assertSame(10, $metadata->timeSeriesOptions->expireAfterSeconds); + } + + public function testTimeSeriesMappingWithGranularityAndBucketMaxSpanSeconds(): void + { + $metadata = $this->dm->getClassMetadata(TimeSeriesTestDocument::class); + $metadata->markAsTimeSeries(new ODM\TimeSeries('time', granularity: Granularity::Hours, bucketMaxSpanSeconds: 15, bucketRoundingSeconds: 20)); + + /* + * We don't throw for invalid settings here, including: + * - bucketMaxSpanSeconds not being equal to bucketRoundingSeconds + * - granularity and bucket settings applied together + */ + self::assertSame(Granularity::Hours, $metadata->timeSeriesOptions->granularity); + self::assertSame(15, $metadata->timeSeriesOptions->bucketMaxSpanSeconds); + self::assertSame(20, $metadata->timeSeriesOptions->bucketRoundingSeconds); + } } /** @template-extends DocumentRepository */ @@ -1008,3 +1067,16 @@ class EmbeddedAssociationsCascadeTest #[ODM\EmbedOne(targetDocument: Address::class)] public $addresses; } + +#[ODM\Document] +class TimeSeriesTestDocument +{ + #[ODM\Id] + public ?string $id = null; + + #[ODM\Field] + public DateTime $time; + + #[ODM\Field] + public string $metadata; +} diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverTimeSeriesDocumentWithBucket.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverTimeSeriesDocumentWithBucket.dcm.xml new file mode 100644 index 000000000..981a3b8dc --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverTimeSeriesDocumentWithBucket.dcm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverTimeSeriesDocumentWithGranularity.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverTimeSeriesDocumentWithGranularity.dcm.xml new file mode 100644 index 000000000..6cb5fddb7 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverTimeSeriesDocumentWithGranularity.dcm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php index cdd3cdf8e..c532a8bd8 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php @@ -8,6 +8,7 @@ use Doctrine\Common\EventManager; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Mapping\TimeSeries\Granularity; use Doctrine\ODM\MongoDB\SchemaManager; use Documents\BaseDocument; use Documents\CmsAddress; @@ -20,6 +21,7 @@ use Documents\Sharded\ShardedOne; use Documents\Sharded\ShardedOneWithDifferentKey; use Documents\SimpleReferenceUser; +use Documents\TimeSeries\TimeSeriesDocument; use Documents\Tournament\Tournament; use Documents\UserName; use InvalidArgumentException; @@ -762,6 +764,33 @@ public function testCreateView(array $expectedWriteOptions, ?int $maxTimeMs, ?Wr /** @phpstan-param IndexOptions $expectedWriteOptions */ #[DataProvider('getWriteOptions')] + public function testCreateTimeSeriesCollection(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void + { + $metadata = $this->dm->getClassMetadata(TimeSeriesDocument::class); + + $options = [ + 'timeseries' => [ + 'timeField' => 'time', + 'metaField' => 'metadata', + 'granularity' => Granularity::Seconds, + ], + 'expireAfterSeconds' => 86400, + ]; + + $database = $this->documentDatabases[$this->getDatabaseName($metadata)]; + $database + ->expects($this->once()) + ->method('createCollection') + ->with( + 'TimeSeriesDocument', + $this->writeOptions($options + $expectedWriteOptions), + ); + + $this->schemaManager->createDocumentCollection(TimeSeriesDocument::class, $maxTimeMs, $writeConcern); + } + + /** @psalm-param IndexOptions $expectedWriteOptions */ + #[DataProvider('getWriteOptions')] public function testCreateCollections(array $expectedWriteOptions, ?int $maxTimeMs, ?WriteConcern $writeConcern): void { $class = $this->dm->getClassMetadata(Tournament::class); diff --git a/tests/Documents/TimeSeries/TimeSeriesDocument.php b/tests/Documents/TimeSeries/TimeSeriesDocument.php new file mode 100644 index 000000000..74426a414 --- /dev/null +++ b/tests/Documents/TimeSeries/TimeSeriesDocument.php @@ -0,0 +1,26 @@ + Date: Tue, 22 Oct 2024 10:41:37 +0200 Subject: [PATCH 09/28] Remove usage of deprecated ReadPreference::RP_* constants (#2693) --- .../Tests/Functional/ReadPreferenceTest.php | 18 +++++++++--------- .../Tests/Functional/ReferencePrimerTest.php | 2 +- .../ODM/MongoDB/Tests/Query/BuilderTest.php | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php index 8135cce28..1ad64db3a 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php @@ -46,7 +46,7 @@ public function testHintIsNotSetByDefault(): void /** @psalm-param ReadPreferenceTagShape[] $tags */ #[DataProvider('provideReadPreferenceHints')] - public function testHintIsSetOnQuery(int $readPreference, array $tags = []): void + public function testHintIsSetOnQuery(string $readPreference, array $tags = []): void { $this->skipTestIfSharded(User::class); @@ -68,9 +68,9 @@ public function testHintIsSetOnQuery(int $readPreference, array $tags = []): voi public static function provideReadPreferenceHints(): array { return [ - [ReadPreference::RP_PRIMARY, []], - [ReadPreference::RP_SECONDARY_PREFERRED, []], - [ReadPreference::RP_SECONDARY, [['dc' => 'east'], []]], + [ReadPreference::PRIMARY, []], + [ReadPreference::SECONDARY_PREFERRED, []], + [ReadPreference::SECONDARY, [['dc' => 'east'], []]], ]; } @@ -78,7 +78,7 @@ public function testDocumentLevelReadPreferenceIsSetInCollection(): void { $coll = $this->dm->getDocumentCollection(DocumentWithReadPreference::class); - self::assertSame(ReadPreference::RP_NEAREST, $coll->getReadPreference()->getMode()); + self::assertSame(ReadPreference::NEAREST, $coll->getReadPreference()->getModeString()); self::assertSame([['dc' => 'east']], $coll->getReadPreference()->getTagSets()); } @@ -88,7 +88,7 @@ public function testDocumentLevelReadPreferenceIsAppliedInQueryBuilder(): void ->createQueryBuilder() ->getQuery(); - $this->assertReadPreferenceHint(ReadPreference::RP_NEAREST, $query->getQuery()['readPreference'], [['dc' => 'east']]); + $this->assertReadPreferenceHint(ReadPreference::NEAREST, $query->getQuery()['readPreference'], [['dc' => 'east']]); } public function testDocumentLevelReadPreferenceCanBeOverriddenInQueryBuilder(): void @@ -98,14 +98,14 @@ public function testDocumentLevelReadPreferenceCanBeOverriddenInQueryBuilder(): ->setReadPreference(new ReadPreference('secondary', [])) ->getQuery(); - $this->assertReadPreferenceHint(ReadPreference::RP_SECONDARY, $query->getQuery()['readPreference']); + $this->assertReadPreferenceHint(ReadPreference::SECONDARY, $query->getQuery()['readPreference']); } /** @psalm-param ReadPreferenceTagShape[] $tags */ - private function assertReadPreferenceHint(int $mode, ReadPreference $readPreference, array $tags = []): void + private function assertReadPreferenceHint(string $mode, ReadPreference $readPreference, array $tags = []): void { self::assertInstanceOf(ReadPreference::class, $readPreference); - self::assertEquals($mode, $readPreference->getMode()); + self::assertEquals($mode, $readPreference->getModeString()); self::assertEquals($tags, $readPreference->getTagSets()); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php index 44c2c5c40..00468ea54 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php @@ -391,7 +391,7 @@ public function testPrimeReferencesInvokesPrimer(): void // Note: using a secondary read preference here can cause issues when using transactions // Using a primaryPreferred works just as well to check if the hint is passed on to the primer - $readPreference = new ReadPreference(ReadPreference::RP_PRIMARY_PREFERRED); + $readPreference = new ReadPreference(ReadPreference::PRIMARY_PREFERRED); $this->dm->createQueryBuilder(User::class) ->field('account')->prime($primer) ->field('groups')->prime($primer) diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php index 77252d76d..0f7d67308 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php @@ -675,7 +675,7 @@ public function testSetReadPreference(): void $readPreference = $qb->debug('readPreference'); self::assertInstanceOf(ReadPreference::class, $readPreference); - self::assertEquals(ReadPreference::RP_SECONDARY, $readPreference->getMode()); + self::assertEquals(ReadPreference::SECONDARY, $readPreference->getModeString()); self::assertEquals([['dc' => 'east']], $readPreference->getTagSets()); } From 0b05bf2f9f629a7dc462176734b32f02b8c8ab37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 4 Nov 2024 09:17:32 +0100 Subject: [PATCH 10/28] Run tests with PHP 8.4 (#2616) * Run tests with PHP 8.4 * Skip psalm on PHP 8.4 --- .github/workflows/continuous-integration.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index bc54f2b01..56cd905e1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -21,6 +21,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" mongodb-version: - "7.0" - "6.0" @@ -110,6 +111,13 @@ jobs: composer require --no-update symfony/var-dumper:^7@dev composer require --no-update --dev symfony/cache:^7@dev + - name: "Configure PHP 8.4" + if: "${{ matrix.php-version == '8.4' }}" + run: | + # psalm is not compatible with PHP 8.4 + # https://github.com/vimeo/psalm/pull/10928 + composer remove --no-update --dev vimeo/psalm + - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v3" with: From 27483495e170e5af1e36eecf250e91de4903d294 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Mon, 18 Nov 2024 13:08:43 +0100 Subject: [PATCH 11/28] Fix handling of unmapped properties in proxy objects (#2698) --- .../Proxy/Factory/StaticProxyFactory.php | 14 +++++++++++--- .../Proxy/Factory/StaticProxyFactoryTest.php | 19 +++++++++++++------ .../DocumentWithUnmappedProperties.php | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 tests/Documents/DocumentWithUnmappedProperties.php diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php index 737795fe4..5d7afba23 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php @@ -145,13 +145,21 @@ private function createInitializer( */ private function skippedFieldsFqns(ClassMetadata $metadata): array { - $idFieldFqcns = []; + $skippedFieldsFqns = []; foreach ($metadata->getIdentifierFieldNames() as $idField) { - $idFieldFqcns[] = $this->propertyFqcn($metadata->getReflectionProperty($idField)); + $skippedFieldsFqns[] = $this->propertyFqcn($metadata->getReflectionProperty($idField)); } - return $idFieldFqcns; + foreach ($metadata->getReflectionClass()->getProperties() as $property) { + if ($metadata->hasField($property->getName())) { + continue; + } + + $skippedFieldsFqns[] = $this->propertyFqcn($property); + } + + return $skippedFieldsFqns; } private function propertyFqcn(ReflectionProperty $property): string diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php index 2be5e3fc1..5a1db88dc 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php @@ -11,6 +11,7 @@ use Doctrine\ODM\MongoDB\LockException; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\Cart; +use Documents\DocumentWithUnmappedProperties; use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; @@ -22,15 +23,10 @@ class StaticProxyFactoryTest extends BaseTestCase /** @var Client|MockObject */ private Client $client; - public function setUp(): void + public function testProxyInitializeWithException(): void { - parent::setUp(); - $this->dm = $this->createMockedDocumentManager(); - } - public function testProxyInitializeWithException(): void - { $collection = $this->createMock(Collection::class); $database = $this->createMock(Database::class); @@ -81,6 +77,17 @@ private function createMockedDocumentManager(): DocumentManager return DocumentManager::create($this->client, $config); } + + public function testCreateProxyForDocumentWithUnmappedProperties(): void + { + $proxy = $this->dm->getReference(DocumentWithUnmappedProperties::class, '123'); + self::assertInstanceOf(GhostObjectInterface::class, $proxy); + + // Disable initialiser so we can access properties without initialising the object + $proxy->setProxyInitializer(null); + + self::assertSame('bar', $proxy->foo); + } } class DocumentNotFoundListener diff --git a/tests/Documents/DocumentWithUnmappedProperties.php b/tests/Documents/DocumentWithUnmappedProperties.php new file mode 100644 index 000000000..06339e79b --- /dev/null +++ b/tests/Documents/DocumentWithUnmappedProperties.php @@ -0,0 +1,17 @@ + Date: Mon, 25 Nov 2024 03:22:15 +0000 Subject: [PATCH 12/28] Bump doctrine/.github from 5.1.0 to 5.3.0 Bumps [doctrine/.github](https://github.com/doctrine/.github) from 5.1.0 to 5.3.0. - [Release notes](https://github.com/doctrine/.github/releases) - [Commits](https://github.com/doctrine/.github/compare/5.1.0...5.3.0) --- updated-dependencies: - dependency-name: doctrine/.github dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/coding-standards.yml | 2 +- .github/workflows/release-on-milestone-closed.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 486a54896..90a668340 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@5.1.0" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@5.3.0" diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index e193d87b6..429cc0b14 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@5.1.0" + uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@5.3.0" secrets: GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} From a860babb287d8c6f50d0af2aa8715fdf2c18ef61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 03:56:11 +0000 Subject: [PATCH 13/28] Bump doctrine/.github from 5.3.0 to 6.0.0 Bumps [doctrine/.github](https://github.com/doctrine/.github) from 5.3.0 to 6.0.0. - [Release notes](https://github.com/doctrine/.github/releases) - [Commits](https://github.com/doctrine/.github/compare/5.3.0...6.0.0) --- updated-dependencies: - dependency-name: doctrine/.github dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/coding-standards.yml | 2 +- .github/workflows/release-on-milestone-closed.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 90a668340..20decf3a7 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@5.3.0" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@6.0.0" diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index 429cc0b14..af9efcdad 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@5.3.0" + uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@6.0.0" secrets: GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} From 9d9104774b8584244a9c3b053759593b4c28d8d8 Mon Sep 17 00:00:00 2001 From: Stefan Beer Date: Fri, 13 Dec 2024 16:47:19 +0100 Subject: [PATCH 14/28] doc: Fix simple search engine cookbook Use array instead of ArrayCollection. It is not possible to use an ArrayCollection with a collection field type. This would result in a runtime exception: "Collection type requires value of type array or null, Doctrine\Common\Collections\ArrayCollection given (Doctrine\ODM\MongoDB\MongoDBException)". --- docs/en/cookbook/simple-search-engine.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/en/cookbook/simple-search-engine.rst b/docs/en/cookbook/simple-search-engine.rst index b26595766..659c88216 100644 --- a/docs/en/cookbook/simple-search-engine.rst +++ b/docs/en/cookbook/simple-search-engine.rst @@ -3,7 +3,7 @@ Simple Search Engine It is very easy to implement a simple keyword search engine with MongoDB. Because of its flexible schema less nature we can store the keywords we want to search through directly -on the document. MongoDB is capable of indexing the embedded documents so the results are fast +on the document. MongoDB is capable of indexing an array field, so the results are fast and scalable. Sample Model: Product @@ -25,14 +25,13 @@ setup a document like the following with a ``$keywords`` property that is mapped #[Field(type: 'string')] public string $title; - /** @var Collection */ #[Field(type: 'collection')] #[Index] - public Collection $keywords; + public array $keywords; public function __construct() { - $this->keywords = new ArrayCollection(); + $this->keywords = []; } } @@ -47,11 +46,11 @@ Now, create a product and add some keywords: $product = new Product(); $product->title = 'Nike Air Jordan 2011'; - $product->keywords->add('nike shoes'); - $product->keywords->add('jordan shoes'); - $product->keywords->add('air jordan'); - $product->keywords->add('shoes'); - $product->keywords->add('2011'); + $product->keywords[] = 'nike shoes'; + $product->keywords[] = 'jordan shoes'; + $product->keywords[] = 'air jordan'; + $product->keywords[] = 'shoes'; + $product->keywords[] = '2011'; $dm->persist($product); $dm->flush(); From a59a48305346f6f30b7cec2cd18856ba2d119a87 Mon Sep 17 00:00:00 2001 From: Stefan Beer Date: Fri, 13 Dec 2024 17:46:38 +0100 Subject: [PATCH 15/28] doc: Simplify simple search engine cookbook example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jérôme Tamarelle --- docs/en/cookbook/simple-search-engine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/cookbook/simple-search-engine.rst b/docs/en/cookbook/simple-search-engine.rst index 659c88216..a8f67ab22 100644 --- a/docs/en/cookbook/simple-search-engine.rst +++ b/docs/en/cookbook/simple-search-engine.rst @@ -27,7 +27,7 @@ setup a document like the following with a ``$keywords`` property that is mapped #[Field(type: 'collection')] #[Index] - public array $keywords; + public array $keywords = []; public function __construct() { From 09641e59d99b431afccca138539bf644b88a6d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 13 Dec 2024 12:15:57 -0500 Subject: [PATCH 16/28] Remove constructor --- docs/en/cookbook/simple-search-engine.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/en/cookbook/simple-search-engine.rst b/docs/en/cookbook/simple-search-engine.rst index a8f67ab22..267e86095 100644 --- a/docs/en/cookbook/simple-search-engine.rst +++ b/docs/en/cookbook/simple-search-engine.rst @@ -28,11 +28,6 @@ setup a document like the following with a ``$keywords`` property that is mapped #[Field(type: 'collection')] #[Index] public array $keywords = []; - - public function __construct() - { - $this->keywords = []; - } } Working with Keywords From 43514efaa01887d3433e33b194cc055b91b696c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 03:20:00 +0000 Subject: [PATCH 17/28] Bump doctrine/.github from 6.0.0 to 7.1.0 Bumps [doctrine/.github](https://github.com/doctrine/.github) from 6.0.0 to 7.1.0. - [Release notes](https://github.com/doctrine/.github/releases) - [Commits](https://github.com/doctrine/.github/compare/6.0.0...7.1.0) --- updated-dependencies: - dependency-name: doctrine/.github dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/coding-standards.yml | 2 +- .github/workflows/release-on-milestone-closed.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 20decf3a7..6a34dc2a8 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@6.0.0" + uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.1.0" diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index af9efcdad..0c4ee4a59 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@6.0.0" + uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.1.0" secrets: GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} From 4d2e51a37f50e3e766fb64c0a041659356e73a3e Mon Sep 17 00:00:00 2001 From: Rick Ogden Date: Mon, 16 Dec 2024 11:41:27 +0000 Subject: [PATCH 18/28] Update BSON PHP Link (#2708) The page no longer exists for the original link, so updated it to the new URL. --- docs/en/reference/basic-mapping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/reference/basic-mapping.rst b/docs/en/reference/basic-mapping.rst index fbbe373de..82000d9c3 100644 --- a/docs/en/reference/basic-mapping.rst +++ b/docs/en/reference/basic-mapping.rst @@ -151,7 +151,7 @@ Here is a quick overview of the built-in mapping types: - ``string`` - ``timestamp`` -You can read more about the available MongoDB types on `php.net `_. +You can read more about the available MongoDB types on `php.net `_. .. note:: From ad7827c7915b8b867cb28f5252d99b08ec67ac78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 19 Dec 2024 10:54:22 +0100 Subject: [PATCH 19/28] Replace `wrapTransversable` generators to prevent memory leaks (#2709) The generator hold a circular reference to the iterator instance. --- .../ODM/MongoDB/Iterator/CachingIterator.php | 49 +++++---------- .../MongoDB/Iterator/HydratingIterator.php | 25 +++----- .../MongoDB/Iterator/UnrewindableIterator.php | 61 ++++++++----------- .../Iterator/CachingIteratorTest.php | 13 ++++ .../Iterator/UnrewindableIteratorTest.php | 9 +++ 5 files changed, 69 insertions(+), 88 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php index d72671201..8d80284fa 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php @@ -5,7 +5,8 @@ namespace Doctrine\ODM\MongoDB\Iterator; use Countable; -use Generator; +use Iterator as SPLIterator; +use IteratorIterator; use ReturnTypeWillChange; use RuntimeException; use Traversable; @@ -33,13 +34,11 @@ final class CachingIterator implements Countable, Iterator /** @var array */ private array $items = []; - /** @var Generator|null */ - private ?Generator $iterator; + /** @var SPLIterator|null */ + private ?SPLIterator $iterator; private bool $iteratorAdvanced = false; - private bool $iteratorExhausted = false; - /** * Initialize the iterator and stores the first item in the cache. This * effectively rewinds the Traversable and the wrapping Generator, which @@ -51,7 +50,8 @@ final class CachingIterator implements Countable, Iterator */ public function __construct(Traversable $iterator) { - $this->iterator = $this->wrapTraversable($iterator); + $this->iterator = new IteratorIterator($iterator); + $this->iterator->rewind(); $this->storeCurrentItem(); } @@ -94,9 +94,10 @@ public function key() /** @see http://php.net/iterator.next */ public function next(): void { - if (! $this->iteratorExhausted) { - $this->getIterator()->next(); + if ($this->iterator !== null) { + $this->iterator->next(); $this->storeCurrentItem(); + $this->iteratorAdvanced = true; } next($this->items); @@ -126,15 +127,13 @@ public function valid(): bool */ private function exhaustIterator(): void { - while (! $this->iteratorExhausted) { + while ($this->iterator !== null) { $this->next(); } - - $this->iterator = null; } - /** @return Generator */ - private function getIterator(): Generator + /** @return SPLIterator */ + private function getIterator(): SPLIterator { if ($this->iterator === null) { throw new RuntimeException('Iterator has already been destroyed'); @@ -148,28 +147,12 @@ private function getIterator(): Generator */ private function storeCurrentItem(): void { - $key = $this->getIterator()->key(); + $key = $this->iterator->key(); if ($key === null) { - return; + $this->iterator = null; + } else { + $this->items[$key] = $this->getIterator()->current(); } - - $this->items[$key] = $this->getIterator()->current(); - } - - /** - * @param Traversable $traversable - * - * @return Generator - */ - private function wrapTraversable(Traversable $traversable): Generator - { - foreach ($traversable as $key => $value) { - yield $key => $value; - - $this->iteratorAdvanced = true; - } - - $this->iteratorExhausted = true; } } diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php index 94f5f219f..1e1433cdb 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php @@ -6,8 +6,8 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\UnitOfWork; -use Generator; use Iterator; +use IteratorIterator; use ReturnTypeWillChange; use RuntimeException; use Traversable; @@ -24,8 +24,8 @@ */ final class HydratingIterator implements Iterator { - /** @var Generator>|null */ - private ?Generator $iterator; + /** @var Iterator>|null */ + private ?Iterator $iterator; /** * @param Traversable> $traversable @@ -34,7 +34,8 @@ final class HydratingIterator implements Iterator */ public function __construct(Traversable $traversable, private UnitOfWork $unitOfWork, private ClassMetadata $class, private array $unitOfWorkHints = []) { - $this->iterator = $this->wrapTraversable($traversable); + $this->iterator = new IteratorIterator($traversable); + $this->iterator->rewind(); } public function __destruct() @@ -74,8 +75,8 @@ public function valid(): bool return $this->key() !== null; } - /** @return Generator> */ - private function getIterator(): Generator + /** @return Iterator> */ + private function getIterator(): Iterator { if ($this->iterator === null) { throw new RuntimeException('Iterator has already been destroyed'); @@ -93,16 +94,4 @@ private function hydrate(?array $document): ?object { return $document !== null ? $this->unitOfWork->getOrCreateDocument($this->class->name, $document, $this->unitOfWorkHints) : null; } - - /** - * @param Traversable> $traversable - * - * @return Generator> - */ - private function wrapTraversable(Traversable $traversable): Generator - { - foreach ($traversable as $key => $value) { - yield $key => $value; - } - } } diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php index f13baed35..c0352c050 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php @@ -4,7 +4,8 @@ namespace Doctrine\ODM\MongoDB\Iterator; -use Generator; +use Iterator as SPLIterator; +use IteratorIterator; use LogicException; use ReturnTypeWillChange; use RuntimeException; @@ -23,39 +24,34 @@ */ final class UnrewindableIterator implements Iterator { - /** @var Generator|null */ - private ?Generator $iterator; + /** @var SPLIterator|null */ + private ?SPLIterator $iterator; private bool $iteratorAdvanced = false; /** - * Initialize the iterator. This effectively rewinds the Traversable and - * the wrapping Generator, which will execute up to its first yield statement. - * Additionally, this mimics behavior of the SPL iterators and allows users - * to omit an explicit call to rewind() before using the other methods. + * Initialize the iterator. This effectively rewinds the Traversable. + * This mimics behavior of the SPL iterators and allows users to omit an + * explicit call to rewind() before using the other methods. * * @param Traversable $iterator */ public function __construct(Traversable $iterator) { - $this->iterator = $this->wrapTraversable($iterator); - $this->iterator->key(); + $this->iterator = new IteratorIterator($iterator); + $this->iterator->rewind(); } public function toArray(): array { $this->preventRewinding(__METHOD__); - $toArray = function () { - if (! $this->valid()) { - return; - } - - yield $this->key() => $this->current(); - yield from $this->getIterator(); - }; - - return iterator_to_array($toArray()); + try { + return iterator_to_array($this->getIterator()); + } finally { + $this->iteratorAdvanced = true; + $this->iterator = null; + } } /** @return TValue|null */ @@ -84,6 +80,13 @@ public function next(): void } $this->iterator->next(); + $this->iteratorAdvanced = true; + + if ($this->iterator->valid()) { + return; + } + + $this->iterator = null; } /** @see http://php.net/iterator.rewind */ @@ -108,8 +111,8 @@ private function preventRewinding(string $method): void } } - /** @return Generator */ - private function getIterator(): Generator + /** @return SPLIterator */ + private function getIterator(): SPLIterator { if ($this->iterator === null) { throw new RuntimeException('Iterator has already been destroyed'); @@ -117,20 +120,4 @@ private function getIterator(): Generator return $this->iterator; } - - /** - * @param Traversable $traversable - * - * @return Generator - */ - private function wrapTraversable(Traversable $traversable): Generator - { - foreach ($traversable as $key => $value) { - yield $key => $value; - - $this->iteratorAdvanced = true; - } - - $this->iterator = null; - } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Iterator/CachingIteratorTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Iterator/CachingIteratorTest.php index 52b90c1de..dc8373f11 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Iterator/CachingIteratorTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Iterator/CachingIteratorTest.php @@ -59,6 +59,19 @@ public function testIterationWithEmptySet(): void self::assertFalse($iterator->valid()); } + public function testIterationWithInvalidIterator(): void + { + $mock = $this->createMock(Iterator::class); + // The method next() should not be called on a dead cursor. + $mock->expects(self::never())->method('next'); + // The method valid() return false on a dead cursor. + $mock->expects(self::once())->method('valid')->willReturn(false); + + $iterator = new CachingIterator($mock); + + $this->assertEquals([], $iterator->toArray()); + } + public function testPartialIterationDoesNotExhaust(): void { $traversable = $this->getTraversableThatThrows([1, 2, new Exception()]); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Iterator/UnrewindableIteratorTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Iterator/UnrewindableIteratorTest.php index 304724c2a..128420425 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Iterator/UnrewindableIteratorTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Iterator/UnrewindableIteratorTest.php @@ -100,6 +100,15 @@ public function testRewindAfterPartialIteration(): void iterator_to_array($iterator); } + public function testRewindAfterToArray(): void + { + $iterator = new UnrewindableIterator($this->getTraversable([1, 2, 3])); + + $iterator->toArray(); + $this->expectException(LogicException::class); + $iterator->rewind(); + } + public function testToArray(): void { $iterator = new UnrewindableIterator($this->getTraversable([1, 2, 3])); From bf9f88937bac27a8bac3d4a61e91c0c7b79a57ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 19 Dec 2024 22:44:47 +0100 Subject: [PATCH 20/28] Use short urls for all php.net links (#2711) * Use short urls for all php.net links: Detect the user language * Remove links to php.net from source files --- docs/en/reference/attributes-reference.rst | 4 ++-- docs/en/reference/basic-mapping.rst | 4 ++-- docs/en/reference/introduction.rst | 2 +- docs/en/tutorials/getting-started.rst | 2 +- lib/Doctrine/ODM/MongoDB/Aggregation/Stage/MatchStage.php | 1 - lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php | 4 ---- lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php | 3 --- lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php | 3 --- lib/Doctrine/ODM/MongoDB/Query/Query.php | 2 -- 9 files changed, 6 insertions(+), 19 deletions(-) diff --git a/docs/en/reference/attributes-reference.rst b/docs/en/reference/attributes-reference.rst index dedaf906a..55fed36a3 100644 --- a/docs/en/reference/attributes-reference.rst +++ b/docs/en/reference/attributes-reference.rst @@ -1235,7 +1235,7 @@ for the related collection. `Extended JSON specification `_. The recommended way to fill up this property is to create a class constant (eg. ``::VALIDATOR``) using the - `HEREDOC/NOWDOC syntax `_ + `HEREDOC/NOWDOC syntax `_ for clarity and to reference it as the attribute value. - ``action`` - Determines how MongoDB handles documents that violate @@ -1398,6 +1398,6 @@ root class specified in the view mapping. .. _BSON specification: http://bsonspec.org/spec.html .. _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/manual/en/class.mongodb-bson-objectid.php +.. _MongoDB\BSON\ObjectId: https://www.php.net/class.mongodb-bson-objectid .. |FQCN| raw:: html FQCN diff --git a/docs/en/reference/basic-mapping.rst b/docs/en/reference/basic-mapping.rst index 82000d9c3..e25ada74e 100644 --- a/docs/en/reference/basic-mapping.rst +++ b/docs/en/reference/basic-mapping.rst @@ -27,7 +27,7 @@ document mapping metadata: Introduction to Attributes -------------------------- -`PHP attributes `_ +`PHP attributes `_ are a PHP 8+ feature that provides a native way to add metadata to classes, methods, properties, and other language constructs. They replace doctrine annotations by offering a standardized approach to metadata, eliminating @@ -151,7 +151,7 @@ Here is a quick overview of the built-in mapping types: - ``string`` - ``timestamp`` -You can read more about the available MongoDB types on `php.net `_. +You can read more about the available MongoDB types on `php.net `_. .. note:: diff --git a/docs/en/reference/introduction.rst b/docs/en/reference/introduction.rst index 42b44f2be..4452b34e9 100644 --- a/docs/en/reference/introduction.rst +++ b/docs/en/reference/introduction.rst @@ -436,4 +436,4 @@ please create separate clients for your application and ODM. .. _MongoDB: https://www.mongodb.com/ .. _Composer: http://getcomposer.org/ .. _tuning for production: https://ocramius.github.io/ProxyManager/docs/tuning-for-production.html -.. _official PHP manual: https://www.php.net/manual/en/mongodb.installation.php +.. _official PHP manual: https://www.php.net/mongodb.installation diff --git a/docs/en/tutorials/getting-started.rst b/docs/en/tutorials/getting-started.rst index 11ef0ccc8..df745ba92 100755 --- a/docs/en/tutorials/getting-started.rst +++ b/docs/en/tutorials/getting-started.rst @@ -298,4 +298,4 @@ access to the properties and methods that you have defined yourself. You can continue reading :doc:`Introduction to MongoDB Object Document Mapper <../reference/introduction>`. .. _MongoDB Compass: https://www.mongodb.com/products/tools/compass -.. _ObjectId: https://www.php.net/manual/en/class.mongodb-bson-objectid.php +.. _ObjectId: https://www.php.net/class.mongodb-bson-objectid diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/MatchStage.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/MatchStage.php index 2e8d7bef0..c562824ae 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/MatchStage.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/MatchStage.php @@ -26,7 +26,6 @@ public function __construct(Builder $builder) $this->query = $this->expr(); } - /** @see http://php.net/manual/en/language.oop5.cloning.php */ public function __clone() { $this->query = clone $this->query; diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php index 8d80284fa..2a3096ad3 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/CachingIterator.php @@ -55,7 +55,6 @@ public function __construct(Traversable $iterator) $this->storeCurrentItem(); } - /** @see https://php.net/countable.count */ public function count(): int { $currentKey = key($this->items); @@ -91,7 +90,6 @@ public function key() return key($this->items); } - /** @see http://php.net/iterator.next */ public function next(): void { if ($this->iterator !== null) { @@ -103,7 +101,6 @@ public function next(): void next($this->items); } - /** @see http://php.net/iterator.rewind */ public function rewind(): void { /* If the iterator has advanced, exhaust it now so that future iteration @@ -116,7 +113,6 @@ public function rewind(): void reset($this->items); } - /** @see http://php.net/iterator.valid */ public function valid(): bool { return $this->key() !== null; diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php index 7e3fc075c..306e15c8d 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php @@ -57,19 +57,16 @@ public function key() return $this->getIterator()->key(); } - /** @see http://php.net/iterator.next */ public function next(): void { $this->getIterator()->next(); } - /** @see http://php.net/iterator.rewind */ public function rewind(): void { $this->getIterator()->rewind(); } - /** @see http://php.net/iterator.valid */ public function valid(): bool { return $this->key() !== null; diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php b/lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php index c0352c050..288ced8cb 100644 --- a/lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php +++ b/lib/Doctrine/ODM/MongoDB/Iterator/UnrewindableIterator.php @@ -72,7 +72,6 @@ public function key() return null; } - /** @see http://php.net/iterator.next */ public function next(): void { if (! $this->iterator) { @@ -89,13 +88,11 @@ public function next(): void $this->iterator = null; } - /** @see http://php.net/iterator.rewind */ public function rewind(): void { $this->preventRewinding(__METHOD__); } - /** @see http://php.net/iterator.valid */ public function valid(): bool { return $this->key() !== null; diff --git a/lib/Doctrine/ODM/MongoDB/Query/Query.php b/lib/Doctrine/ODM/MongoDB/Query/Query.php index bf3372bb6..62cc56f8b 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/Query.php +++ b/lib/Doctrine/ODM/MongoDB/Query/Query.php @@ -254,8 +254,6 @@ public function getDocumentManager(): DocumentManager * Otherwise, the query will be executed and UnexpectedValueException will * be thrown if {@link Query::execute()} does not return an Iterator. * - * @see http://php.net/manual/en/iteratoraggregate.getiterator.php - * * @throws BadMethodCallException If the query type would not return an Iterator. * @throws UnexpectedValueException If the query did not return an Iterator. * @throws MongoDBException From 27002d10758b9548ced123523beebe335dc456f6 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 10 Jan 2025 09:40:44 +0100 Subject: [PATCH 21/28] Install latest python version to fix CI (#2714) --- .github/workflows/continuous-integration.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index cf9b10606..169724059 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -118,6 +118,11 @@ jobs: dependency-versions: "${{ matrix.dependencies }}" composer-options: "--prefer-dist" + - name: "Install latest Python version" + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - id: setup-mongodb uses: mongodb-labs/drivers-evergreen-tools@master with: From 0b9c1c7555c6f7d359ddfbab02a225d0785cb127 Mon Sep 17 00:00:00 2001 From: parada85 Date: Fri, 17 Jan 2025 11:02:37 +0100 Subject: [PATCH 22/28] Fix discriminatorValue key 0 (#2716) --- .../Mapping/Annotations/DiscriminatorMap.php | 4 +- .../MongoDB/Persisters/DocumentPersister.php | 2 +- .../Tests/Functional/Ticket/GH2158Test.php | 46 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2158Test.php diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DiscriminatorMap.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DiscriminatorMap.php index cd1131a25..644e24443 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DiscriminatorMap.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DiscriminatorMap.php @@ -16,10 +16,10 @@ #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)] final class DiscriminatorMap implements Annotation { - /** @var array */ + /** @var array */ public $value; - /** @param array $value */ + /** @param array $value */ public function __construct(array $value) { $this->value = $value; diff --git a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php index 780c0c600..061b0c9da 100644 --- a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php +++ b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php @@ -1470,7 +1470,7 @@ private function getClassDiscriminatorValues(ClassMetadata $metadata): array foreach ($metadata->subClasses as $className) { $key = array_search($className, $metadata->discriminatorMap); - if (! $key) { + if ($key === false) { continue; } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2158Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2158Test.php new file mode 100644 index 000000000..f01456ce2 --- /dev/null +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2158Test.php @@ -0,0 +1,46 @@ +dm->persist($obj); + $this->dm->flush(); + + self::assertEquals($this->dm->find(GH2158Abstract::class, $obj->getId()), $obj); + } +} + +#[ODM\Document(collection: 'documents')] +#[ODM\InheritanceType('SINGLE_COLLECTION')] +#[ODM\DiscriminatorField('type')] +#[ODM\DiscriminatorMap([0 => GH2158FirstType::class, 1 => GH2158SecondType::class])] +abstract class GH2158Abstract +{ + /** @var string */ + #[ODM\Id] + protected $id; + + public function getId(): string + { + return $this->id; + } +} + +#[ODM\Document] +class GH2158FirstType extends GH2158Abstract +{ +} + +#[ODM\Document] +class GH2158SecondType extends GH2158Abstract +{ +} From 8f0b3702b0c600d54bafc7c418ec13f4c2e990be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 17 Jan 2025 15:16:04 +0100 Subject: [PATCH 23/28] Relax final constrain on stage classes (#2717) --- lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php | 3 ++- lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php index b658656bb..61355728b 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/AddFields.php @@ -11,8 +11,9 @@ * * @psalm-import-type OperatorExpression from Expr * @psalm-type AddFieldsStageExpression = array{'$addFields': array} + * @final */ -final class AddFields extends Operator +class AddFields extends Operator { /** @return AddFieldsStageExpression */ public function getExpression(): array diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php index cca5aed34..3d4afacac 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Set.php @@ -11,8 +11,9 @@ * * @psalm-import-type OperatorExpression from Expr * @psalm-type SetStageExpression = array{'$set': array} + * @final */ -final class Set extends Operator +class Set extends Operator { /** @psalm-return SetStageExpression */ public function getExpression(): array From f78c67c620ea254ba35590a627d644837a3baeed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 6 Jul 2024 16:34:05 +0200 Subject: [PATCH 24/28] Ensure compatibility with persistence 4 This is mostly about adding return type declarations. In one instance though, it is more than that: ClassMetadataFactory redeclares $cacheSalt, a protected property inherited from the persistence package. Since it is not possible to widen or narrow the type, and since the redeclaration seems to be about setting a default value, let us set it in the constructor. --- composer.json | 2 +- lib/Doctrine/ODM/MongoDB/DocumentManager.php | 33 ++++++++++--------- .../ODM/MongoDB/Mapping/ClassMetadata.php | 2 +- .../MongoDB/Mapping/ClassMetadataFactory.php | 10 +++--- .../Mapping/Driver/AttributeDriver.php | 2 +- .../ODM/MongoDB/Mapping/Driver/XmlDriver.php | 2 +- lib/Doctrine/ODM/MongoDB/UnitOfWork.php | 2 +- .../ODM/MongoDB/Tests/UnitOfWorkTest.php | 2 +- 8 files changed, 29 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 1bdaf6c08..dfc8aa1a1 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "doctrine/collections": "^1.5 || ^2.0", "doctrine/event-manager": "^1.0 || ^2.0", "doctrine/instantiator": "^1.1 || ^2", - "doctrine/persistence": "^3.2", + "doctrine/persistence": "^3.2 || ^4", "friendsofphp/proxy-manager-lts": "^1.0", "jean85/pretty-package-versions": "^1.3.0 || ^2.0.1", "mongodb/mongodb": "^1.17.0", diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index b8c40aea3..e07c2ddad 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -21,6 +21,7 @@ use Doctrine\ODM\MongoDB\Repository\ViewRepository; use Doctrine\Persistence\Mapping\ProxyClassNameResolver; use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectRepository; use InvalidArgumentException; use Jean85\PrettyVersions; use MongoDB\Client; @@ -218,12 +219,8 @@ public function getClient(): Client return $this->client; } - /** - * Gets the metadata factory used to gather the metadata of classes. - * - * @return ClassMetadataFactoryInterface - */ - public function getMetadataFactory() + /** Gets the metadata factory used to gather the metadata of classes. */ + public function getMetadataFactory(): ClassmetadataFactoryInterface { return $this->metadataFactory; } @@ -235,7 +232,7 @@ public function getMetadataFactory() * * @param object $obj */ - public function initializeObject($obj) + public function initializeObject($obj): void { $this->unitOfWork->initializeObject($obj); } @@ -243,8 +240,12 @@ public function initializeObject($obj) /** * Helper method to check whether a lazy loading proxy or persistent collection has been initialized. */ - public function isUninitializedObject(object $obj): bool + public function isUninitializedObject(mixed $obj): bool { + if (! is_object($obj)) { + return false; + } + return $this->unitOfWork->isUninitializedObject($obj); } @@ -437,7 +438,7 @@ public function createAggregationBuilder(string $documentName): Aggregation\Buil * * @throws InvalidArgumentException When the given $object param is not an object. */ - public function persist($object) + public function persist($object): void { if (! is_object($object)) { throw new InvalidArgumentException(gettype($object)); @@ -457,7 +458,7 @@ public function persist($object) * * @throws InvalidArgumentException When the $object param is not an object. */ - public function remove($object) + public function remove($object): void { if (! is_object($object)) { throw new InvalidArgumentException(gettype($object)); @@ -475,7 +476,7 @@ public function remove($object) * * @throws InvalidArgumentException When the given $object param is not an object. */ - public function refresh($object) + public function refresh($object): void { if (! is_object($object)) { throw new InvalidArgumentException(gettype($object)); @@ -496,7 +497,7 @@ public function refresh($object) * * @throws InvalidArgumentException When the $object param is not an object. */ - public function detach($object) + public function detach($object): void { if (! is_object($object)) { throw new InvalidArgumentException(gettype($object)); @@ -556,7 +557,7 @@ public function unlock(object $document): void * * @template T of object */ - public function getRepository($className) + public function getRepository($className): ObjectRepository { return $this->repositoryFactory->getRepository($this, $className); } @@ -572,7 +573,7 @@ public function getRepository($className) * @throws MongoDBException * @throws Throwable From event listeners. */ - public function flush(array $options = []) + public function flush(array $options = []): void { $this->errorIfClosed(); $this->unitOfWork->commit($options); @@ -680,7 +681,7 @@ public function find($className, $id, $lockMode = LockMode::NONE, $lockVersion = * * @param string|null $objectName if given, only documents of this type will get detached */ - public function clear($objectName = null) + public function clear($objectName = null): void { if ($objectName !== null) { trigger_deprecation( @@ -716,7 +717,7 @@ public function close() * * @throws InvalidArgumentException When the $object param is not an object. */ - public function contains($object) + public function contains($object): bool { if (! is_object($object)) { throw new InvalidArgumentException(gettype($object)); diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php index 85dddf148..45cf542ae 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php @@ -2241,7 +2241,7 @@ public function isAssociationInverseSide($assocName): bool } /** @param string $assocName */ - public function getAssociationMappedByTargetField($assocName) + public function getAssociationMappedByTargetField($assocName): string { throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.'); } diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php index 4c11dda38..1b78b1e62 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php @@ -40,9 +40,6 @@ */ final class ClassMetadataFactory extends AbstractClassMetadataFactory implements ClassMetadataFactoryInterface { - /** @var string */ - protected $cacheSalt = '$MONGODBODMCLASSMETADATA'; - /** @var DocumentManager The DocumentManager instance */ private DocumentManager $dm; @@ -55,6 +52,11 @@ final class ClassMetadataFactory extends AbstractClassMetadataFactory implements /** @var EventManager The event manager instance */ private EventManager $evm; + public function __construct() + { + $this->cacheSalt = '$MONGODBODMCLASSMETADATA'; + } + public function setDocumentManager(DocumentManager $dm): void { $this->dm = $dm; @@ -82,7 +84,7 @@ protected function initialize(): void } /** @param string $className */ - protected function onNotFoundMetadata($className) + protected function onNotFoundMetadata($className): ?ClassMetadata { if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) { return null; diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php index fb3b00543..6a0ed2ee2 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AttributeDriver.php @@ -62,7 +62,7 @@ public function __construct($paths = null, ?Reader $reader = null) $this->addPaths((array) $paths); } - public function isTransient($className) + public function isTransient($className): bool { $classAttributes = $this->getClassAttributes(new ReflectionClass($className)); diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php index 8b093dd2b..34084182e 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php @@ -81,7 +81,7 @@ public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENS } // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed - public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\ClassMetadata $metadata) + public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\ClassMetadata $metadata): void { assert($metadata instanceof ClassMetadata); $xmlRoot = $this->getElement($className); diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index f7a728693..a9fd158a4 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -2968,7 +2968,7 @@ public function clearDocumentChangeSet(string $oid): void * @param mixed $oldValue The old value of the property. * @param mixed $newValue The new value of the property. */ - public function propertyChanged($sender, $propertyName, $oldValue, $newValue) + public function propertyChanged($sender, $propertyName, $oldValue, $newValue): void { $oid = spl_object_hash($sender); $class = $this->dm->getClassMetadata($sender::class); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php index e79c0ef93..fead82a86 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php @@ -653,7 +653,7 @@ public function setTransient($value): void $this->transient = $value; } - public function addPropertyChangedListener(PropertyChangedListener $listener) + public function addPropertyChangedListener(PropertyChangedListener $listener): void { $this->_listeners[] = $listener; } From d726f05365921e261c8d5610151dd0c9085c2030 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 21 Jan 2025 23:14:50 +0100 Subject: [PATCH 25/28] Improve query and aggregator classes (#2702) * Add joint iterable interface for queries and aggregations * Update declared return types for query and aggregation builders --- .../ODM/MongoDB/Aggregation/Aggregation.php | 24 ++++++++++++-- .../ODM/MongoDB/Aggregation/Builder.php | 5 ++- .../ODM/MongoDB/Iterator/IterableResult.php | 32 +++++++++++++++++++ lib/Doctrine/ODM/MongoDB/Query/Builder.php | 5 ++- lib/Doctrine/ODM/MongoDB/Query/Query.php | 10 +++--- 5 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 lib/Doctrine/ODM/MongoDB/Iterator/IterableResult.php diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php index cf722aa86..32379a82c 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Aggregation.php @@ -7,11 +7,11 @@ use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Iterator\CachingIterator; use Doctrine\ODM\MongoDB\Iterator\HydratingIterator; +use Doctrine\ODM\MongoDB\Iterator\IterableResult; use Doctrine\ODM\MongoDB\Iterator\Iterator; use Doctrine\ODM\MongoDB\Iterator\UnrewindableIterator; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Iterator as SPLIterator; -use IteratorAggregate; use MongoDB\Collection; use MongoDB\Driver\CursorInterface; @@ -19,7 +19,7 @@ use function assert; /** @phpstan-import-type PipelineExpression from Builder */ -final class Aggregation implements IteratorAggregate +final class Aggregation implements IterableResult { /** * @param array $pipeline @@ -30,6 +30,26 @@ public function __construct(private DocumentManager $dm, private ?ClassMetadata { } + public function execute(): Iterator + { + return $this->getIterator(); + } + + /** + * Execute the query and return the first result. + * + * @return array|object|null + */ + public function getSingleResult(): mixed + { + $clone = clone $this; + + // Limit the pipeline to a single result for efficiency + $this->pipeline[] = ['$limit' => 1]; + + return $clone->getIterator()->current() ?: null; + } + public function getIterator(): Iterator { // Force cursor to be used diff --git a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php index 8ec6ad5cb..cbc62a3b8 100644 --- a/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php +++ b/lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php @@ -6,6 +6,7 @@ use Doctrine\ODM\MongoDB\Aggregation\Stage\Sort; use Doctrine\ODM\MongoDB\DocumentManager; +use Doctrine\ODM\MongoDB\Iterator\IterableResult; use Doctrine\ODM\MongoDB\Iterator\Iterator; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Persisters\DocumentPersister; @@ -246,8 +247,10 @@ public function geoNear($x, $y = null): Stage\GeoNear * Returns an aggregation object for the current pipeline * * @param array $options + * + * @return Aggregation */ - public function getAggregation(array $options = []): Aggregation + public function getAggregation(array $options = []): IterableResult { $class = $this->hydrationClass ? $this->dm->getClassMetadata($this->hydrationClass) : null; diff --git a/lib/Doctrine/ODM/MongoDB/Iterator/IterableResult.php b/lib/Doctrine/ODM/MongoDB/Iterator/IterableResult.php new file mode 100644 index 000000000..1e6a60c35 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Iterator/IterableResult.php @@ -0,0 +1,32 @@ + $options + * + * @return Query */ - public function getQuery(array $options = []): Query + public function getQuery(array $options = []): IterableResult { $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name); diff --git a/lib/Doctrine/ODM/MongoDB/Query/Query.php b/lib/Doctrine/ODM/MongoDB/Query/Query.php index 62cc56f8b..d33961f6d 100644 --- a/lib/Doctrine/ODM/MongoDB/Query/Query.php +++ b/lib/Doctrine/ODM/MongoDB/Query/Query.php @@ -9,6 +9,7 @@ use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Iterator\CachingIterator; use Doctrine\ODM\MongoDB\Iterator\HydratingIterator; +use Doctrine\ODM\MongoDB\Iterator\IterableResult; use Doctrine\ODM\MongoDB\Iterator\Iterator; use Doctrine\ODM\MongoDB\Iterator\PrimingIterator; use Doctrine\ODM\MongoDB\Iterator\UnrewindableIterator; @@ -16,7 +17,6 @@ use Doctrine\ODM\MongoDB\MongoDBException; use Doctrine\ODM\MongoDB\UnitOfWork; use InvalidArgumentException; -use IteratorAggregate; use MongoDB\Collection; use MongoDB\DeleteResult; use MongoDB\Driver\ReadPreference; @@ -63,7 +63,7 @@ * @phpstan-import-type Hints from UnitOfWork * @phpstan-import-type SortMeta from Sort */ -final class Query implements IteratorAggregate +final class Query implements IterableResult { public const TYPE_FIND = 1; public const TYPE_FIND_AND_UPDATE = 2; @@ -200,7 +200,7 @@ public function debug(?string $name = null) * * @throws MongoDBException */ - public function execute() + public function execute(): mixed { $results = $this->runQuery(); @@ -296,7 +296,7 @@ public function getQuery(): array * * @return array|object|null */ - public function getSingleResult() + public function getSingleResult(): mixed { $clonedQuery = clone $this; $clonedQuery->query['limit'] = 1; @@ -353,7 +353,7 @@ public function setRewindable(bool $rewindable = true): void /** * Execute the query and return its results as an array. * - * @see IteratorAggregate::toArray() + * @see Iterator::toArray() * * @return mixed[] */ From 336a70e8616fb28d760d0bf28760a7c254988b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 22 Jan 2025 13:18:50 +0100 Subject: [PATCH 26/28] Switch proxies to `LazyGhostTrait` (#2700) * Generate proxy classes using symfony/var-exporter * Leverage UOW::initializeObject in tests * Trigger lifecycleEventManager from lazy ghost object * Run the full test suite with proxy manager * Fix assert not lazy object * Fix deprecation version and refactor autoregenerate condition * Fix testCreateProxyForDocumentWithUnmappedProperties * Keep a single ProxyManagerConfiguration instance --- .github/workflows/continuous-integration.yml | 21 + composer.json | 5 +- lib/Doctrine/ODM/MongoDB/Configuration.php | 121 ++++-- lib/Doctrine/ODM/MongoDB/DocumentManager.php | 10 +- .../ODM/MongoDB/Hydrator/HydratorFactory.php | 7 + .../ODM/MongoDB/Mapping/ClassMetadata.php | 9 +- lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php | 90 +++++ .../Proxy/Factory/LazyGhostProxyFactory.php | 380 ++++++++++++++++++ .../MongoDB/Proxy/Factory/ProxyFactory.php | 5 +- .../Proxy/Factory/StaticProxyFactory.php | 2 + .../ODM/MongoDB/Proxy/InternalProxy.php | 18 + .../LazyGhostProxyClassNameResolver.php | 31 ++ lib/Doctrine/ODM/MongoDB/UnitOfWork.php | 19 +- phpstan-baseline.neon | 5 + phpunit.xml.dist | 1 + .../ODM/MongoDB/Tests/BaseTestCase.php | 8 + .../Tests/Functional/IdentifiersTest.php | 5 +- .../Tests/Functional/ReferencePrimerTest.php | 47 ++- .../Tests/Functional/ReferencesTest.php | 22 +- .../Tests/Functional/SimpleReferencesTest.php | 4 +- .../Tests/Functional/Ticket/GH520Test.php | 3 +- .../Tests/Functional/Ticket/GH593Test.php | 7 +- .../Tests/Functional/Ticket/GH602Test.php | 7 +- .../Tests/Functional/Ticket/GH852Test.php | 7 +- .../Tests/Functional/Ticket/GH936Test.php | 3 +- .../ODM/MongoDB/Tests/Functional/ViewTest.php | 5 +- .../ODM/MongoDB/Tests/HydratorTest.php | 9 +- .../Tests/Mapping/ClassMetadataTest.php | 9 +- ...xyFactoryTest.php => ProxyFactoryTest.php} | 19 +- .../ODM/MongoDB/Tests/UnitOfWorkTest.php | 3 +- 30 files changed, 753 insertions(+), 129 deletions(-) create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Resolver/LazyGhostProxyClassNameResolver.php rename tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/{StaticProxyFactoryTest.php => ProxyFactoryTest.php} (81%) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 244d7faf7..62f56d0e9 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -34,6 +34,8 @@ jobs: - "highest" symfony-version: - "stable" + proxy: + - "lazy-ghost" include: # Test against lowest dependencies - dependencies: "lowest" @@ -42,6 +44,7 @@ jobs: driver-version: "1.17.0" topology: "server" symfony-version: "stable" + proxy: "lazy-ghost" # Test with highest dependencies - topology: "server" php-version: "8.2" @@ -49,6 +52,7 @@ jobs: driver-version: "stable" dependencies: "highest" symfony-version: "7" + proxy: "lazy-ghost" # Test with a 5.0 replica set - topology: "replica_set" php-version: "8.2" @@ -56,6 +60,14 @@ jobs: driver-version: "stable" dependencies: "highest" symfony-version: "stable" + proxy: "lazy-ghost" + # Test with ProxyManager + - php-version: "8.2" + mongodb-version: "5.0" + driver-version: "stable" + dependencies: "highest" + symfony-version: "stable" + proxy: "proxy-manager" # Test with a 5.0 sharded cluster # Currently disabled due to a bug where MongoDB reports "sharding status unknown" # - topology: "sharded_cluster" @@ -64,6 +76,7 @@ jobs: # driver-version: "stable" # dependencies: "highest" # symfony-version: "stable" +# proxy: "lazy-ghost" steps: - name: "Checkout" @@ -111,6 +124,13 @@ jobs: composer require --no-update symfony/var-dumper:^7@dev composer require --no-update --dev symfony/cache:^7@dev + - name: "Remove proxy-manager-lts" + if: "${{ matrix.proxy != 'proxy-manager' }}" + run: | + # proxy-manager-lts is not installed by default and must not be used + # unless explicitly requested + composer remove --no-update --dev friendsofphp/proxy-manager-lts + - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v3" with: @@ -132,3 +152,4 @@ jobs: run: "vendor/bin/phpunit" env: DOCTRINE_MONGODB_SERVER: ${{ steps.setup-mongodb.outputs.cluster-uri }} + USE_LAZY_GHOST_OBJECTS: ${{ matrix.proxy == 'lazy-ghost' && '1' || '0' }}" diff --git a/composer.json b/composer.json index dfc8aa1a1..2ae0368ac 100644 --- a/composer.json +++ b/composer.json @@ -28,19 +28,20 @@ "doctrine/event-manager": "^1.0 || ^2.0", "doctrine/instantiator": "^1.1 || ^2", "doctrine/persistence": "^3.2 || ^4", - "friendsofphp/proxy-manager-lts": "^1.0", "jean85/pretty-package-versions": "^1.3.0 || ^2.0.1", "mongodb/mongodb": "^1.17.0", "psr/cache": "^1.0 || ^2.0 || ^3.0", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/deprecation-contracts": "^2.2 || ^3.0", - "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0" + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0" }, "require-dev": { "ext-bcmath": "*", "doctrine/annotations": "^1.12 || ^2.0", "doctrine/coding-standard": "^12.0", "doctrine/orm": "^3.2", + "friendsofphp/proxy-manager-lts": "^1.0", "jmikola/geojson": "^1.0", "phpbench/phpbench": "^1.0.0", "phpstan/phpstan": "~1.10.67", diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index adc486991..ca61fcbfc 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -24,6 +24,7 @@ use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\ObjectRepository; use InvalidArgumentException; +use LogicException; use MongoDB\Driver\WriteConcern; use ProxyManager\Configuration as ProxyManagerConfiguration; use ProxyManager\Factory\LazyLoadingGhostFactory; @@ -33,6 +34,7 @@ use ReflectionClass; use function array_key_exists; +use function class_exists; use function interface_exists; use function trigger_deprecation; use function trim; @@ -82,12 +84,23 @@ class Configuration */ public const AUTOGENERATE_EVAL = 3; + /** + * Autogenerate the proxy class when the proxy file does not exist or + * when the proxied file changed. + * + * This strategy causes a file_exists() call whenever any proxy is used the + * first time in a request. When the proxied file is changed, the proxy will + * be updated. + */ + public const AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED = 4; + /** * Array of attributes for this configuration instance. * * @phpstan-var array{ * autoGenerateHydratorClasses?: self::AUTOGENERATE_*, * autoGeneratePersistentCollectionClasses?: self::AUTOGENERATE_*, + * autoGenerateProxyClasses?: self::AUTOGENERATE_*, * classMetadataFactoryName?: class-string, * defaultCommitOptions?: CommitOptions, * defaultDocumentRepositoryClassName?: class-string>, @@ -106,6 +119,8 @@ class Configuration * persistentCollectionGenerator?: PersistentCollectionGenerator, * persistentCollectionDir?: string, * persistentCollectionNamespace?: string, + * proxyDir?: string, + * proxyNamespace?: string, * repositoryFactory?: RepositoryFactory * } */ @@ -113,17 +128,12 @@ class Configuration private ?CacheItemPoolInterface $metadataCache = null; + /** @deprecated */ private ProxyManagerConfiguration $proxyManagerConfiguration; - private int $autoGenerateProxyClasses = self::AUTOGENERATE_EVAL; - private bool $useTransactionalFlush = false; - public function __construct() - { - $this->proxyManagerConfiguration = new ProxyManagerConfiguration(); - $this->setAutoGenerateProxyClasses(self::AUTOGENERATE_FILE_NOT_EXISTS); - } + private bool $useLazyGhostObject = true; /** * Adds a namespace under a certain alias. @@ -248,14 +258,8 @@ public function setMetadataCache(CacheItemPoolInterface $cache): void */ public function setProxyDir(string $dir): void { - $this->getProxyManagerConfiguration()->setProxiesTargetDir($dir); - - // Recreate proxy generator to ensure its path was updated - if ($this->autoGenerateProxyClasses !== self::AUTOGENERATE_FILE_NOT_EXISTS) { - return; - } - - $this->setAutoGenerateProxyClasses($this->autoGenerateProxyClasses); + $this->attributes['proxyDir'] = $dir; + unset($this->proxyManagerConfiguration); } /** @@ -263,53 +267,43 @@ public function setProxyDir(string $dir): void */ public function getProxyDir(): ?string { - return $this->getProxyManagerConfiguration()->getProxiesTargetDir(); + return $this->attributes['proxyDir'] ?? null; } /** * Gets an int flag that indicates whether proxy classes should always be regenerated * during each script execution. + * + * @return self::AUTOGENERATE_* */ public function getAutoGenerateProxyClasses(): int { - return $this->autoGenerateProxyClasses; + return $this->attributes['autoGenerateProxyClasses'] ?? self::AUTOGENERATE_FILE_NOT_EXISTS; } /** * Sets an int flag that indicates whether proxy classes should always be regenerated * during each script execution. * + * @param self::AUTOGENERATE_* $mode + * * @throws InvalidArgumentException If an invalid mode was given. */ public function setAutoGenerateProxyClasses(int $mode): void { - $this->autoGenerateProxyClasses = $mode; - $proxyManagerConfig = $this->getProxyManagerConfiguration(); - - switch ($mode) { - case self::AUTOGENERATE_FILE_NOT_EXISTS: - $proxyManagerConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy( - new FileLocator($proxyManagerConfig->getProxiesTargetDir()), - )); - - break; - case self::AUTOGENERATE_EVAL: - $proxyManagerConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); - - break; - default: - throw new InvalidArgumentException('Invalid proxy generation strategy given - only AUTOGENERATE_FILE_NOT_EXISTS and AUTOGENERATE_EVAL are supported.'); - } + $this->attributes['autoGenerateProxyClasses'] = $mode; + unset($this->proxyManagerConfiguration); } public function getProxyNamespace(): ?string { - return $this->getProxyManagerConfiguration()->getProxiesNamespace(); + return $this->attributes['proxyNamespace'] ?? null; } public function setProxyNamespace(string $ns): void { - $this->getProxyManagerConfiguration()->setProxiesNamespace($ns); + $this->attributes['proxyNamespace'] = $ns; + unset($this->proxyManagerConfiguration); } public function setHydratorDir(string $dir): void @@ -589,14 +583,39 @@ public function getPersistentCollectionGenerator(): PersistentCollectionGenerato return $this->attributes['persistentCollectionGenerator']; } + /** @deprecated */ public function buildGhostObjectFactory(): LazyLoadingGhostFactory { - return new LazyLoadingGhostFactory(clone $this->getProxyManagerConfiguration()); + return new LazyLoadingGhostFactory($this->getProxyManagerConfiguration()); } + /** @deprecated */ public function getProxyManagerConfiguration(): ProxyManagerConfiguration { - return $this->proxyManagerConfiguration; + if (isset($this->proxyManagerConfiguration)) { + return $this->proxyManagerConfiguration; + } + + $proxyManagerConfiguration = new ProxyManagerConfiguration(); + $proxyManagerConfiguration->setProxiesTargetDir($this->getProxyDir()); + $proxyManagerConfiguration->setProxiesNamespace($this->getProxyNamespace()); + + switch ($this->getAutoGenerateProxyClasses()) { + case self::AUTOGENERATE_FILE_NOT_EXISTS: + $proxyManagerConfiguration->setGeneratorStrategy(new FileWriterGeneratorStrategy( + new FileLocator($proxyManagerConfiguration->getProxiesTargetDir()), + )); + + break; + case self::AUTOGENERATE_EVAL: + $proxyManagerConfiguration->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + + break; + default: + throw new InvalidArgumentException('Invalid proxy generation strategy given - only AUTOGENERATE_FILE_NOT_EXISTS and AUTOGENERATE_EVAL are supported.'); + } + + return $this->proxyManagerConfiguration = $proxyManagerConfiguration; } public function setUseTransactionalFlush(bool $useTransactionalFlush): void @@ -608,6 +627,32 @@ public function isTransactionalFlushEnabled(): bool { return $this->useTransactionalFlush; } + + /** + * Generate proxy classes using Symfony VarExporter's LazyGhostTrait if true. + * Otherwise, use ProxyManager's LazyLoadingGhostFactory (deprecated) + */ + public function setUseLazyGhostObject(bool $flag): void + { + if ($flag === false) { + if (! class_exists(ProxyManagerConfiguration::class)) { + throw new LogicException('Package "friendsofphp/proxy-manager-lts" is required to disable LazyGhostObject.'); + } + + trigger_deprecation( + 'doctrine/mongodb-odm', + '2.10', + 'Using "friendsofphp/proxy-manager-lts" is deprecated. Use "symfony/var-exporter" LazyGhostObjects instead.', + ); + } + + $this->useLazyGhostObject = $flag; + } + + public function isLazyGhostObjectEnabled(): bool + { + return $this->useLazyGhostObject; + } } interface_exists(MappingDriver::class); diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index e07c2ddad..ab0915c3b 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -9,10 +9,12 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\ODM\MongoDB\Proxy\Factory\LazyGhostProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Resolver\CachingClassNameResolver; use Doctrine\ODM\MongoDB\Proxy\Resolver\ClassNameResolver; +use Doctrine\ODM\MongoDB\Proxy\Resolver\LazyGhostProxyClassNameResolver; use Doctrine\ODM\MongoDB\Proxy\Resolver\ProxyManagerClassNameResolver; use Doctrine\ODM\MongoDB\Query\FilterCollection; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; @@ -157,7 +159,9 @@ protected function __construct(?Client $client = null, ?Configuration $config = ], ); - $this->classNameResolver = new CachingClassNameResolver(new ProxyManagerClassNameResolver($this->config)); + $this->classNameResolver = $config->isLazyGhostObjectEnabled() + ? new CachingClassNameResolver(new LazyGhostProxyClassNameResolver()) + : new CachingClassNameResolver(new ProxyManagerClassNameResolver($this->config)); $metadataFactoryClassName = $this->config->getClassMetadataFactoryName(); $this->metadataFactory = new $metadataFactoryClassName(); @@ -182,7 +186,9 @@ protected function __construct(?Client $client = null, ?Configuration $config = $this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory); $this->schemaManager = new SchemaManager($this, $this->metadataFactory); - $this->proxyFactory = new StaticProxyFactory($this); + $this->proxyFactory = $config->isLazyGhostObjectEnabled() + ? new LazyGhostProxyFactory($this, $config->getProxyDir(), $config->getProxyNamespace(), $config->getAutoGenerateProxyClasses()) + : new StaticProxyFactory($this); $this->repositoryFactory = $this->config->getRepositoryFactory(); } diff --git a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php index 2b91220d5..1e8135b6a 100644 --- a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php @@ -11,6 +11,7 @@ use Doctrine\ODM\MongoDB\Event\PreLoadEventArgs; use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Types\Type; use Doctrine\ODM\MongoDB\UnitOfWork; use ProxyManager\Proxy\GhostObjectInterface; @@ -448,6 +449,12 @@ public function hydrate(object $document, array $data, array $hints = []): array } } + if ($document instanceof InternalProxy) { + // Skip initialization to not load any object data + $document->__setInitialized(true); + } + + // Support for legacy proxy-manager-lts if ($document instanceof GhostObjectInterface && $document->getProxyInitializer() !== null) { // Inject an empty initialiser to not load any object data $document->setProxyInitializer(static function ( diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php index 45cf542ae..896faf240 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php @@ -14,6 +14,7 @@ use Doctrine\ODM\MongoDB\Id\IdGenerator; use Doctrine\ODM\MongoDB\LockException; use Doctrine\ODM\MongoDB\Mapping\Annotations\TimeSeries; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Types\Incrementable; use Doctrine\ODM\MongoDB\Types\Type; use Doctrine\ODM\MongoDB\Types\Versionable; @@ -1893,9 +1894,11 @@ public function getIdentifierObject(object $document) */ public function setFieldValue(object $document, string $field, $value): void { - if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) { + if ($document instanceof InternalProxy && ! $document->__isInitialized()) { //property changes to an uninitialized proxy will not be tracked or persisted, //so the proxy needs to be loaded first. + $document->__load(); + } elseif ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) { $document->initializeProxy(); } @@ -1909,7 +1912,9 @@ public function setFieldValue(object $document, string $field, $value): void */ public function getFieldValue(object $document, string $field) { - if ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) { + if ($document instanceof InternalProxy && $field !== $this->identifier && ! $document->__isInitialized()) { + $document->__load(); + } elseif ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) { $document->initializeProxy(); } diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php b/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php new file mode 100644 index 000000000..d346a0c19 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php @@ -0,0 +1,90 @@ +; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR + */ +class extends \ implements \ +{ + + + public function __isInitialized(): bool + { + return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); + } + + public function __serialize(): array + { + + } +} + +EOPHP; + + /** The UnitOfWork this factory uses to retrieve persisters */ + private readonly UnitOfWork $uow; + + /** @var Configuration::AUTOGENERATE_* */ + private int $autoGenerate; + + /** @var array */ + private array $proxyFactories = []; + + private LifecycleEventManager $lifecycleEventManager; + + /** + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. + * + * @param DocumentManager $dm The EntityManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param bool|Configuration::AUTOGENERATE_* $autoGenerate The strategy for automatically generating proxy classes. + */ + public function __construct( + private readonly DocumentManager $dm, + private readonly string $proxyDir, + private readonly string $proxyNs, + bool|int $autoGenerate = Configuration::AUTOGENERATE_NEVER, + ) { + if (! $proxyDir) { + throw new InvalidArgumentException('You must configure a proxy directory. See docs for details'); + } + + if (! $proxyNs) { + throw new InvalidArgumentException('You must configure a proxy namespace'); + } + + if (is_int($autoGenerate) && ($autoGenerate < 0 || $autoGenerate > 4)) { + throw new InvalidArgumentException(sprintf('Invalid auto generate mode "%s" given.', is_scalar($autoGenerate) ? (string) $autoGenerate : get_debug_type($autoGenerate))); + } + + $this->uow = $dm->getUnitOfWork(); + $this->autoGenerate = (int) $autoGenerate; + $this->lifecycleEventManager = new LifecycleEventManager($dm, $this->uow, $dm->getEventManager()); + } + + /** @param mixed $identifier */ + public function getProxy(ClassMetadata $metadata, $identifier): InternalProxy + { + $className = $metadata->getName(); + + $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); + + return $proxyFactory($identifier); + } + + /** + * Generates proxy classes for all given classes. + * + * @param ClassMetadata[] $classes The classes (ClassMetadata instances) for which to generate proxies. + * @param string|null $proxyDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the EntityManager used + * by this factory is used. + * + * @return int Number of generated proxies. + */ + public function generateProxyClasses(array $classes, string|null $proxyDir = null): int + { + $generated = 0; + + foreach ($classes as $class) { + if ($this->skipClass($class)) { + continue; + } + + $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->proxyDir); + $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); + + $this->generateProxyClass($class, $proxyFileName, $proxyClassName); + + ++$generated; + } + + return $generated; + } + + protected function skipClass(ClassMetadata $metadata): bool + { + return $metadata->isMappedSuperclass + || $metadata->isEmbeddedDocument + || $metadata->getReflectionClass()->isAbstract(); + } + + /** + * Creates a closure capable of initializing a proxy + * + * @param ClassMetadata $classMetadata + * + * @return Closure(InternalProxy&T, array):void + * + * @throws DocumentNotFoundException + * + * @template T of object + */ + private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPersister $persister): Closure + { + $factory = $this; + + return static function (InternalProxy $proxy, mixed $identifier) use ($persister, $classMetadata, $factory): void { + $original = $persister->load([$classMetadata->identifier => $identifier], $proxy); + + if (! $original && ! $factory->lifecycleEventManager->documentNotFound($proxy, $identifier)) { + throw DocumentNotFoundException::documentNotFound($classMetadata->getName(), $identifier); + } + + // phpcs:ignore SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed + if ($proxy instanceof NotifyPropertyChanged) { + $proxy->addPropertyChangedListener($factory->uow); + } + }; + } + + private function getProxyFileName(string $className, string $baseDirectory): string + { + $baseDirectory = $baseDirectory ?: $this->proxyDir; + + return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . InternalProxy::MARKER + . str_replace('\\', '', $className) . '.php'; + } + + /** @param class-string $className */ + private function getProxyFactory(string $className): Closure + { + $skippedProperties = []; + $class = $this->dm->getClassMetadata($className); + $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; + $reflector = $class->getReflectionClass(); + + while ($reflector) { + foreach ($reflector->getProperties($filter) as $property) { + $name = $property->name; + + if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)))) { + continue; + } + + $prefix = $property->isPrivate() ? "\0" . $property->class . "\0" : ($property->isProtected() ? "\0*\0" : ''); + + $skippedProperties[$prefix . $name] = true; + } + + $filter = ReflectionProperty::IS_PRIVATE; + $reflector = $reflector->getParentClass(); + } + + $className = $class->getName(); // aliases and case sensitivity + $entityPersister = $this->uow->getDocumentPersister($className); + $initializer = $this->createLazyInitializer($class, $entityPersister); + $proxyClassName = $this->loadProxyClass($class); + + $proxyFactory = Closure::bind(static function (mixed $identifier) use ($initializer, $skippedProperties, $class): InternalProxy { + /** @see LazyGhostTrait::createLazyGhost() */ + $proxy = static::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void { + $initializer($object, $identifier); + }, $skippedProperties); + + $class->setIdentifierValue($proxy, $identifier); + + return $proxy; + }, null, $proxyClassName); + + return $this->proxyFactories[$className] = $proxyFactory; + } + + private function loadProxyClass(ClassMetadata $class): string + { + $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); + + if (class_exists($proxyClassName, false)) { + return $proxyClassName; + } + + if ($this->autoGenerate === Configuration::AUTOGENERATE_EVAL) { + $this->generateProxyClass($class, null, $proxyClassName); + + return $proxyClassName; + } + + $fileName = $this->getProxyFileName($class->getName(), $this->proxyDir); + + if ( + match ($this->autoGenerate) { + Configuration::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED => ! file_exists($fileName) || filemtime($fileName) < filemtime($class->getReflectionClass()->getFileName()), + Configuration::AUTOGENERATE_FILE_NOT_EXISTS => ! file_exists($fileName), + Configuration::AUTOGENERATE_ALWAYS => true, + Configuration::AUTOGENERATE_NEVER => false, + } + ) { + $this->generateProxyClass($class, $fileName, $proxyClassName); + } + + require $fileName; + + return $proxyClassName; + } + + private function generateProxyClass(ClassMetadata $class, string|null $fileName, string $proxyClassName): void + { + $i = strrpos($proxyClassName, '\\'); + $placeholders = [ + '' => $class->getName(), + '' => substr($proxyClassName, 0, $i), + '' => substr($proxyClassName, 1 + $i), + '' => InternalProxy::class, + ]; + + preg_match_all('(<([a-zA-Z]+)>)', self::PROXY_CLASS_TEMPLATE, $placeholderMatches); + + foreach (array_combine($placeholderMatches[0], $placeholderMatches[1]) as $placeholder => $name) { + $placeholders[$placeholder] ?? $placeholders[$placeholder] = $this->{'generate' . ucfirst($name)}($class); + } + + $proxyCode = strtr(self::PROXY_CLASS_TEMPLATE, $placeholders); + + if (! $fileName) { + if (! class_exists($proxyClassName)) { + eval(substr($proxyCode, 5)); + } + + return; + } + + $parentDirectory = dirname($fileName); + + if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true) || ! is_writable($parentDirectory)) { + throw new InvalidArgumentException(sprintf('Your proxy directory "%s" must be writable', $this->proxyDir)); + } + + $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); + + file_put_contents($tmpFileName, $proxyCode); + @chmod($tmpFileName, 0664); + rename($tmpFileName, $fileName); + } + + private function generateUseLazyGhostTrait(ClassMetadata $class): string + { + $code = ProxyHelper::generateLazyGhost($class->getReflectionClass()); + $code = substr($code, 7 + (int) strpos($code, "\n{")); + $code = substr($code, 0, (int) strpos($code, "\n}")); + $code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait { + initializeLazyObject as private; + setLazyObjectAsInitialized as public __setInitialized; + isLazyObjectInitialized as private; + createLazyGhost as private; + resetLazyObject as private; + } + + public function __load(): void + { + $this->initializeLazyObject(); + } + '), $code); + + return $code; + } + + private function generateSerializeImpl(ClassMetadata $class): string + { + $reflector = $class->getReflectionClass(); + $properties = $reflector->hasMethod('__serialize') ? 'parent::__serialize()' : '(array) $this'; + + $code = '$properties = ' . $properties . '; + unset($properties["\0" . self::class . "\0lazyObjectState"]); + + '; + + if ($reflector->hasMethod('__serialize') || ! $reflector->hasMethod('__sleep')) { + return $code . 'return $properties;'; + } + + return $code . '$data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0' . $reflector->name . '\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(sprintf(\'serialize(): "%s" returned as member variable from __sleep() but does not exist\', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data;'; + } + + private static function generateProxyClassName(string $className, string $proxyNamespace): string + { + return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\'); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php index 1540e40e9..4a8d93aee 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php @@ -5,7 +5,6 @@ namespace Doctrine\ODM\MongoDB\Proxy\Factory; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use ProxyManager\Proxy\GhostObjectInterface; interface ProxyFactory { @@ -19,9 +18,9 @@ public function generateProxyClasses(array $classes): int; * @param mixed $identifier * @phpstan-param ClassMetadata $metadata * - * @return T&GhostObjectInterface + * @return T * * @template T of object */ - public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface; + public function getProxy(ClassMetadata $metadata, $identifier): object; } diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php index e13bbfb14..d3d6d2dc1 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php @@ -22,6 +22,8 @@ /** * This factory is used to create proxy objects for documents at runtime. + * + * @deprecated since 2.10, use LazyGhostProxyFactory instead */ final class StaticProxyFactory implements ProxyFactory { diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php b/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php new file mode 100644 index 000000000..90af928ca --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php @@ -0,0 +1,18 @@ + + */ +interface InternalProxy extends Proxy +{ + public function __setInitialized(bool $initialized): void; +} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/LazyGhostProxyClassNameResolver.php b/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/LazyGhostProxyClassNameResolver.php new file mode 100644 index 000000000..e9bb658cf --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/LazyGhostProxyClassNameResolver.php @@ -0,0 +1,31 @@ +resolveClassName($class); + } + + public function resolveClassName(string $className): string + { + $pos = strrpos($className, '\\' . Proxy::MARKER . '\\'); + + if ($pos === false) { + return $className; + } + + return substr($className, $pos + Proxy::MARKER_LENGTH + 2); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index a9fd158a4..a2284b861 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -14,6 +14,7 @@ use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface; use Doctrine\ODM\MongoDB\Persisters\CollectionPersister; use Doctrine\ODM\MongoDB\Persisters\PersistenceBuilder; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Query\Query; use Doctrine\ODM\MongoDB\Types\DateType; use Doctrine\ODM\MongoDB\Types\Type; @@ -970,6 +971,10 @@ private function computeAssociationChanges(object $parentDocument, array $assoc, $class = $this->dm->getClassMetadata($parentDocument::class); $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument); + if ($value instanceof InternalProxy && ! $value->__isInitialized()) { + return; + } + if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) { return; } @@ -2777,7 +2782,14 @@ public function getOrCreateDocument(string $className, array $data, array &$hint $document = $this->identityMap[$class->name][$serializedId]; $oid = spl_object_hash($document); if ($this->isUninitializedObject($document)) { - $document->setProxyInitializer(null); + if ($document instanceof InternalProxy) { + $document->__setInitialized(true); + } elseif ($document instanceof GhostObjectInterface) { + $document->setProxyInitializer(null); + } else { + throw new \RuntimeException(sprintf('Expected uninitialized proxy or ghost object from class "%s"', $document::name)); + } + $overrideLocalValues = true; if ($document instanceof NotifyPropertyChanged) { $document->addPropertyChangedListener($this); @@ -3057,7 +3069,9 @@ public function getScheduledCollectionUpdates(): array */ public function initializeObject(object $obj): void { - if ($obj instanceof GhostObjectInterface && $obj->isProxyInitialized() === false) { + if ($obj instanceof InternalProxy && $obj->__isInitialized() === false) { + $obj->__load(); + } elseif ($obj instanceof GhostObjectInterface && $obj->isProxyInitialized() === false) { $obj->initializeProxy(); } elseif ($obj instanceof PersistentCollectionInterface) { $obj->initialize(); @@ -3072,6 +3086,7 @@ public function initializeObject(object $obj): void public function isUninitializedObject(object $obj): bool { return match (true) { + $obj instanceof InternalProxy => $obj->__isInitialized() === false, $obj instanceof GhostObjectInterface => $obj->isProxyInitialized() === false, $obj instanceof PersistentCollectionInterface => $obj->isInitialized() === false, default => false diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e2c6ab2d8..6a889b8a5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -545,6 +545,11 @@ parameters: count: 1 path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php + - + message: "#^Call to an undefined static method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\Factory\\\\LazyGhostProxyFactory\\:\\:createLazyGhost\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.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\\.$#" count: 1 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c16a73401..3860390bd 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,5 +29,6 @@ + diff --git a/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php b/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php index b24c54c3f..67652d5c2 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/BaseTestCase.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Configuration; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\Query\Filter\Filter; use Doctrine\ODM\MongoDB\UnitOfWork; use Doctrine\Persistence\Mapping\Driver\MappingDriver; @@ -15,6 +16,7 @@ use MongoDB\Driver\Server; use MongoDB\Model\DatabaseInfo; use PHPUnit\Framework\TestCase; +use ProxyManager\Proxy\LazyLoadingInterface; use function array_key_exists; use function array_map; @@ -87,6 +89,7 @@ protected static function getConfiguration(): Configuration $config->setPersistentCollectionNamespace('PersistentCollections'); $config->setDefaultDB(DOCTRINE_MONGODB_DATABASE); $config->setMetadataDriverImpl(static::createMetadataDriverImpl()); + $config->setUseLazyGhostObject((bool) $_ENV['USE_LAZY_GHOST_OBJECTS']); $config->addFilter('testFilter', Filter::class); $config->addFilter('testFilter2', Filter::class); @@ -114,6 +117,11 @@ public static function assertArraySubset(array $subset, array $array, bool $chec } } + public static function isLazyObject(object $document): bool + { + return $document instanceof InternalProxy || $document instanceof LazyLoadingInterface; + } + protected static function createMetadataDriverImpl(): MappingDriver { return AttributeDriver::create(__DIR__ . '/../../../../Documents'); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php index 66cec0181..3994f0a29 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php @@ -7,7 +7,6 @@ use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\Event; use Documents\User; -use ProxyManager\Proxy\LazyLoadingInterface; use function assert; use function get_class; @@ -30,7 +29,7 @@ public function testGetIdentifierValue(): void $userTest = $test->getUser(); self::assertEquals($user->getId(), $userTest->getId()); - self::assertInstanceOf(LazyLoadingInterface::class, $userTest); + self::assertTrue(self::isLazyObject($userTest)); self::assertTrue($this->uow->isUninitializedObject($userTest)); $this->dm->clear(); @@ -42,7 +41,7 @@ public function testGetIdentifierValue(): void $foundUser = $test->getUser(); self::assertEquals($user->getId(), $class->getIdentifierValue($user)); self::assertEquals($user->getId(), $class->getFieldValue($foundUser, 'id')); - self::assertInstanceOf(LazyLoadingInterface::class, $foundUser); + self::assertTrue(self::isLazyObject($foundUser)); self::assertTrue($this->uow->isUninitializedObject($foundUser)); self::assertEquals('jwage', $foundUser->getUsername()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php index 00468ea54..8017b89f3 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php @@ -33,7 +33,6 @@ use InvalidArgumentException; use MongoDB\Driver\ReadPreference; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; -use ProxyManager\Proxy\GhostObjectInterface; use function assert; use function func_get_args; @@ -95,7 +94,7 @@ public function testPrimeReferencesWithDBRefObjects(): void ->field('groups')->prime(true); foreach ($qb->getQuery() as $user) { - self::assertInstanceOf(GhostObjectInterface::class, $user->getAccount()); + self::assertTrue(self::isLazyObject($user->getAccount())); self::assertFalse($this->uow->isUninitializedObject($user->getAccount())); self::assertCount(2, $user->getGroups()); @@ -104,7 +103,7 @@ public function testPrimeReferencesWithDBRefObjects(): void * initialized, they will not be hydrated as proxy objects. */ foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(GhostObjectInterface::class, $group); + self::assertFalse(self::isLazyObject($group)); self::assertInstanceOf(Group::class, $group); } } @@ -133,13 +132,13 @@ public function testPrimeReferencesWithSimpleReferences(): void ->field('users')->prime(true); foreach ($qb->getQuery() as $simpleUser) { - self::assertInstanceOf(GhostObjectInterface::class, $simpleUser->getUser()); + self::assertTrue(self::isLazyObject($simpleUser->getUser())); self::assertFalse($this->uow->isUninitializedObject($simpleUser->getUser())); self::assertCount(2, $simpleUser->getUsers()); foreach ($simpleUser->getUsers() as $user) { - self::assertNotInstanceOf(GhostObjectInterface::class, $user); + self::assertFalse(self::isLazyObject($user)); self::assertInstanceOf(User::class, $user); } } @@ -188,20 +187,20 @@ public function testPrimeReferencesNestedInNamedEmbeddedReference(): void ->field('embeddedDocs.referencedDocs')->prime(true); foreach ($qb->getQuery() as $root) { - self::assertNotInstanceOf(GhostObjectInterface::class, $root->embeddedDoc); + self::assertFalse(self::isLazyObject($root->embeddedDoc)); self::assertInstanceOf(EmbeddedWhichReferences::class, $root->embeddedDoc); self::assertCount(2, $root->embeddedDocs); foreach ($root->embeddedDocs as $embeddedDoc) { - self::assertNotInstanceOf(GhostObjectInterface::class, $embeddedDoc); + self::assertFalse(self::isLazyObject($embeddedDoc)); self::assertInstanceOf(EmbeddedWhichReferences::class, $embeddedDoc); - self::assertInstanceOf(GhostObjectInterface::class, $embeddedDoc->referencedDoc); + self::assertTrue(self::isLazyObject($embeddedDoc->referencedDoc)); self::assertFalse($this->uow->isUninitializedObject($embeddedDoc->referencedDoc)); self::assertCount(2, $embeddedDoc->referencedDocs); foreach ($embeddedDoc->referencedDocs as $referencedDoc) { - self::assertNotInstanceOf(GhostObjectInterface::class, $referencedDoc); + self::assertFalse(self::isLazyObject($referencedDoc)); self::assertInstanceOf(Reference::class, $referencedDoc); } } @@ -252,37 +251,37 @@ public function testPrimeReferencesWithDifferentStoreAsReferences(): void assert($referenceUser instanceof ReferenceUser); $user = $referenceUser->getUser(); self::assertInstanceOf(User::class, $user); - self::assertInstanceOf(GhostObjectInterface::class, $user); + self::assertTrue(self::isLazyObject($user)); self::assertFalse($this->uow->isUninitializedObject($user)); self::assertCount(1, $referenceUser->getUsers()); foreach ($referenceUser->getUsers() as $user) { - self::assertNotInstanceOf(GhostObjectInterface::class, $user); + self::assertFalse(self::isLazyObject($user)); self::assertInstanceOf(User::class, $user); } $parentUser = $referenceUser->getParentUser(); - self::assertInstanceOf(GhostObjectInterface::class, $parentUser); + self::assertTrue(self::isLazyObject($parentUser)); self::assertInstanceOf(User::class, $parentUser); self::assertFalse($this->uow->isUninitializedObject($parentUser)); self::assertCount(1, $referenceUser->getParentUsers()); foreach ($referenceUser->getParentUsers() as $user) { - self::assertNotInstanceOf(GhostObjectInterface::class, $user); + self::assertFalse(self::isLazyObject($user)); self::assertInstanceOf(User::class, $user); } $otherUser = $referenceUser->getOtherUser(); self::assertInstanceOf(User::class, $otherUser); - self::assertInstanceOf(GhostObjectInterface::class, $otherUser); + self::assertTrue(self::isLazyObject($otherUser)); self::assertFalse($this->uow->isUninitializedObject($otherUser)); self::assertCount(1, $referenceUser->getOtherUsers()); foreach ($referenceUser->getOtherUsers() as $user) { - self::assertNotInstanceOf(GhostObjectInterface::class, $user); + self::assertFalse(self::isLazyObject($user)); self::assertInstanceOf(User::class, $user); } } @@ -309,10 +308,10 @@ public function testPrimeReferencesWithDiscriminatedReferenceMany(): void foreach ($qb->getQuery() as $user) { $favorites = $user->getFavorites()->toArray(); - self::assertNotInstanceOf(GhostObjectInterface::class, $favorites[0]); + self::assertFalse(self::isLazyObject($favorites[0])); self::assertInstanceOf(Group::class, $favorites[0]); - self::assertNotInstanceOf(GhostObjectInterface::class, $favorites[1]); + self::assertFalse(self::isLazyObject($favorites[1])); self::assertInstanceOf(Project::class, $favorites[1]); } } @@ -331,7 +330,7 @@ public function testPrimeReferencesWithDiscriminatedReferenceOne(): void ->field('server')->prime(true); foreach ($qb->getQuery() as $agent) { - self::assertInstanceOf(GhostObjectInterface::class, $agent->server); + self::assertTrue(self::isLazyObject($agent->server)); self::assertFalse($this->uow->isUninitializedObject($agent->server)); } } @@ -360,7 +359,7 @@ public function testPrimeReferencesIgnoresInitializedProxyObjects(): void self::assertCount(2, $user->getGroups()); foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(GhostObjectInterface::class, $group); + self::assertFalse(self::isLazyObject($group)); self::assertInstanceOf(Group::class, $group); } } @@ -440,7 +439,7 @@ public function testPrimeReferencesInFindAndModifyResult(): void self::assertCount(1, $user->getGroups()); foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(GhostObjectInterface::class, $group); + self::assertFalse(self::isLazyObject($group)); self::assertInstanceOf(Group::class, $group); } } @@ -472,7 +471,7 @@ public function testPrimeEmbeddedReferenceOneLevelDeep(): void $phonenumber = $phonenumbers->current(); - self::assertNotInstanceOf(GhostObjectInterface::class, $phonenumber); + self::assertFalse(self::isLazyObject($phonenumber)); self::assertInstanceOf(Phonenumber::class, $phonenumber); } @@ -523,7 +522,7 @@ public function testPrimeEmbeddedReferenceTwoLevelsDeep(): void $currency = $money->getCurrency(); - self::assertInstanceOf(GhostObjectInterface::class, $currency); + self::assertTrue(self::isLazyObject($currency)); self::assertInstanceOf(Currency::class, $currency); self::assertFalse($this->uow->isUninitializedObject($currency)); } @@ -551,7 +550,7 @@ public function testPrimeReferencesInReferenceMany(): void self::assertInstanceOf(BlogPost::class, $post); $comment = $post->comments->first(); - self::assertInstanceOf(GhostObjectInterface::class, $comment->author); + self::assertTrue(self::isLazyObject($comment->author)); self::assertFalse($this->uow->isUninitializedObject($comment->author)); } @@ -578,7 +577,7 @@ public function testPrimeReferencesInReferenceManyWithRepositoryMethodEager(): v self::assertInstanceOf(BlogPost::class, $post); $comment = $post->repoCommentsWithPrimer->first(); - self::assertInstanceOf(GhostObjectInterface::class, $comment->author); + self::assertTrue(self::isLazyObject($comment->author)); self::assertFalse($this->uow->isUninitializedObject($comment->author)); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php index 10de47590..88cc979f0 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php @@ -22,8 +22,6 @@ use Documents\User; use MongoDB\BSON\Binary; use MongoDB\BSON\ObjectId; -use ProxyManager\Proxy\GhostObjectInterface; -use ProxyManager\Proxy\LazyLoadingInterface; use function assert; @@ -82,7 +80,7 @@ public function testLazyLoadReference(): void assert($profile instanceof Profile); self::assertInstanceOf(Profile::class, $profile); - self::assertInstanceOf(GhostObjectInterface::class, $profile); + self::assertTrue(self::isLazyObject($profile)); $profile->getFirstName(); @@ -104,7 +102,7 @@ public function testLazyLoadedWithNotifyPropertyChanged(): void $user = $this->dm->find($user::class, $user->getId()); $profile = $user->getProfileNotify(); - self::assertInstanceOf(GhostObjectInterface::class, $profile); + self::assertTrue(self::isLazyObject($profile)); self::assertTrue($this->uow->isUninitializedObject($profile)); $user->getProfileNotify()->setLastName('Malarz'); @@ -396,13 +394,13 @@ public function testDocumentNotFoundExceptionWithArrayId(): void ); $test = $this->dm->find($test::class, $test->id); - self::assertInstanceOf(LazyLoadingInterface::class, $test->referenceOne); + self::assertTrue(self::isLazyObject($test->referenceOne)); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Doctrine\ODM\MongoDB\Tests\Functional\DocumentWithArrayId" document with identifier ' . '{"identifier":2} could not be found.', ); - $test->referenceOne->initializeProxy(); + $this->uow->initializeObject($test->referenceOne); } public function testDocumentNotFoundExceptionWithObjectId(): void @@ -429,12 +427,12 @@ public function testDocumentNotFoundExceptionWithObjectId(): void $user = $this->dm->find($user::class, $user->getId()); $profile = $user->getProfile(); - self::assertInstanceOf(LazyLoadingInterface::class, $profile); + self::assertTrue(self::isLazyObject($profile)); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Documents\Profile" document with identifier "abcdefabcdefabcdefabcdef" could not be found.', ); - $profile->initializeProxy(); + $this->uow->initializeObject($profile); } public function testDocumentNotFoundExceptionWithMongoBinDataId(): void @@ -460,13 +458,13 @@ public function testDocumentNotFoundExceptionWithMongoBinDataId(): void ); $test = $this->dm->find($test::class, $test->id); - self::assertInstanceOf(LazyLoadingInterface::class, $test->referenceOne); + self::assertTrue(self::isLazyObject($test->referenceOne)); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Doctrine\ODM\MongoDB\Tests\Functional\DocumentWithMongoBinDataId" document with identifier ' . '"testbindata" could not be found.', ); - $test->referenceOne->initializeProxy(); + $this->uow->initializeObject($test->referenceOne); } public function testDocumentNotFoundEvent(): void @@ -502,8 +500,8 @@ public function testDocumentNotFoundEvent(): void $this->dm->getEventManager()->addEventListener(Events::documentNotFound, new DocumentNotFoundListener($closure)); - self::assertInstanceOf(LazyLoadingInterface::class, $profile); - $profile->initializeProxy(); + self::assertTrue(self::isLazyObject($profile)); + $this->uow->initializeObject($profile); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php index 4d69bff53..c164321f5 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php @@ -8,7 +8,6 @@ use Documents\SimpleReferenceUser; use Documents\User; use MongoDB\BSON\ObjectId; -use ProxyManager\Proxy\GhostObjectInterface; use stdClass; use function assert; @@ -84,10 +83,9 @@ public function testProxy(): void self::assertNotNull($test); $user = $test->getUser(); - assert($user instanceof User && $user instanceof GhostObjectInterface); self::assertNotNull($user); self::assertInstanceOf(User::class, $user); - self::assertInstanceOf(GhostObjectInterface::class, $user); + self::assertTrue(self::isLazyObject($user)); self::assertTrue($this->uow->isUninitializedObject($user)); self::assertEquals('jwage', $user->getUsername()); self::assertFalse($this->uow->isUninitializedObject($user)); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php index 6ff634841..bd4406891 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php @@ -8,7 +8,6 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; -use ProxyManager\Proxy\GhostObjectInterface; class GH520Test extends BaseTestCase { @@ -29,7 +28,7 @@ public function testPrimeWithGetSingleResult(): void $document = $query->getSingleResult(); self::assertInstanceOf(GH520Document::class, $document); - self::assertInstanceOf(GhostObjectInterface::class, $document->ref); + self::assertTrue(self::isLazyObject($document->ref)); self::assertFalse($this->uow->isUninitializedObject($document->ref)); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php index 0248d9fd8..b468dd2a8 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php @@ -9,7 +9,6 @@ use Doctrine\ODM\MongoDB\DocumentNotFoundException; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; -use ProxyManager\Proxy\GhostObjectInterface; use function iterator_to_array; @@ -58,16 +57,16 @@ public function testReferenceManyOwningSidePreparesFilterCriteria(): void */ self::assertCount(2, $user1following); - self::assertInstanceOf(GhostObjectInterface::class, $user1following[0]); + self::assertTrue(self::isLazyObject($user1following[0])); self::assertFalse($this->uow->isUninitializedObject($user1following[0])); self::assertEquals($user2->getId(), $user1following[0]->getId()); - self::assertInstanceOf(GhostObjectInterface::class, $user1following[1]); + self::assertTrue(self::isLazyObject($user1following[1])); self::assertTrue($this->uow->isUninitializedObject($user1following[1])); self::assertEquals($user3->getId(), $user1following[1]->getId()); $this->expectException(DocumentNotFoundException::class); - $user1following[1]->initializeProxy(); + $this->uow->initializeObject($user1following[1]); } public function testReferenceManyInverseSidePreparesFilterCriteria(): void diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php index ce38f44ed..f1e3cfe35 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php @@ -9,7 +9,6 @@ use Doctrine\ODM\MongoDB\DocumentNotFoundException; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; -use ProxyManager\Proxy\GhostObjectInterface; use function iterator_to_array; @@ -49,16 +48,16 @@ public function testReferenceManyOwningSidePreparesFilterCriteriaForDifferentCla */ self::assertCount(2, $user1likes); - self::assertInstanceOf(GhostObjectInterface::class, $user1likes[0]); + self::assertTrue(self::isLazyObject($user1likes[0])); self::assertFalse($this->uow->isUninitializedObject($user1likes[0])); self::assertEquals($thing1->getId(), $user1likes[0]->getId()); - self::assertInstanceOf(GhostObjectInterface::class, $user1likes[1]); + self::assertTrue(self::isLazyObject($user1likes[1])); self::assertTrue($this->uow->isUninitializedObject($user1likes[1])); self::assertEquals($thing2->getId(), $user1likes[1]->getId()); $this->expectException(DocumentNotFoundException::class); - $user1likes[1]->initializeProxy(); + $this->uow->initializeObject($user1likes[1]); } public function testReferenceManyInverseSidePreparesFilterCriteriaForDifferentClass(): void diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php index 0b4e912e8..3895f4325 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php @@ -13,7 +13,6 @@ use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use MongoDB\BSON\Binary; use PHPUnit\Framework\Attributes\DataProvider; -use ProxyManager\Proxy\GhostObjectInterface; class GH852Test extends BaseTestCase { @@ -49,7 +48,7 @@ public function testA(Closure $idGenerator): void self::assertEquals($idGenerator('parent'), $parent->id); self::assertEquals('parent', $parent->name); - self::assertInstanceOf(GhostObjectInterface::class, $parent->refOne); + self::assertTrue(self::isLazyObject($parent->refOne)); self::assertInstanceOf(GH852Document::class, $parent->refOne); self::assertTrue($this->uow->isUninitializedObject($parent->refOne)); self::assertEquals($idGenerator('childA'), $parent->refOne->id); @@ -61,13 +60,13 @@ public function testA(Closure $idGenerator): void /* These proxies will be initialized when we first access the collection * by DocumentPersister::loadReferenceManyCollectionOwningSide(). */ - self::assertInstanceOf(GhostObjectInterface::class, $parent->refMany[0]); + self::assertTrue(self::isLazyObject($parent->refMany[0])); self::assertInstanceOf(GH852Document::class, $parent->refMany[0]); self::assertFalse($this->uow->isUninitializedObject($parent->refMany[0])); self::assertEquals($idGenerator('childB'), $parent->refMany[0]->id); self::assertEquals('childB', $parent->refMany[0]->name); - self::assertInstanceOf(GhostObjectInterface::class, $parent->refMany[1]); + self::assertTrue(self::isLazyObject($parent->refMany[1])); self::assertInstanceOf(GH852Document::class, $parent->refMany[1]); self::assertFalse($this->uow->isUninitializedObject($parent->refMany[1])); self::assertEquals($idGenerator('childC'), $parent->refMany[1]->id); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php index b2eb71704..fd25150b8 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php @@ -8,7 +8,6 @@ use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; -use ProxyManager\Proxy\GhostObjectInterface; class GH936Test extends BaseTestCase { @@ -27,7 +26,7 @@ public function testRemoveCascadesThroughProxyDocuments(): void $foo = $this->dm->find(GH936Document::class, $foo->id); - self::assertInstanceOf(GhostObjectInterface::class, $foo->ref); + self::assertTrue(self::isLazyObject($foo->ref)); $this->dm->remove($foo); $this->dm->flush(); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php index 608fc45a4..63ab53512 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php @@ -10,7 +10,6 @@ use Documents\CmsUser; use Documents\UserName; use Documents\ViewReference; -use ProxyManager\Proxy\GhostObjectInterface; use function assert; @@ -112,7 +111,7 @@ public function testViewReferences(): void $viewReference = $this->dm->find(ViewReference::class, $alcaeus->getId()); self::assertInstanceOf(ViewReference::class, $viewReference); - self::assertInstanceOf(GhostObjectInterface::class, $viewReference->getReferenceOneView()); + self::assertTrue(self::isLazyObject($viewReference->getReferenceOneView())); self::assertSame($malarzm->getId(), $viewReference->getReferenceOneView()->getId()); // No proxies for inverse referenceOne @@ -120,7 +119,7 @@ public function testViewReferences(): void self::assertSame($alcaeus->getId(), $viewReference->getReferenceOneViewMappedBy()->getId()); self::assertCount(1, $viewReference->getReferenceManyView()); - self::assertInstanceOf(GhostObjectInterface::class, $viewReference->getReferenceManyView()[0]); + self::assertTrue(self::isLazyObject($viewReference->getReferenceManyView()[0])); self::assertSame($malarzm->getId(), $viewReference->getReferenceManyView()[0]->getId()); // No proxies for inverse referenceMany diff --git a/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php b/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php index 30191a6da..0982d629b 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php @@ -11,7 +11,6 @@ use Doctrine\ODM\MongoDB\PersistentCollection; use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface; use Doctrine\ODM\MongoDB\Query\Query; -use ProxyManager\Proxy\GhostObjectInterface; class HydratorTest extends BaseTestCase { @@ -41,10 +40,10 @@ public function testHydrator(): void self::assertEquals('jon', $user->name); self::assertInstanceOf(DateTime::class, $user->birthdate); self::assertInstanceOf(HydrationClosureReferenceOne::class, $user->referenceOne); - self::assertInstanceOf(GhostObjectInterface::class, $user->referenceOne); + self::assertTrue(self::isLazyObject($user->referenceOne)); self::assertInstanceOf(PersistentCollection::class, $user->referenceMany); - self::assertInstanceOf(GhostObjectInterface::class, $user->referenceMany[0]); - self::assertInstanceOf(GhostObjectInterface::class, $user->referenceMany[1]); + self::assertTrue(self::isLazyObject($user->referenceMany[0])); + self::assertTrue(self::isLazyObject($user->referenceMany[1])); self::assertInstanceOf(HydrationClosureEmbedOne::class, $user->embedOne); self::assertInstanceOf(PersistentCollection::class, $user->embedMany); self::assertEquals('jon', $user->embedOne->name); @@ -54,7 +53,7 @@ public function testHydrator(): void public function testHydrateProxyWithMissingAssociations(): void { $user = $this->dm->getReference(HydrationClosureUser::class, 1); - self::assertInstanceOf(GhostObjectInterface::class, $user); + self::assertTrue(self::isLazyObject($user)); $this->dm->getHydratorFactory()->hydrate($user, [ '_id' => 1, diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php index c8f74769d..cb69ce98c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php @@ -39,7 +39,6 @@ use InvalidArgumentException; use MongoDB\BSON\Document; use PHPUnit\Framework\Attributes\DataProvider; -use ProxyManager\Proxy\GhostObjectInterface; use ReflectionClass; use ReflectionException; use stdClass; @@ -496,7 +495,7 @@ public function testGetFieldValueInitializesProxy(): void $metadata = $this->dm->getClassMetadata(Album::class); self::assertEquals($document->getName(), $metadata->getFieldValue($proxy, 'name')); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertTrue(self::isLazyObject($proxy)); self::assertFalse($this->uow->isUninitializedObject($proxy)); } @@ -511,7 +510,7 @@ public function testGetFieldValueOfIdentifierDoesNotInitializeProxy(): void $metadata = $this->dm->getClassMetadata(Album::class); self::assertEquals($document->getId(), $metadata->getFieldValue($proxy, 'id')); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertTrue(self::isLazyObject($proxy)); self::assertTrue($this->uow->isUninitializedObject($proxy)); } @@ -533,7 +532,7 @@ public function testSetFieldValueWithProxy(): void $this->dm->clear(); $proxy = $this->dm->getReference(Album::class, $document->getId()); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertTrue(self::isLazyObject($proxy)); $metadata = $this->dm->getClassMetadata(Album::class); $metadata->setFieldValue($proxy, 'name', 'nevermind'); @@ -542,7 +541,7 @@ public function testSetFieldValueWithProxy(): void $this->dm->clear(); $proxy = $this->dm->getReference(Album::class, $document->getId()); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertTrue(self::isLazyObject($proxy)); self::assertInstanceOf(Album::class, $proxy); self::assertEquals('nevermind', $proxy->getName()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/ProxyFactoryTest.php similarity index 81% rename from tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php rename to tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/ProxyFactoryTest.php index 5a1db88dc..16373cfe4 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/ProxyFactoryTest.php @@ -9,6 +9,7 @@ use Doctrine\ODM\MongoDB\Event\DocumentNotFoundEventArgs; use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\LockException; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\Cart; use Documents\DocumentWithUnmappedProperties; @@ -18,7 +19,7 @@ use PHPUnit\Framework\MockObject\MockObject; use ProxyManager\Proxy\GhostObjectInterface; -class StaticProxyFactoryTest extends BaseTestCase +class ProxyFactoryTest extends BaseTestCase { /** @var Client|MockObject */ private Client $client; @@ -45,7 +46,7 @@ public function testProxyInitializeWithException(): void $uow = $this->dm->getUnitOfWork(); $proxy = $this->dm->getReference(Cart::class, '123'); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertTrue(self::isLazyObject($proxy)); $closure = static function (DocumentNotFoundEventArgs $eventArgs) { self::fail('DocumentNotFoundListener should not be called'); @@ -53,7 +54,7 @@ public function testProxyInitializeWithException(): void $this->dm->getEventManager()->addEventListener(Events::documentNotFound, new DocumentNotFoundListener($closure)); try { - $proxy->initializeProxy(); + $this->uow->initializeObject($proxy); self::fail('An exception should have been thrown'); } catch (LockException $exception) { self::assertInstanceOf(LockException::class, $exception); @@ -61,7 +62,7 @@ public function testProxyInitializeWithException(): void $uow->computeChangeSets(); - self::assertFalse($proxy->isProxyInitialized(), 'Proxy should not be initialized'); + self::assertTrue($this->uow->isUninitializedObject($proxy), 'Proxy should not be initialized'); } public function tearDown(): void @@ -81,10 +82,14 @@ private function createMockedDocumentManager(): DocumentManager public function testCreateProxyForDocumentWithUnmappedProperties(): void { $proxy = $this->dm->getReference(DocumentWithUnmappedProperties::class, '123'); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertTrue(self::isLazyObject($proxy)); - // Disable initialiser so we can access properties without initialising the object - $proxy->setProxyInitializer(null); + // Disable initializer so we can access properties without initialising the object + if ($proxy instanceof InternalProxy) { + $proxy->__setInitialized(true); + } elseif ($proxy instanceof GhostObjectInterface) { + $proxy->setProxyInitializer(null); + } self::assertSame('bar', $proxy->foo); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php index fead82a86..4eabffe4e 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php @@ -28,7 +28,6 @@ use MongoDB\Driver\WriteConcern; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; -use ProxyManager\Proxy\GhostObjectInterface; use ReflectionProperty; use Throwable; @@ -489,7 +488,7 @@ public function testRecomputeChangesetForUninitializedProxyDoesNotCreateChangese $user = $this->dm->find(ForumUser::class, $id); self::assertInstanceOf(ForumUser::class, $user); - self::assertInstanceOf(GhostObjectInterface::class, $user->getAvatar()); + self::assertTrue(self::isLazyObject($user->getAvatar())); $classMetadata = $this->dm->getClassMetadata(ForumAvatar::class); From d1dbffee9569461b391233b0dba4d6853db74d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 23 Jan 2025 08:50:33 +0100 Subject: [PATCH 27/28] Keep proxy-manager by default (#2720) --- .github/workflows/continuous-integration.yml | 7 ------- composer.json | 2 +- lib/Doctrine/ODM/MongoDB/Configuration.php | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 62f56d0e9..a78a7e66a 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -124,13 +124,6 @@ jobs: composer require --no-update symfony/var-dumper:^7@dev composer require --no-update --dev symfony/cache:^7@dev - - name: "Remove proxy-manager-lts" - if: "${{ matrix.proxy != 'proxy-manager' }}" - run: | - # proxy-manager-lts is not installed by default and must not be used - # unless explicitly requested - composer remove --no-update --dev friendsofphp/proxy-manager-lts - - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v3" with: diff --git a/composer.json b/composer.json index 2ae0368ac..6e98dadf9 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "doctrine/event-manager": "^1.0 || ^2.0", "doctrine/instantiator": "^1.1 || ^2", "doctrine/persistence": "^3.2 || ^4", + "friendsofphp/proxy-manager-lts": "^1.0", "jean85/pretty-package-versions": "^1.3.0 || ^2.0.1", "mongodb/mongodb": "^1.17.0", "psr/cache": "^1.0 || ^2.0 || ^3.0", @@ -41,7 +42,6 @@ "doctrine/annotations": "^1.12 || ^2.0", "doctrine/coding-standard": "^12.0", "doctrine/orm": "^3.2", - "friendsofphp/proxy-manager-lts": "^1.0", "jmikola/geojson": "^1.0", "phpbench/phpbench": "^1.0.0", "phpstan/phpstan": "~1.10.67", diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index ca61fcbfc..04ae28d7a 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -133,7 +133,7 @@ class Configuration private bool $useTransactionalFlush = false; - private bool $useLazyGhostObject = true; + private bool $useLazyGhostObject = false; /** * Adds a namespace under a certain alias. From a00f135370ca3e99832a09397e7abb7bf12be681 Mon Sep 17 00:00:00 2001 From: Claudio Zizza <859964+SenseException@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:34:31 +0100 Subject: [PATCH 28/28] Create website schema validation workflow (#2721) --- .github/workflows/website-schema.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/website-schema.yml diff --git a/.github/workflows/website-schema.yml b/.github/workflows/website-schema.yml new file mode 100644 index 000000000..e251588e5 --- /dev/null +++ b/.github/workflows/website-schema.yml @@ -0,0 +1,21 @@ + +name: "Website config validation" + +on: + pull_request: + branches: + - "*.x" + paths: + - ".doctrine-project.json" + - ".github/workflows/website-schema.yml" + push: + branches: + - "*.x" + paths: + - ".doctrine-project.json" + - ".github/workflows/website-schema.yml" + +jobs: + json-validate: + name: "Validate JSON schema" + uses: "doctrine/.github/.github/workflows/website-schema.yml@7.1.0"