Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Closure to callable #321

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ types. This would only work in PHP 7.2+ which is the first version featuring

You can find a list of major changes to public API below.

### Closure vs callable

While 1.x branch is using `Closure` as param type declaration, 2.x is using `callable`.
If you want to support both branches you can use `Closure|callable` (or no type declaration at all).

```php
class MyCollection implements Collection
{
public function exists(Closure|callable $p): bool
{
// ...
}
}
```

### Doctrine\Common\Collections\Collection

| before | after |
Expand Down
19 changes: 9 additions & 10 deletions lib/Doctrine/Common/Collections/AbstractLazyCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Doctrine\Common\Collections;

use Closure;
use LogicException;
use Traversable;

Expand Down Expand Up @@ -172,49 +171,49 @@ public function next(): mixed
return $this->collection->next();
}

public function exists(Closure $p): bool
public function exists(callable $p): bool
{
$this->initialize();

return $this->collection->exists($p);
}

public function findFirst(Closure $p): mixed
public function findFirst(callable $p): mixed
{
$this->initialize();

return $this->collection->findFirst($p);
}

/**
* @psalm-param Closure(T=, TKey=):bool $p
* @psalm-param callable(T=, TKey=):bool $p
*
* @return Collection<mixed>
* @psalm-return Collection<TKey, T>
*/
public function filter(Closure $p): Collection
public function filter(callable $p): Collection
{
$this->initialize();

return $this->collection->filter($p);
}

public function forAll(Closure $p): bool
public function forAll(callable $p): bool
{
$this->initialize();

return $this->collection->forAll($p);
}

/**
* @psalm-param Closure(T=):U $func
* @psalm-param callable(T=):U $func
*
* @return Collection<mixed>
* @psalm-return Collection<TKey, U>
*
* @psalm-template U
*/
public function map(Closure $func): Collection
public function map(callable $func): Collection
{
$this->initialize();

Expand All @@ -224,7 +223,7 @@ public function map(Closure $func): Collection
/**
* {@inheritDoc}
*/
public function reduce(Closure $func, $initial = null): mixed
public function reduce(callable $func, $initial = null): mixed
{
$this->initialize();

Expand All @@ -234,7 +233,7 @@ public function reduce(Closure $func, $initial = null): mixed
/**
* {@inheritDoc}
*/
public function partition(Closure $p): array
public function partition(callable $p): array
{
$this->initialize();

Expand Down
17 changes: 8 additions & 9 deletions lib/Doctrine/Common/Collections/ArrayCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Doctrine\Common\Collections;

use ArrayIterator;
use Closure;
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
use Stringable;
use Traversable;
Expand Down Expand Up @@ -209,7 +208,7 @@ public function contains($element): bool
return in_array($element, $this->elements, true);
}

public function exists(Closure $p): bool
public function exists(callable $p): bool
{
foreach ($this->elements as $key => $element) {
if ($p($key, $element)) {
Expand Down Expand Up @@ -294,22 +293,22 @@ public function getIterator(): Traversable
/**
* {@inheritDoc}
*
* @psalm-param Closure(T=):U $func
* @psalm-param callable(T=):U $func
*
* @return static
* @psalm-return static<TKey, U>
*
* @psalm-template U
*/
public function map(Closure $func): Collection
public function map(callable $func): Collection
{
return $this->createFrom(array_map($func, $this->elements));
}

/**
* {@inheritDoc}
*/
public function reduce(Closure $func, $initial = null): mixed
public function reduce(callable $func, $initial = null): mixed
{
return array_reduce($this->elements, $func, $initial);
}
Expand All @@ -320,12 +319,12 @@ public function reduce(Closure $func, $initial = null): mixed
* @return static
* @psalm-return static<TKey,T>
*/
public function filter(Closure $p): Collection
public function filter(callable $p): Collection
{
return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH));
}

public function findFirst(Closure $p): mixed
public function findFirst(callable $p): mixed
{
foreach ($this->elements as $key => $element) {
if ($p($key, $element)) {
Expand All @@ -336,7 +335,7 @@ public function findFirst(Closure $p): mixed
return null;
}

public function forAll(Closure $p): bool
public function forAll(callable $p): bool
{
foreach ($this->elements as $key => $element) {
if (! $p($key, $element)) {
Expand All @@ -350,7 +349,7 @@ public function forAll(Closure $p): bool
/**
* {@inheritDoc}
*/
public function partition(Closure $p): array
public function partition(callable $p): array
{
$matches = $noMatches = [];

Expand Down
39 changes: 19 additions & 20 deletions lib/Doctrine/Common/Collections/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Doctrine\Common\Collections;

use ArrayAccess;
use Closure;
use Countable;
use IteratorAggregate;

Expand Down Expand Up @@ -182,87 +181,87 @@ public function next(): mixed;
/**
* Tests for the existence of an element that satisfies the given predicate.
*
* @param Closure $p The predicate.
* @psalm-param Closure(TKey=, T=):bool $p
* @param callable $p The predicate.
* @psalm-param callable(TKey=, T=):bool $p
*
* @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
*/
public function exists(Closure $p): bool;
public function exists(callable $p): bool;

/**
* Returns the first element of this collection that satisfies the predicate p.
*
* @param Closure $p The predicate.
* @psalm-param Closure(TKey=, T=):bool $p
* @param callable $p The predicate.
* @psalm-param callable(TKey=, T=):bool $p
*
* @return mixed The first element respecting the predicate,
* null if no element respects the predicate.
* @psalm-return T|null
*/
public function findFirst(Closure $p): mixed;
public function findFirst(callable $p): mixed;

/**
* Returns all the elements of this collection that satisfy the predicate p.
* The order of the elements is preserved.
*
* @param Closure $p The predicate used for filtering.
* @psalm-param Closure(T=, TKey=):bool $p
* @param callable $p The predicate used for filtering.
* @psalm-param callable(T=, TKey=):bool $p
*
* @return Collection<mixed> A collection with the results of the filter operation.
* @psalm-return Collection<TKey, T>
*/
public function filter(Closure $p): self;
public function filter(callable $p): self;

/**
* Tests whether the given predicate p holds for all elements of this collection.
*
* @param Closure $p The predicate.
* @psalm-param Closure(TKey=, T=):bool $p
* @param callable $p The predicate.
* @psalm-param callable(TKey=, T=):bool $p
*
* @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
*/
public function forAll(Closure $p): bool;
public function forAll(callable $p): bool;

/**
* Applies the given function to each element in the collection and returns
* a new collection with the elements returned by the function.
*
* @psalm-param Closure(T=):U $func
* @psalm-param callable(T=):U $func
*
* @return Collection<mixed>
* @psalm-return Collection<TKey, U>
*
* @psalm-template U
*/
public function map(Closure $func): self;
public function map(callable $func): self;

/**
* Applies iteratively the given function to each element in the collection,
* so as to reduce the collection to a single value.
*
* @psalm-param Closure(TReturn|TInitial|null, T):(TInitial|TReturn) $func
* @psalm-param callable(TReturn|TInitial|null, T):(TInitial|TReturn) $func
* @psalm-param TInitial|null $initial
*
* @psalm-return TReturn|TInitial|null
*
* @psalm-template TReturn
* @psalm-template TInitial
*/
public function reduce(Closure $func, mixed $initial = null): mixed;
public function reduce(callable $func, mixed $initial = null): mixed;

/**
* Partitions this collection in two collections according to a predicate.
* Keys are preserved in the resulting collections.
*
* @param Closure $p The predicate on which to partition.
* @psalm-param Closure(TKey=, T=):bool $p
* @param callable $p The predicate on which to partition.
* @psalm-param callable(TKey=, T=):bool $p
*
* @return Collection<mixed>[] An array with two elements. The first element contains the collection
* of elements where the predicate returned TRUE, the second element
* contains the collection of elements where the predicate returned FALSE.
* @psalm-return array{0: Collection<TKey, T>, 1: Collection<TKey, T>}
*/
public function partition(Closure $p): array;
public function partition(callable $p): array;

/**
* Gets the index/key of a given element. The comparison of two elements is strict,
Expand Down
13 changes: 13 additions & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,18 @@
<file name="lib/Doctrine/Common/Collections/ArrayCollection.php"/>
</errorLevel>
</UnsafeGenericInstantiation>

<MixedInferredReturnType>
<errorLevel type="suppress">
<!-- Remove when https://github.com/vimeo/psalm/pull/8435 is released -->
<file name="lib/Doctrine/Common/Collections/ArrayCollection.php"/>
</errorLevel>
</MixedInferredReturnType>
<MixedReturnStatement>
<errorLevel type="suppress">
<!-- Remove when https://github.com/vimeo/psalm/pull/8435 is released -->
<file name="lib/Doctrine/Common/Collections/ArrayCollection.php"/>
</errorLevel>
</MixedReturnStatement>
</issueHandlers>
</psalm>
40 changes: 40 additions & 0 deletions tests/Doctrine/Tests/Common/Collections/BaseCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ public function testExists(): void
self::assertTrue($exists);
$exists = $this->collection->exists(static fn ($k, $e) => $e === 'other');
self::assertFalse($exists);

// Check support for callable
$exists = $this->collection->exists([$this, 'oneCallable']);
self::assertTrue($exists);
}

public function oneCallable(int $k, string $e): bool
{
return $e === 'one';
}

public function testFindFirst(): void
Expand All @@ -51,6 +60,10 @@ public function testFindFirst(): void
$this->collection->add('two');
$one = $this->collection->findFirst(static fn ($k, $e) => $e === 'one');
self::assertSame('one', $one);

// Check support for callable
$one = $this->collection->findFirst([$this, 'oneCallable']);
self::assertSame('one', $one);
}

public function testFindFirstNotFound(): void
Expand All @@ -67,6 +80,15 @@ public function testMap(): void
$this->collection->add(2);
$res = $this->collection->map(static fn ($e) => $e * 2);
self::assertEquals([2, 4], $res->toArray());

// Check support for callable
$res = $this->collection->map([$this, 'mapCallable']);
self::assertEquals([2, 4], $res->toArray());
}

public function mapCallable(int $e): int
{
return $e * 2;
}

public function testReduce(): void
Expand All @@ -78,6 +100,15 @@ public function testReduce(): void

$res = $this->collection->reduce(static fn ($sum, $e) => $sum + $e);
self::assertSame(10, $res);

// Check support for callable
$res = $this->collection->reduce([$this, 'reduceCallable']);
self::assertSame(10, $res);
}

public function reduceCallable(?int $sum, int $e): int
{
return $sum + $e;
}

public function testFilter(): void
Expand All @@ -87,6 +118,15 @@ public function testFilter(): void
$this->collection->add(3);
$res = $this->collection->filter(static fn ($e) => is_numeric($e));
self::assertEquals([0 => 1, 2 => 3], $res->toArray());

// Check support for callable
$res = $this->collection->filter([$this, 'filterCallable']);
self::assertEquals([0 => 1, 2 => 3], $res->toArray());
}

public function filterCallable(string|int $e): bool
{
return is_numeric($e);
}

public function testFilterByValueAndKey(): void
Expand Down