├── .gitignore
├── src
├── Equatable.php
├── functions.php
└── Result.php
├── phpunit.xml.dist
├── .github
└── workflows
│ └── continuous-integration.yml
├── composer.json
├── LICENSE
├── CHANGELOG.md
├── tests
├── ResultTest.php
└── FunctionsTest.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /vendor
3 | /composer.lock
4 | /.phpunit.cache
5 |
--------------------------------------------------------------------------------
/src/Equatable.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 | ./tests/
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.github/workflows/continuous-integration.yml:
--------------------------------------------------------------------------------
1 | name: "CI"
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | tests:
7 | runs-on: "ubuntu-latest"
8 |
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | php-version:
13 | - 8.4
14 | dependencies:
15 | - "highest"
16 |
17 | steps:
18 | - uses: "actions/checkout@v5"
19 |
20 | - name: "Setup PHP"
21 | uses: "shivammathur/setup-php@v2"
22 | with:
23 | php-version: "${{ matrix.php-version }}"
24 |
25 | - name: "Install dependencies using Composer"
26 | uses: "ramsey/composer-install@v3"
27 | with:
28 | dependency-versions: "${{ matrix.dependencies }}"
29 |
30 | - name: "Run PHPUnit"
31 | run: "vendor/bin/phpunit"
32 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jungi/common",
3 | "description": "A minimal library that defines primitive building blocks of PHP code.",
4 | "keywords": ["equatable", "result", "expected"],
5 | "type": "library",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Piotr Kugla",
10 | "email": "piku235@gmail.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=8.4"
15 | },
16 | "require-dev": {
17 | "phpunit/phpunit": "^12.3"
18 | },
19 | "autoload": {
20 | "files": [
21 | "src/functions.php"
22 | ],
23 | "psr-4": {
24 | "Jungi\\Common\\": "src/"
25 | }
26 | },
27 | "autoload-dev": {
28 | "psr-4": {
29 | "Jungi\\Common\\Tests\\": "tests/"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Piotr Kugla
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [2.0.0] - 2025-09-27
11 |
12 | ### Changed
13 | - Simplified `Result` and adjusted to suit more PHP. The main inspirations: c++23 `std::expected` and Rust's `Result`
14 | - Adjusted to PHP **8.4**
15 | - Updated PHPUnit to **v12**
16 |
17 | ### Removed
18 | - Class `Option`
19 |
20 | ## [1.2.0] - 2022-07-09
21 |
22 | ### Added
23 | - Function `array_equals()` that checks if both arrays have the same keys and their values are equal
24 |
25 | ## [1.1.1] - 2022-05-27
26 |
27 | ### Added
28 | - Template type declaration at `Equatable::equals()`. PHPStorm 2022.1 now handles generics types in @method tags, and it's also helpful when using an analysis tool.
29 | - Missing @param tag at `Option::some()`
30 | - Missing @param tags in local functions
31 | - Missing @template tags in static methods of `Result`
32 | - Missing @template tags in static methods of `Option`
33 |
34 | ## [1.1.0] - 2021-10-30
35 |
36 | ### Added
37 | - Function `iterable_search()` that returns the first key where the given value is equal.
38 |
39 | [unreleased]: https://github.com/jungi-php/common/compare/v2.0.0...HEAD
40 | [2.0.0]: https://github.com/jungi-php/common/compare/v1.2.0...v2.0.0
41 | [1.2.0]: https://github.com/jungi-php/common/compare/v1.1.1...v1.2.0
42 | [1.1.1]: https://github.com/jungi-php/common/compare/v1.1.0...v1.1.1
43 | [1.1.0]: https://github.com/jungi-php/common/compare/v1.0.0...v1.1.0
44 |
--------------------------------------------------------------------------------
/src/functions.php:
--------------------------------------------------------------------------------
1 | equals($b);
16 | } catch (\TypeError) {
17 | return false;
18 | }
19 | }
20 |
21 | return $a === $b;
22 | }
23 |
24 | /**
25 | * Returns true if a value is present in an iterable,
26 | * otherwise false.
27 | */
28 | function in_iterable(mixed $value, iterable $iterable): bool
29 | {
30 | foreach ($iterable as $iteratedValue) {
31 | if (equals($value, $iteratedValue)) {
32 | return true;
33 | }
34 | }
35 |
36 | return false;
37 | }
38 |
39 | /**
40 | * Returns an iterable without duplicates.
41 | */
42 | function iterable_unique(iterable $iterable): iterable
43 | {
44 | $seen = [];
45 | foreach ($iterable as $key => $value) {
46 | if (!in_iterable($value, $seen)) {
47 | $seen[] = $value;
48 |
49 | yield $key => $value;
50 | }
51 | }
52 | }
53 |
54 | /**
55 | * Returns the first key where the given value is equal.
56 | * If the value is not found, false is returned.
57 | */
58 | function iterable_search(mixed $value, iterable $iterable): mixed
59 | {
60 | foreach ($iterable as $key => $iteratedValue) {
61 | if (equals($value, $iteratedValue)) {
62 | return $key;
63 | }
64 | }
65 |
66 | return false;
67 | }
68 |
69 | /**
70 | * Returns true if both arrays have the same keys
71 | * and their values are equal.
72 | */
73 | function array_equals(array $a, array $b): bool
74 | {
75 | if (count($a) !== count($b)) {
76 | return false;
77 | }
78 |
79 | foreach ($a as $key => $value) {
80 | if (!array_key_exists($key, $b) || !equals($value, $b[$key])) {
81 | return false;
82 | }
83 | }
84 |
85 | return true;
86 | }
87 |
--------------------------------------------------------------------------------
/tests/ResultTest.php:
--------------------------------------------------------------------------------
1 | $r */
16 | $r = Result::ok("foo");
17 |
18 | $this->assertTrue($r->isOk());
19 | $this->assertEquals("foo", $r->value);
20 | $this->assertEquals("foo", $r());
21 | }
22 |
23 | public function testThatThrowsOnValue(): void
24 | {
25 | /** @var Result $r */
26 | $r = Result::ok("foo");
27 |
28 | $this->expectException(\LogicException::class);
29 |
30 | $r->error;
31 | }
32 |
33 | public function testThatErrorIsAvailable(): void
34 | {
35 | /** @var Result $r */
36 | $r = Result::error(self::ERR_TEST);
37 |
38 | $this->assertFalse($r->isOk());
39 | $this->assertEquals(self::ERR_TEST, $r->error);
40 | }
41 |
42 | public function testThatThrowsOnError(): void
43 | {
44 | /** @var Result $r */
45 | $r = Result::error(self::ERR_TEST);
46 |
47 | $this->expectException(\LogicException::class);
48 |
49 | $r->value;
50 | }
51 |
52 | public function testThatResultEqualsAndDoesNotEqual(): void
53 | {
54 | $this->assertTrue(Result::ok("foo")->equals(Result::ok("foo")));
55 | $this->assertTrue(Result::ok(new Foo("foo"))->equals(Result::ok(new Foo("foo"))));
56 | $this->assertTrue(Result::error(self::ERR_TEST)->equals(Result::error(self::ERR_TEST)));
57 | $this->assertTrue(Result::error(new Foo("foo"))->equals(Result::error(new Foo("foo"))));
58 | $this->assertFalse(Result::ok("foo")->equals(Result::error("foo")));
59 | $this->assertFalse(Result::error("foo")->equals(Result::ok("foo")));
60 | $this->assertFalse(Result::error("foo")->equals(Result::ok("foo")));
61 | }
62 |
63 | public function testThatDefaultValueIsReturned(): void
64 | {
65 | /** @var Result $r */
66 | $r = Result::error(self::ERR_TEST);
67 |
68 | $this->assertNull($r->valueOr(null));
69 | }
70 |
71 | public function testThatDefaultValueIsNotReturned(): void
72 | {
73 | /** @var Result $r */
74 | $r = Result::ok("foo");
75 |
76 | $this->assertEquals("foo", $r->valueOr("default"));
77 | }
78 |
79 | public function testThatValueIsMapped(): void
80 | {
81 | /** @var Result $r */
82 | $r = Result::ok("foo")->map(fn ($value) => 1.23);
83 |
84 | $this->assertTrue($r->isOk());
85 | $this->assertEquals(1.23, $r->value);
86 | }
87 |
88 | public function testThatValueIsNotMapped(): void
89 | {
90 | /** @var Result $r */
91 | $r = Result::error(self::ERR_TEST)->map(fn($value) => 1.23);
92 |
93 | $this->assertFalse($r->isOk());
94 | $this->assertEquals(self::ERR_TEST, $r->error);
95 | }
96 |
97 | public function testThatErrorIsMapped(): void
98 | {
99 | /** @var Result $r */
100 | $r = Result::error(self::ERR_TEST)->mapError(fn ($value) => "err");
101 |
102 | $this->assertFalse($r->isOk());
103 | $this->assertEquals("err", $r->error);
104 | }
105 |
106 | public function testThatErrorIsNotMapped(): void
107 | {
108 | /** @var Result $r */
109 | $r = Result::ok("foo")->mapError(fn ($value) => "err");
110 |
111 | $this->assertTrue($r->isOk());
112 | $this->assertEquals("foo", $r->value);
113 | }
114 | }
115 |
116 | final class Foo implements Equatable
117 | {
118 | public function __construct(public readonly string $value) {}
119 |
120 | public function equals(self $other): bool
121 | {
122 | return $this->value === $other->value;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Result.php:
--------------------------------------------------------------------------------
1 | >
13 | *
14 | * @final
15 | */
16 | abstract class Result implements Equatable
17 | {
18 | /**
19 | * @param T|null $value Null in case T of void
20 | *
21 | * @return Result
22 | */
23 | public static function ok($value = null): self
24 | {
25 | return new Ok($value);
26 | }
27 |
28 | /**
29 | * @param E $value
30 | *
31 | * @return Result
32 | */
33 | public static function error($value): self
34 | {
35 | return new Error($value);
36 | }
37 |
38 | /**
39 | * @var T
40 | *
41 | * @throws \LogicException If accessed when the result has error
42 | */
43 | protected(set) mixed $value;
44 |
45 | /**
46 | * @var E
47 | *
48 | * @throws \LogicException If accessed when the result has value
49 | */
50 | protected(set) mixed $error;
51 |
52 | /**
53 | * Dereferences the contained value.
54 | *
55 | * This is a shorthand for accessing the result's value directly `$this->value`.
56 | *
57 | * @example $r()->foo
58 | *
59 | * @return T The contained value
60 | *
61 | * @throws \LogicException If the result has error
62 | */
63 | public function __invoke()
64 | {
65 | return $this->value;
66 | }
67 |
68 | abstract public function isOk(): bool;
69 |
70 | /**
71 | * Returns the contained value, or the given default if the result is an error.
72 | *
73 | * @template U
74 | *
75 | * @param U $defaultValue
76 | *
77 | * @return T|U
78 | */
79 | abstract public function valueOr($defaultValue);
80 |
81 | /**
82 | * Returns a new result with the value mapped using the given callback.
83 | * If the result has error, it is returned unchanged.
84 | *
85 | *
86 | * function mul(int $value): int { return 3 * $value; }
87 | *
88 | * assert(6 === Result::ok(2)->map('mul'));
89 | * assert(2 === Result::error(2)->map('mul'));
90 | *
91 | *
92 | * @template U
93 | *
94 | * @param callable(T): U $fn
95 | *
96 | * @return Result
97 | */
98 | abstract public function map(callable $fn): self;
99 |
100 | /**
101 | * Returns a new result with the error mapped using the given callback.
102 | * If the result has value, it is returned unchanged.
103 | *
104 | *
105 | * function mul(int $value): int { return 3 * $value; }
106 | *
107 | * assert(2 === Result::ok(2)->mapError('mul'));
108 | * assert(6 === Result::error(2)->mapError('mul'));
109 | *
110 | *
111 | * @template U
112 | *
113 | * @param callable(E): U $fn
114 | *
115 | * @return Result
116 | */
117 | abstract public function mapError(callable $fn): self;
118 | }
119 |
120 | /**
121 | * @internal Part of the implementation details, do not use outside
122 | */
123 | final class Ok extends Result
124 | {
125 | protected(set) mixed $value {
126 | get {
127 | return $this->value;
128 | }
129 | }
130 |
131 | protected(set) mixed $error {
132 | get => throw new \LogicException("Called on expected value.");
133 | }
134 |
135 | public function __construct($value)
136 | {
137 | $this->value = $value;
138 | }
139 |
140 | public function equals(Result $other): bool
141 | {
142 | return $other instanceof self && equals($this->value, $other->value);
143 | }
144 |
145 | public function isOk(): bool
146 | {
147 | return true;
148 | }
149 |
150 | public function valueOr($defaultValue)
151 | {
152 | return $this->value;
153 | }
154 |
155 | public function map(callable $fn): self
156 | {
157 | return new self($fn($this->value));
158 | }
159 |
160 | public function mapError(callable $fn): self
161 | {
162 | return $this;
163 | }
164 | }
165 |
166 | /**
167 | * @internal Part of the implementation details, do not use outside
168 | */
169 | final class Error extends Result
170 | {
171 | protected(set) mixed $value {
172 | get => throw new \LogicException("Called on error.");
173 | }
174 |
175 | protected(set) mixed $error {
176 | get {
177 | return $this->error;
178 | }
179 | }
180 |
181 | public function __construct($error)
182 | {
183 | $this->error = $error;
184 | }
185 |
186 | public function equals(Result $other): bool
187 | {
188 | return $other instanceof self && equals($this->error, $other->error);
189 | }
190 |
191 | public function isOk(): bool
192 | {
193 | return false;
194 | }
195 |
196 | public function valueOr($defaultValue)
197 | {
198 | return $defaultValue;
199 | }
200 |
201 | public function map(callable $fn): self
202 | {
203 | return $this;
204 | }
205 |
206 | public function mapError(callable $fn): self
207 | {
208 | return new self($fn($this->error));
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🌱 jungi/common
2 |
3 | [](https://github.com/jungi-php/common/actions/workflows/continuous-integration.yml)
4 | 
5 |
6 | A minimal library that defines primitive building blocks of PHP code. It defines basic types and useful functions.
7 |
8 | **Primitive types:**
9 |
10 | * [`Equatable`](https://piku235.gitbook.io/jungi-common/equatable)
11 | * [`Result`](https://piku235.gitbook.io/jungi-common/result)
12 |
13 | ## Installation
14 |
15 | ```text
16 | composer require jungi/common
17 | ```
18 |
19 | ## Documentation
20 |
21 | [GitBook](https://piku235.gitbook.io/jungi-common)
22 |
23 | ## Quick insight
24 |
25 | ### Equatable
26 |
27 | ```php
28 | /** @implements Equatable */
29 | class Phone implements Equatable
30 | {
31 | public function __construct(private string $value) {}
32 |
33 | public function equals(self $other): bool
34 | {
35 | return $this->value === $other->value;
36 | }
37 | }
38 |
39 | assert(true === (new Phone('(321) 456-1234'))->equals(new Phone('(321) 456-1234')));
40 | assert(false === (new Phone('(321) 456-1234'))->equals(new Phone('(454) 456-1234')));
41 | ```
42 |
43 | ### Result
44 |
45 | ```php
46 | final class Student
47 | {
48 | public function __construct(
49 | public readonly StudentId $id,
50 | private(set) bool $active,
51 | private(set) string $name,
52 | ) {}
53 | }
54 |
55 | enum ClassEnrollmentError: string {
56 | case InactiveStudent = 'inactive_student';
57 | case StudentAlreadyEnrolled = 'student_already_enrolled';
58 | case NoSeatsAvailable = 'no_seats_available';
59 | }
60 |
61 | final class Class_
62 | {
63 | public function __construct(
64 | public readonly ClassId $id,
65 | private(set) int $numberOfSeats,
66 | /** @var StudentId[] */
67 | private(set) array $students,
68 | ) {}
69 |
70 | /** @return Result */
71 | public function enroll(Student $student): Result
72 | {
73 | if (!$student->active) {
74 | return Result::error(ClassEnrollmentError::InactiveStudent);
75 | }
76 | if (in_iterable($student->id(), $this->students)) {
77 | return Result::error(ClassEnrollmentError::StudentAlreadyEnrolled);
78 | }
79 | if (count($this->students) >= $this->numberOfSeats) {
80 | return Result::error(ClassEnrollmentError::NoSeatsAvailable);
81 | }
82 |
83 | $this->students[] = $student->id();
84 |
85 | return Result::ok();
86 | }
87 | }
88 |
89 | class ClassController
90 | {
91 | // PUT /classes/{classId}/students/{studentId}
92 | public function enrollToClass(string $classId, string $studentId)
93 | {
94 | // ... fetch the class and the student
95 | $r = $class->enroll($student);
96 |
97 | if ($r->isOk()) {
98 | return $this->created();
99 | }
100 |
101 | return match ($r->error) {
102 | ClassEnrollmentError::StudentAlreadyEnrolled => $this->noContent(),
103 | default => $this->conflict($error),
104 | };
105 | }
106 | }
107 | ```
108 |
109 | ### Functions
110 |
111 | ```php
112 | use function Jungi\Common\equals;
113 | use function Jungi\Common\in_iterable;
114 | use function Jungi\Common\iterable_unique;
115 | use function Jungi\Common\array_equals;
116 |
117 | /** @implements Equatable */
118 | class ContactInformation implements Equatable
119 | {
120 | public function __construct(
121 | private Phone $phone,
122 | private ?Phone $mobile = null
123 | ) {}
124 |
125 | public function equals(self $other): bool
126 | {
127 | return $this->phone->equals($other->phone)
128 | && equals($this->mobile, $other->mobile);
129 | }
130 | }
131 |
132 | // equals()
133 |
134 | $a = new ContactInformation(new Phone('(321) 456-1234'), new Phone('(886) 456-6543'));
135 | $b = new ContactInformation(new Phone('(321) 456-1234'), new Phone('(886) 456-6543'));
136 | assert(true === equals($a, $b);
137 |
138 | $a = new ContactInformation(new Phone('(321) 456-1234'));
139 | $b = new ContactInformation(new Phone('(321) 456-1234'), new Phone('(886) 456-6543'));
140 | assert(false === equals($a, $b);
141 |
142 | // array_equals()
143 |
144 | $a = [new Phone('(321) 456-1234'), new Phone('(465) 799-4566')];
145 | $b = [new Phone('(321) 456-1234'), new Phone('(465) 799-4566')];
146 | assert(true === array_equals($a, $b));
147 |
148 | $a = [new Phone('(321) 456-1234'), new Phone('(465) 799-4566')];
149 | $b = [new Phone('(321) 456-1234')];
150 | assert(false === array_equals($a, $b));
151 |
152 | // in_iterable()
153 |
154 | $iterable = [new Phone('(656) 456-7765'), new Phone('(321) 456-1234')];
155 | assert(true === in_iterable(new Phone('(321) 456-1234'), $iterable));
156 | assert(false === in_iterable(new Phone('(232) 456-1234'), $iterable));
157 |
158 | // iterable_unique()
159 |
160 | $unique = iterable_unique([
161 | new Phone('(321) 456-1234'),
162 | new Phone('(465) 799-4566'),
163 | new Phone('(321) 456-1234'),
164 | ]);
165 | $expected = [
166 | new Phone('(321) 456-1234'),
167 | new Phone('(465) 799-4566'),
168 | ];
169 | assert(true === array_equals($expected, $unique));
170 | ```
171 |
--------------------------------------------------------------------------------
/tests/FunctionsTest.php:
--------------------------------------------------------------------------------
1 | 3], [1, 2, 2, 3, 2]];
67 | yield [['foo'], ['foo', 'foo']];
68 | yield [['foo', ''], ['foo', '']];
69 | yield [[true, false], [true, false, true]];
70 | yield [[1.23, 2.34], [1.23, 2.34, 1.23]];
71 | yield [
72 | [new SameEquatable(123), new SameEquatable(345), 3 => new SameEquatable(321)],
73 | [new SameEquatable(123), new SameEquatable(345), new SameEquatable(123), new SameEquatable(321)]
74 | ];
75 | }
76 |
77 | public static function provideIterablesWithExistingKeys(): iterable
78 | {
79 | yield ['bar', 2, [
80 | 'foo' => 1,
81 | 'bar' => 2,
82 | 'zoo' => 2
83 | ]];
84 | yield ['bar', new SameEquatable(2), [
85 | 'foo' => 1,
86 | 'bar' => new SameEquatable(2),
87 | 'zoo' => new SameEquatable(2)
88 | ]];
89 | yield ['zoo', new VaryEquatable(2), [
90 | 'foo' => 1,
91 | 'bar' => new SameEquatable(2),
92 | 'zoo' => 2
93 | ]];
94 | }
95 |
96 | public static function provideIterablesWithNonExistingKeys(): iterable
97 | {
98 | yield [0, []];
99 | yield [0, [
100 | 'foo' => 1,
101 | 'bar' => 2,
102 | 'zoo' => 2
103 | ]];
104 | yield [new SameEquatable(1), [
105 | 'foo' => 1,
106 | 'bar' => new SameEquatable(2),
107 | 'zoo' => new SameEquatable(2)
108 | ]];
109 | }
110 |
111 | public static function provideEqualArrays(): iterable
112 | {
113 | yield [[], []];
114 | yield [[null], [null]];
115 | yield [[1 => 'foo', 0 => 'bar'], ['bar', 'foo']];
116 | yield [[1.23, 2.34], [1.23, 2.34]];
117 | yield [[new SameEquatable(123), new SameEquatable(234)], [new SameEquatable(123), new SameEquatable(234)]];
118 | yield [[new VaryEquatable(123)], [123]];
119 | }
120 |
121 | public static function provideNotEqualArrays(): iterable
122 | {
123 | yield [[], [null]];
124 | yield [[false], [null]];
125 | yield [['foo'], []];
126 | yield [['foo'], ['foo', 'bar']];
127 | yield [['foo', 'bar'], ['foo']];
128 | yield [['foo', 'bar'], ['bar', 'foo']];
129 | yield [[new SameEquatable(123)], [new SameEquatable(234)]];
130 | yield [[123], [new VaryEquatable(123)]];
131 | }
132 |
133 | #[DataProvider('provideEqualVariables')]
134 | public function testThatTwoVariablesEqual($a, $b): void
135 | {
136 | $this->assertTrue(equals($a, $b));
137 | }
138 |
139 | #[DataProvider('provideNotEqualVariables')]
140 | public function testThatTwoVariablesNotEqual($a, $b): void
141 | {
142 | $this->assertFalse(equals($a, $b));
143 | }
144 |
145 | #[DataProvider('providePresentValuesInIterables')]
146 | public function testThatValueIsInIterable($value, $iterable): void
147 | {
148 | $this->assertTrue(in_iterable($value, $iterable));
149 | }
150 |
151 | #[DataProvider('provideNotPresentValuesInIterables')]
152 | public function testThatValueIsNotInIterable($value, $iterable): void
153 | {
154 | $this->assertFalse(in_iterable($value, $iterable));
155 | }
156 |
157 | #[DataProvider('provideIterablesWithUniqueAndDuplicatedValues')]
158 | public function testThatDuplicatedIterableValuesAreRemoved(array $expected, iterable $iterable): void
159 | {
160 | $uniqueIterable = $this->iterableToArray(iterable_unique($iterable));
161 |
162 | $this->assertCount(count($expected), $uniqueIterable);
163 | $this->assertEmpty(array_udiff_assoc($expected, $uniqueIterable, function ($a, $b) {
164 | return equals($a, $b) ? 0 : ($a > $b ? 1 : -1);
165 | }));
166 | }
167 |
168 | #[DataProvider('provideIterablesWithExistingKeys')]
169 | public function testThatKeyIsReturnedFromIterable($expectedKey, $value, iterable $iterable): void
170 | {
171 | $this->assertSame($expectedKey, iterable_search($value, $iterable));
172 | }
173 |
174 | #[DataProvider('provideIterablesWithNonExistingKeys')]
175 | public function testThatKeyIsNotReturnedFromIterable($value, iterable $iterable): void
176 | {
177 | $this->assertFalse(iterable_search($value, $iterable));
178 | }
179 |
180 | #[DataProvider('provideEqualArrays')]
181 | public function testThatArraysEqual(array $a, array $b): void
182 | {
183 | $this->assertTrue(array_equals($a, $b));
184 | }
185 |
186 | #[DataProvider('provideNotEqualArrays')]
187 | public function testThatArraysNotEqual(array $a, array $b): void
188 | {
189 | $this->assertFalse(array_equals($a, $b));
190 | }
191 |
192 | private function iterableToArray(iterable $iterable): array
193 | {
194 | $arr = [];
195 | foreach ($iterable as $key => $value) {
196 | $arr[$key] = $value;
197 | }
198 |
199 | return $arr;
200 | }
201 | }
202 |
203 | /** @implements Equatable */
204 | final class SameEquatable implements Equatable
205 | {
206 | private $value;
207 |
208 | public function __construct($value)
209 | {
210 | $this->value = $value;
211 | }
212 |
213 | public function equals(self $other): bool
214 | {
215 | return $this->value == $other->value;
216 | }
217 | }
218 |
219 | /** @implements Equatable */
220 | final class VaryEquatable implements Equatable
221 | {
222 | private int $value;
223 |
224 | public function __construct(int $value)
225 | {
226 | $this->value = $value;
227 | }
228 |
229 | public function equals(int $other): bool
230 | {
231 | return $this->value == $other;
232 | }
233 | }
234 |
--------------------------------------------------------------------------------