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

Yoast / PHPUnit-Polyfills / 8548615786

04 Apr 2024 02:56AM UTC coverage: 95.597% (-1.1%) from 96.708%
8548615786

push

github

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

608 of 636 relevant lines covered (95.6%)

84.15 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
trait AssertObjectEquals {
27

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

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

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

88
                /*
89
                 * Comparator method validation.
90
                 */
91
                $reflObject = new ReflectionObject( $actual );
141✔
92

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

103
                $reflMethod = $reflObject->getMethod( $method );
132✔
104

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

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

130
                $noDeclaredTypeError = \sprintf(
114✔
131
                        'Parameter of comparison method %s::%s() does not have a declared type.',
114✔
132
                        \get_class( $actual ),
114✔
133
                        $method
114✔
134
                );
42✔
135

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

143
                $reflParameter = $reflMethod->getParameters()[0];
114✔
144

145
                if ( \method_exists( $reflParameter, 'hasType' ) ) {
114✔
146
                        // PHP >= 7.0.
147
                        $hasType = $reflParameter->hasType();
114✔
148
                        if ( $hasType === false ) {
114✔
149
                                throw new InvalidComparisonMethodException( $noDeclaredTypeError );
9✔
150
                        }
151

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

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

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

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

193
                        $typeName = $hasType->name;
×
194
                }
195

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

203
                if ( ( $expected instanceof $typeName ) === false ) {
102✔
204
                        throw new InvalidComparisonMethodException( $notAcceptableTypeError );
27✔
205
                }
206

207
                /*
208
                 * Execute the comparator method.
209
                 */
210
                $result = $actual->{$method}( $expected );
75✔
211

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

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

228
                if ( $message !== '' ) {
66✔
229
                        $msg = $message . \PHP_EOL . $msg;
9✔
230
                }
231

232
                static::assertTrue( $result, $msg );
66✔
233
        }
30✔
234
}
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