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

Yoast / PHPUnit-Polyfills / 16579531037

28 Jul 2025 08:25PM UTC coverage: 63.368% (-32.5%) from 95.846%
16579531037

push

github

jrfnl
Merge branch '1.x' into 2.x

365 of 576 relevant lines covered (63.37%)

17.41 hits per line

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

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

3
namespace Yoast\PHPUnitPolyfills\Polyfills;
4

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

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

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

69
                if ( \is_object( $actual ) === false ) {
56✔
70
                        throw new TypeError(
3✔
71
                                \sprintf(
3✔
72
                                        'Argument 2 passed to assertObjectEquals() must be an object, %s given',
3✔
73
                                        \gettype( $actual )
3✔
74
                                )
2✔
75
                        );
2✔
76
                }
77

78
                if ( \is_scalar( $method ) === false ) {
53✔
79
                        throw new TypeError(
3✔
80
                                \sprintf(
3✔
81
                                        'Argument 3 passed to assertObjectEquals() must be of the type string, %s given',
3✔
82
                                        \gettype( $method )
3✔
83
                                )
2✔
84
                        );
2✔
85
                }
86
                else {
87
                        $method = (string) $method;
50✔
88
                }
89

90
                /*
91
                 * Comparator method validation.
92
                 */
93
                $reflObject = new ReflectionObject( $actual );
50✔
94

95
                if ( $reflObject->hasMethod( $method ) === false ) {
50✔
96
                        throw new InvalidComparisonMethodException(
3✔
97
                                \sprintf(
3✔
98
                                        'Comparison method %s::%s() does not exist.',
3✔
99
                                        \get_class( $actual ),
3✔
100
                                        $method
3✔
101
                                )
2✔
102
                        );
2✔
103
                }
104

105
                $reflMethod = $reflObject->getMethod( $method );
47✔
106

107
                /*
108
                 * As the next step, PHPUnit natively would validate the return type,
109
                 * but as return type declarations is a PHP 7.0+ feature, the polyfill
110
                 * skips this check in favour of checking the type of the actual
111
                 * returned value.
112
                 *
113
                 * Also see the upstream discussion about this:
114
                 * {@link https://github.com/sebastianbergmann/phpunit/issues/4707}
115
                 */
116

117
                /*
118
                 * Comparator method parameter requirements validation.
119
                 */
120
                if ( $reflMethod->getNumberOfParameters() !== 1
47✔
121
                        || $reflMethod->getNumberOfRequiredParameters() !== 1
47✔
122
                ) {
123
                        throw new InvalidComparisonMethodException(
6✔
124
                                \sprintf(
6✔
125
                                        'Comparison method %s::%s() does not declare exactly one parameter.',
6✔
126
                                        \get_class( $actual ),
6✔
127
                                        $method
6✔
128
                                )
4✔
129
                        );
4✔
130
                }
131

132
                $noDeclaredTypeError = \sprintf(
41✔
133
                        'Parameter of comparison method %s::%s() does not have a declared type.',
41✔
134
                        \get_class( $actual ),
41✔
135
                        $method
41✔
136
                );
28✔
137

138
                $notAcceptableTypeError = \sprintf(
41✔
139
                        '%s is not an accepted argument type for comparison method %s::%s().',
41✔
140
                        \get_class( $expected ),
41✔
141
                        \get_class( $actual ),
41✔
142
                        $method
41✔
143
                );
28✔
144

145
                $reflParameter = $reflMethod->getParameters()[0];
41✔
146

147
                if ( \method_exists( $reflParameter, 'hasType' ) ) {
41✔
148
                        // PHP >= 7.0.
149
                        $hasType = $reflParameter->hasType();
41✔
150
                        if ( $hasType === false ) {
41✔
151
                                throw new InvalidComparisonMethodException( $noDeclaredTypeError );
3✔
152
                        }
153

154
                        $type = $reflParameter->getType();
38✔
155
                        if ( \class_exists( 'ReflectionNamedType' ) ) {
38✔
156
                                // PHP >= 7.1.
157
                                if ( ( $type instanceof ReflectionNamedType ) === false ) {
38✔
158
                                        throw new InvalidComparisonMethodException( $noDeclaredTypeError );
2✔
159
                                }
160

161
                                $typeName = $type->getName();
36✔
162
                        }
163
                        else {
164
                                /*
165
                                 * PHP 7.0.
166
                                 * Checking for `ReflectionType` will not throw an error on union types,
167
                                 * but then again union types are not supported on PHP 7.0.
168
                                 */
169
                                if ( ( $type instanceof ReflectionType ) === false ) {
×
170
                                        throw new InvalidComparisonMethodException( $noDeclaredTypeError );
×
171
                                }
172

173
                                $typeName = (string) $type;
24✔
174
                        }
175
                }
176
                else {
177
                        // PHP < 7.0.
178
                        try {
179
                                /*
180
                                 * Using `ReflectionParameter::getClass()` will trigger an autoload of the class,
181
                                 * but that's okay as for a valid class type that would be triggered on the
182
                                 * function call to the $method (at the end of this assertion) anyway.
183
                                 */
184
                                $hasType = $reflParameter->getClass();
×
185
                        } catch ( ReflectionException $e ) {
×
186
                                // Class with a type declaration for a non-existent class.
187
                                throw new InvalidComparisonMethodException( $notAcceptableTypeError );
×
188
                        }
189

190
                        if ( ( $hasType instanceof ReflectionClass ) === false ) {
×
191
                                // Array or callable type.
192
                                throw new InvalidComparisonMethodException( $noDeclaredTypeError );
×
193
                        }
194

195
                        $typeName = $hasType->name;
×
196
                }
197

198
                /*
199
                 * Validate that the $expected object complies with the declared parameter type.
200
                 */
201
                if ( $typeName === 'self' ) {
36✔
202
                        $typeName = \get_class( $actual );
24✔
203
                }
204

205
                if ( ( $expected instanceof $typeName ) === false ) {
36✔
206
                        throw new InvalidComparisonMethodException( $notAcceptableTypeError );
9✔
207
                }
208

209
                /*
210
                 * Execute the comparator method.
211
                 */
212
                $result = $actual->{$method}( $expected );
27✔
213

214
                if ( \is_bool( $result ) === false ) {
27✔
215
                        throw new InvalidComparisonMethodException(
3✔
216
                                \sprintf(
3✔
217
                                        'Comparison method %s::%s() does not return a boolean value.',
3✔
218
                                        \get_class( $actual ),
3✔
219
                                        $method
3✔
220
                                )
2✔
221
                        );
2✔
222
                }
223

224
                $msg = \sprintf(
24✔
225
                        'Failed asserting that two objects are equal. The objects are not equal according to %s::%s()',
24✔
226
                        \get_class( $actual ),
24✔
227
                        $method
24✔
228
                );
16✔
229

230
                if ( $message !== '' ) {
24✔
231
                        $msg = $message . \PHP_EOL . $msg;
3✔
232
                }
233

234
                static::assertTrue( $result, $msg );
24✔
235
        }
6✔
236
}
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