Skip to content

Commit

Permalink
Remove CursorInterface::getId compatibility layer
Browse files Browse the repository at this point in the history
  • Loading branch information
alcaeus committed Sep 19, 2024
1 parent 00fa5e4 commit bb4bb56
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 142 deletions.
37 changes: 6 additions & 31 deletions src/ChangeStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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();

Expand All @@ -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();
}

/**
Expand Down Expand Up @@ -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;
}

Expand Down
26 changes: 8 additions & 18 deletions src/Model/ChangeStreamIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

namespace MongoDB\Model;

use Iterator;
use IteratorIterator;
use MongoDB\BSON\Document;
use MongoDB\BSON\Serializable;
Expand Down Expand Up @@ -49,7 +48,7 @@
*
* @internal
* @template TValue of array|object
* @template-extends IteratorIterator<int, TValue, CursorInterface<int, TValue>&Iterator<int, TValue>>
* @template-extends IteratorIterator<int, TValue, CursorInterface<TValue>>
*/
final class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber
{
Expand Down Expand Up @@ -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<int, TValue>&Iterator<int, TValue>
* @see https://github.com/vimeo/psalm/pull/11100.
*
* @return CursorInterface<TValue>
*/
final public function getInnerIterator(): Iterator
public function getInnerIterator(): CursorInterface
{
$cursor = parent::getInnerIterator();
assert($cursor instanceof CursorInterface);
assert($cursor instanceof Iterator);

return $cursor;
}
Expand Down Expand Up @@ -173,18 +171,10 @@ public function valid(): bool

/**
* @internal
* @psalm-param CursorInterface<int, TValue>&Iterator<int, TValue> $cursor
* @psalm-param CursorInterface<TValue> $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);
}
Expand Down
35 changes: 6 additions & 29 deletions src/Model/CodecCursor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, TValue>
* @template-implements Iterator<int, TValue>
* @template-implements CursorInterface<TValue>
*/
class CodecCursor implements CursorInterface, Iterator
class CodecCursor implements CursorInterface
{
private const TYPEMAP = ['root' => 'bson'];

Expand All @@ -64,33 +58,16 @@ public function current(): ?object
* @param DocumentCodec<NativeClass> $codec
* @return self<NativeClass>
*/
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
Expand Down Expand Up @@ -138,7 +115,7 @@ public function valid(): bool
}

/** @param DocumentCodec<TValue> $codec */
private function __construct(private Cursor $cursor, private DocumentCodec $codec)
private function __construct(private CursorInterface $cursor, private DocumentCodec $codec)
{
}
}
65 changes: 1 addition & 64 deletions tests/Model/CodecCursorFunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,69 +29,13 @@ 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());
$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(true);
} finally {
restore_error_handler();
}

self::assertInstanceOf(Int64::class, $cursorId);
self::assertCount(0, $deprecations);
self::assertInstanceOf(Int64::class, $codecCursor->getId());
}
}

0 comments on commit bb4bb56

Please sign in to comment.