• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / PHPUnit-Polyfills / 10743602567

06 Sep 2024 06:43PM UTC coverage: 96.423%. First build
10743602567

push

github

jrfnl
Drop support for PHP < 7.0 [4]

In the original implementation of the `assertObjectEquals()` polyfill, the polyfill could not mirror the PHPUnit native implementation completely as that required support for return types, which was only added in PHP 7.0, while the polyfill was introduced in PHPUnit Polyfills 1.0, which still supported PHP 5.5.

So instead of checking whether the "comparator" method had a return type declared and verifying that this return type complied with the requirements set by PHPUnit, the polyfill originally checked whether the _returned value_ complied with the required type.

Now support for PHP < 7.0 is being dropped, the `assertObjectEquals()` polyfill can be updated to fix this implementation difference.

Includes unit tests for the changed functionality/new logic paths throwing exceptions.
Includes updated documentation in the README.

Refs:
* 38
* sebastianbergmann/phpunit 4707
* sebastianbergmann/phpunit 4467
* sebastianbergmann/phpunit 4707
* https://github.com/sebastianbergmann/phpunit/commit/1dba8c3a4
* https://github.com/sebastianbergmann/phpunit/commit/6099c5eef

28 of 29 new or added lines in 1 file covered. (96.55%)

620 of 643 relevant lines covered (96.42%)

124.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.98
/src/Polyfills/AssertObjectEquals.php
1
<?php
2

3
namespace Yoast\PHPUnitPolyfills\Polyfills;
4

5
use ReflectionNamedType;
6
use ReflectionObject;
7
use ReflectionType;
8
use TypeError;
9
use Yoast\PHPUnitPolyfills\Exceptions\InvalidComparisonMethodException;
10

11
/**
12
 * Polyfill the Assert::assertObjectEquals() methods.
13
 *
14
 * Introduced in PHPUnit 9.4.0.
15
 *
16
 * The polyfill implementation closely matches the PHPUnit native implementation with the exception
17
 * of the thrown exceptions.
18
 *
19
 * @link https://github.com/sebastianbergmann/phpunit/issues/4467
20
 * @link https://github.com/sebastianbergmann/phpunit/issues/4707
21
 * @link https://github.com/sebastianbergmann/phpunit/commit/1dba8c3a4b2dd04a3ff1869f75daaeb6757a14ee
22
 * @link https://github.com/sebastianbergmann/phpunit/commit/6099c5eefccfda860c889f575d58b5fe6cc10c83
23
 */
24
trait AssertObjectEquals {
25

26
        /**
27
         * Asserts that two objects are considered equal based on a custom object comparison
28
         * using a comparator method in the target object.
29
         *
30
         * The custom comparator method is expected to have the following method
31
         * signature: `equals(self $other): bool` (or similar with a different method name).
32
         *
33
         * Basically, the assertion checks the following:
34
         * - A method with name $method must exist on the $actual object.
35
         * - The method must accept exactly one argument and this argument must be required.
36
         * - This parameter must have a classname-based declared type.
37
         * - The $expected object must be compatible with this declared type.
38
         * - The method must have a declared bool return type.
39
         *
40
         * @param object $expected Expected value.
41
         * @param object $actual   The value to test.
42
         * @param string $method   The name of the comparator method within the object.
43
         * @param string $message  Optional failure message to display.
44
         *
45
         * @return void
46
         *
47
         * @throws TypeError                        When any of the passed arguments do not meet the required type.
48
         * @throws InvalidComparisonMethodException When the comparator method does not comply with the requirements.
49
         */
50
        final public static function assertObjectEquals( $expected, $actual, $method = 'equals', $message = '' ) {
345✔
51
                /*
52
                 * Parameter input validation.
53
                 * In PHPUnit this is done via PHP native type declarations. Emulating this for the polyfill.
54
                 */
55
                if ( \is_object( $expected ) === false ) {
345✔
56
                        throw new TypeError(
17✔
57
                                \sprintf(
17✔
58
                                        'Argument 1 passed to assertObjectEquals() must be an object, %s given',
17✔
59
                                        \gettype( $expected )
17✔
60
                                )
3✔
61
                        );
3✔
62
                }
63

64
                if ( \is_object( $actual ) === false ) {
328✔
65
                        throw new TypeError(
17✔
66
                                \sprintf(
17✔
67
                                        'Argument 2 passed to assertObjectEquals() must be an object, %s given',
17✔
68
                                        \gettype( $actual )
17✔
69
                                )
3✔
70
                        );
3✔
71
                }
72

73
                if ( \is_scalar( $method ) === false ) {
311✔
74
                        throw new TypeError(
17✔
75
                                \sprintf(
17✔
76
                                        'Argument 3 passed to assertObjectEquals() must be of the type string, %s given',
17✔
77
                                        \gettype( $method )
17✔
78
                                )
3✔
79
                        );
3✔
80
                }
81
                else {
82
                        $method = (string) $method;
294✔
83
                }
84

85
                /*
86
                 * Comparator method validation.
87
                 */
88
                $reflObject = new ReflectionObject( $actual );
294✔
89

90
                if ( $reflObject->hasMethod( $method ) === false ) {
294✔
91
                        throw new InvalidComparisonMethodException(
17✔
92
                                \sprintf(
17✔
93
                                        'Comparison method %s::%s() does not exist.',
17✔
94
                                        \get_class( $actual ),
17✔
95
                                        $method
17✔
96
                                )
3✔
97
                        );
3✔
98
                }
99

100
                $reflMethod = $reflObject->getMethod( $method );
277✔
101

102
                /*
103
                 * Comparator method return type requirements validation.
104
                 */
105
                $returnTypeError = \sprintf(
277✔
106
                        'Comparison method %s::%s() does not declare bool return type.',
277✔
107
                        \get_class( $actual ),
277✔
108
                        $method
277✔
109
                );
57✔
110

111
                if ( $reflMethod->hasReturnType() === false ) {
277✔
112
                        throw new InvalidComparisonMethodException( $returnTypeError );
17✔
113
                }
114

115
                $returnType = $reflMethod->getReturnType();
260✔
116

117
                if ( \class_exists( 'ReflectionNamedType' ) ) {
260✔
118
                        // PHP >= 7.1: guard against union/intersection return types.
119
                        if ( ( $returnType instanceof ReflectionNamedType ) === false ) {
236✔
120
                                throw new InvalidComparisonMethodException( $returnTypeError );
219✔
121
                        }
122
                }
123
                elseif ( ( $returnType instanceof ReflectionType ) === false ) {
24✔
124
                        /*
125
                         * PHP 7.0.
126
                         * Checking for `ReflectionType` will not throw an error on union types,
127
                         * but then again union types are not supported on PHP 7.0.
128
                         */
NEW
129
                        throw new InvalidComparisonMethodException( $returnTypeError );
×
130
                }
131

132
                if ( $returnType->allowsNull() === true ) {
257✔
133
                        throw new InvalidComparisonMethodException( $returnTypeError );
15✔
134
                }
135

136
                if ( \method_exists( $returnType, 'getName' ) ) {
242✔
137
                        // PHP 7.1+.
138
                        if ( $returnType->getName() !== 'bool' ) {
218✔
139
                                throw new InvalidComparisonMethodException( $returnTypeError );
203✔
140
                        }
141
                }
142
                elseif ( (string) $returnType !== 'bool' ) {
24✔
143
                        // PHP 7.0.
144
                        throw new InvalidComparisonMethodException( $returnTypeError );
2✔
145
                }
146

147
                /*
148
                 * Comparator method parameter requirements validation.
149
                 */
150
                if ( $reflMethod->getNumberOfParameters() !== 1
225✔
151
                        || $reflMethod->getNumberOfRequiredParameters() !== 1
225✔
152
                ) {
153
                        throw new InvalidComparisonMethodException(
32✔
154
                                \sprintf(
32✔
155
                                        'Comparison method %s::%s() does not declare exactly one parameter.',
32✔
156
                                        \get_class( $actual ),
32✔
157
                                        $method
32✔
158
                                )
6✔
159
                        );
6✔
160
                }
161

162
                $noDeclaredTypeError = \sprintf(
193✔
163
                        'Parameter of comparison method %s::%s() does not have a declared type.',
193✔
164
                        \get_class( $actual ),
193✔
165
                        $method
193✔
166
                );
39✔
167

168
                $notAcceptableTypeError = \sprintf(
193✔
169
                        '%s is not an accepted argument type for comparison method %s::%s().',
193✔
170
                        \get_class( $expected ),
193✔
171
                        \get_class( $actual ),
193✔
172
                        $method
193✔
173
                );
39✔
174

175
                $reflParameter = $reflMethod->getParameters()[0];
193✔
176

177
                $hasType = $reflParameter->hasType();
193✔
178
                if ( $hasType === false ) {
193✔
179
                        throw new InvalidComparisonMethodException( $noDeclaredTypeError );
17✔
180
                }
181

182
                $type = $reflParameter->getType();
176✔
183
                if ( \class_exists( 'ReflectionNamedType' ) ) {
176✔
184
                        // PHP >= 7.1.
185
                        if ( ( $type instanceof ReflectionNamedType ) === false ) {
158✔
186
                                throw new InvalidComparisonMethodException( $noDeclaredTypeError );
3✔
187
                        }
188

189
                        $typeName = $type->getName();
155✔
190
                }
191
                else {
192
                        /*
193
                         * PHP 7.0.
194
                         * Checking for `ReflectionType` will not throw an error on union types,
195
                         * but then again union types are not supported on PHP 7.0.
196
                         */
197
                        if ( ( $type instanceof ReflectionType ) === false ) {
18✔
198
                                throw new InvalidComparisonMethodException( $noDeclaredTypeError );
×
199
                        }
200

201
                        $typeName = (string) $type;
18✔
202
                }
203

204
                /*
205
                 * Validate that the $expected object complies with the declared parameter type.
206
                 */
207
                if ( $typeName === 'self' ) {
173✔
208
                        $typeName = \get_class( $actual );
105✔
209
                }
210

211
                if ( ( $expected instanceof $typeName ) === false ) {
173✔
212
                        throw new InvalidComparisonMethodException( $notAcceptableTypeError );
51✔
213
                }
214

215
                /*
216
                 * Execute the comparator method.
217
                 */
218
                $result = $actual->{$method}( $expected );
122✔
219

220
                $msg = \sprintf(
122✔
221
                        'Failed asserting that two objects are equal. The objects are not equal according to %s::%s()',
122✔
222
                        \get_class( $actual ),
122✔
223
                        $method
122✔
224
                );
24✔
225

226
                if ( $message !== '' ) {
122✔
227
                        $msg = $message . \PHP_EOL . $msg;
17✔
228
                }
229

230
                static::assertTrue( $result, $msg );
122✔
231
        }
70✔
232
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc