diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 7e99f32d6..d17c6e9cf 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -5,11 +5,7 @@ name]]> - - - - @@ -23,6 +19,9 @@ + + + @@ -184,9 +183,6 @@ - - - @@ -339,6 +335,15 @@ + + + + + ]]> + + + ]]> + cursor->nextBatch]]> @@ -354,11 +359,6 @@ current()]]> - - - - - @@ -713,18 +713,6 @@ value ?? null) : null]]> - - - - - - - - - - - - diff --git a/psalm.xml.dist b/psalm.xml.dist index 83d825d51..d9640583b 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -21,6 +21,8 @@ + + diff --git a/src/ChangeStream.php b/src/ChangeStream.php index 55f25b031..f57bcc873 100644 --- a/src/ChangeStream.php +++ b/src/ChangeStream.php @@ -21,7 +21,6 @@ use MongoDB\BSON\Document; use MongoDB\BSON\Int64; use MongoDB\Codec\DocumentCodec; -use MongoDB\Driver\CursorId; use MongoDB\Driver\Exception\ConnectionException; use MongoDB\Driver\Exception\RuntimeException; use MongoDB\Driver\Exception\ServerException; @@ -33,10 +32,6 @@ use function assert; use function call_user_func; use function in_array; -use function sprintf; -use function trigger_error; - -use const E_USER_DEPRECATED; /** * Iterator for a change stream. @@ -88,12 +83,8 @@ class ChangeStream implements Iterator */ private bool $hasAdvanced = false; - /** - * @see https://php.net/iterator.current - * @return array|object|null - */ - #[ReturnTypeWillChange] - public function current() + /** @see https://php.net/iterator.current */ + public function current(): array|object|null { $value = $this->iterator->current(); @@ -106,26 +97,9 @@ public function current() return $this->codec->decode($value); } - /** - * @return CursorId|Int64 - * @psalm-return ($asInt64 is true ? Int64 : CursorId) - */ - #[ReturnTypeWillChange] - public function getCursorId(bool $asInt64 = false) + public function getCursorId(): Int64 { - if (! $asInt64) { - @trigger_error( - sprintf( - 'The method "%s" will no longer return a "%s" instance in the future. Pass "true" as argument to change to the new behavior and receive a "%s" instance instead.', - __METHOD__, - CursorId::class, - Int64::class, - ), - E_USER_DEPRECATED, - ); - } - - return $this->iterator->getInnerIterator()->getId($asInt64); + return $this->iterator->getInnerIterator()->getId(); } /** @@ -255,7 +229,8 @@ private function onIteration(bool $incrementKey): void * have been received in the last response. Therefore, we can unset the * resumeCallable. This will free any reference to Watch as well as the * only reference to any implicit session created therein. */ - if ((string) $this->getCursorId(true) === '0') { + // Use a type-unsafe comparison to compare with Int64 instances + if ($this->getCursorId() == 0) { $this->resumeCallable = null; } diff --git a/src/Model/ChangeStreamIterator.php b/src/Model/ChangeStreamIterator.php index 721c59734..88f47f304 100644 --- a/src/Model/ChangeStreamIterator.php +++ b/src/Model/ChangeStreamIterator.php @@ -17,7 +17,6 @@ namespace MongoDB\Model; -use Iterator; use IteratorIterator; use MongoDB\BSON\Document; use MongoDB\BSON\Serializable; @@ -49,7 +48,7 @@ * * @internal * @template TValue of array|object - * @template-extends IteratorIterator&Iterator> + * @template-extends IteratorIterator> */ final class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber { @@ -77,18 +76,17 @@ public function current() } /** - * Necessary to let psalm know that we're always expecting a cursor as inner - * iterator. This could be side-stepped due to the class not being final, - * but it's very much an invalid use-case. This method can be dropped in 2.0 - * once the class is final. + * This method is necessary as psalm does not properly set the return type + * of IteratorIterator::getInnerIterator to the templated iterator * - * @return CursorInterface&Iterator + * @see https://github.com/vimeo/psalm/pull/11100. + * + * @return CursorInterface */ - final public function getInnerIterator(): Iterator + public function getInnerIterator(): CursorInterface { $cursor = parent::getInnerIterator(); assert($cursor instanceof CursorInterface); - assert($cursor instanceof Iterator); return $cursor; } @@ -173,18 +171,10 @@ public function valid(): bool /** * @internal - * @psalm-param CursorInterface&Iterator $cursor + * @psalm-param CursorInterface $cursor */ public function __construct(CursorInterface $cursor, int $firstBatchSize, array|object|null $initialResumeToken, private ?object $postBatchResumeToken = null) { - if (! $cursor instanceof Iterator) { - throw InvalidArgumentException::invalidType( - '$cursor', - $cursor, - CursorInterface::class . '&' . Iterator::class, - ); - } - if (isset($initialResumeToken) && ! is_document($initialResumeToken)) { throw InvalidArgumentException::expectedDocumentType('$initialResumeToken', $initialResumeToken); } diff --git a/src/Model/CodecCursor.php b/src/Model/CodecCursor.php index 642f3c3c1..d7a3776b2 100644 --- a/src/Model/CodecCursor.php +++ b/src/Model/CodecCursor.php @@ -17,30 +17,24 @@ namespace MongoDB\Model; -use Iterator; use MongoDB\BSON\Document; use MongoDB\BSON\Int64; use MongoDB\Codec\DocumentCodec; -use MongoDB\Driver\Cursor; -use MongoDB\Driver\CursorId; use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Server; -use ReturnTypeWillChange; use function assert; use function iterator_to_array; use function sprintf; use function trigger_error; -use const E_USER_DEPRECATED; use const E_USER_WARNING; /** * @template TValue of object - * @template-implements CursorInterface - * @template-implements Iterator + * @template-implements CursorInterface */ -class CodecCursor implements CursorInterface, Iterator +class CodecCursor implements CursorInterface { private const TYPEMAP = ['root' => 'bson']; @@ -64,33 +58,16 @@ public function current(): ?object * @param DocumentCodec $codec * @return self */ - public static function fromCursor(Cursor $cursor, DocumentCodec $codec): self + public static function fromCursor(CursorInterface $cursor, DocumentCodec $codec): self { $cursor->setTypeMap(self::TYPEMAP); return new self($cursor, $codec); } - /** - * @return CursorId|Int64 - * @psalm-return ($asInt64 is true ? Int64 : CursorId) - */ - #[ReturnTypeWillChange] - public function getId(bool $asInt64 = false) + public function getId(): Int64 { - if (! $asInt64) { - @trigger_error( - sprintf( - 'The method "%s" will no longer return a "%s" instance in the future. Pass "true" as argument to change to the new behavior and receive a "%s" instance instead.', - __METHOD__, - CursorId::class, - Int64::class, - ), - E_USER_DEPRECATED, - ); - } - - return $this->cursor->getId($asInt64); + return $this->cursor->getId(); } public function getServer(): Server @@ -138,7 +115,7 @@ public function valid(): bool } /** @param DocumentCodec $codec */ - private function __construct(private Cursor $cursor, private DocumentCodec $codec) + private function __construct(private CursorInterface $cursor, private DocumentCodec $codec) { } } diff --git a/stubs/Driver/Cursor.stub.php b/stubs/Driver/Cursor.stub.php new file mode 100644 index 000000000..477ece475 --- /dev/null +++ b/stubs/Driver/Cursor.stub.php @@ -0,0 +1,41 @@ + + */ +final class Cursor implements CursorInterface +{ + /** + * @return TValue|null + * @psalm-ignore-nullable-return + */ + public function current(): array|object|null + { + } + + public function next(): void + { + } + + /** @psalm-ignore-nullable-return */ + public function key(): ?int + { + } + + public function valid(): bool + { + } + + public function rewind(): void + { + } + + /** @return array */ + public function toArray(): array + { + } +} diff --git a/stubs/Driver/CursorInterface.stub.php b/stubs/Driver/CursorInterface.stub.php new file mode 100644 index 000000000..2ba7eaa76 --- /dev/null +++ b/stubs/Driver/CursorInterface.stub.php @@ -0,0 +1,32 @@ + + */ +interface CursorInterface extends Iterator +{ + /** + * @return TValue|null + * @psalm-ignore-nullable-return + */ + public function current(): array|object|null; + + public function getId(): \MongoDB\BSON\Int64; + + public function getServer(): Server; + + public function isDead(): bool; + + /** @psalm-ignore-nullable-return */ + public function key(): ?int; + + public function setTypeMap(array $typemap): void; + + /** @return array */ + public function toArray(): array; +} diff --git a/tests/Model/CodecCursorFunctionalTest.php b/tests/Model/CodecCursorFunctionalTest.php index 432d71732..0fb2b35c6 100644 --- a/tests/Model/CodecCursorFunctionalTest.php +++ b/tests/Model/CodecCursorFunctionalTest.php @@ -4,16 +4,9 @@ use MongoDB\BSON\Int64; use MongoDB\Codec\DocumentCodec; -use MongoDB\Driver\CursorId; use MongoDB\Model\CodecCursor; use MongoDB\Tests\FunctionalTestCase; -use function restore_error_handler; -use function set_error_handler; - -use const E_DEPRECATED; -use const E_USER_DEPRECATED; - class CodecCursorFunctionalTest extends FunctionalTestCase { public function setUp(): void @@ -36,44 +29,6 @@ public function testSetTypeMap(): void $codecCursor->setTypeMap(['root' => 'array']); } - public function testGetIdReturnTypeWithoutArgument(): void - { - $collection = self::createTestClient()->selectCollection($this->getDatabaseName(), $this->getCollectionName()); - $cursor = $collection->find(); - - $codecCursor = CodecCursor::fromCursor($cursor, $this->createMock(DocumentCodec::class)); - - $deprecations = []; - - try { - $previousErrorHandler = set_error_handler( - function (...$args) use (&$previousErrorHandler, &$deprecations) { - $deprecations[] = $args; - - return true; - }, - E_USER_DEPRECATED | E_DEPRECATED, - ); - - $cursorId = $codecCursor->getId(); - } finally { - restore_error_handler(); - } - - self::assertInstanceOf(CursorId::class, $cursorId); - - // Expect 2 deprecations: 1 from CodecCursor, one from Cursor - self::assertCount(2, $deprecations); - self::assertSame( - 'The method "MongoDB\Model\CodecCursor::getId" will no longer return a "MongoDB\Driver\CursorId" instance in the future. Pass "true" as argument to change to the new behavior and receive a "MongoDB\BSON\Int64" instance instead.', - $deprecations[0][1], - ); - self::assertSame( - 'MongoDB\Driver\Cursor::getId(): The method "MongoDB\Driver\Cursor::getId" will no longer return a "MongoDB\Driver\CursorId" instance in the future. Pass "true" as argument to change to the new behavior and receive a "MongoDB\BSON\Int64" instance instead.', - $deprecations[1][1], - ); - } - public function testGetIdReturnTypeWithArgument(): void { $collection = self::createTestClient()->selectCollection($this->getDatabaseName(), $this->getCollectionName()); @@ -81,24 +36,6 @@ public function testGetIdReturnTypeWithArgument(): void $codecCursor = CodecCursor::fromCursor($cursor, $this->createMock(DocumentCodec::class)); - $deprecations = []; - - try { - $previousErrorHandler = set_error_handler( - function (...$args) use (&$previousErrorHandler, &$deprecations) { - $deprecations[] = $args; - - return true; - }, - E_USER_DEPRECATED | E_DEPRECATED, - ); - - $cursorId = $codecCursor->getId(true); - } finally { - restore_error_handler(); - } - - self::assertInstanceOf(Int64::class, $cursorId); - self::assertCount(0, $deprecations); + self::assertInstanceOf(Int64::class, $codecCursor->getId()); } }