Issues (158)

tests/Unit/DumperTest.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Debug\Tests\Unit;
6
7
use DateTimeZone;
8
use PHPUnit\Framework\TestCase;
9
use stdClass;
10
use Yiisoft\Yii\Debug as D;
11
use Yiisoft\Yii\Debug\Dumper;
12
use Yiisoft\Yii\Debug\Tests\Support\Stub\ThreeProperties;
13
14
use function socket_create;
15
16
use const AF_INET;
17
use const SOCK_STREAM;
18
use const SOL_TCP;
19
20
final class DumperTest extends TestCase
21
{
22
    /**
23
     * @dataProvider asJsonObjectMapDataProvider
24
     */
25
    public function testAsJsonObjectsMap(mixed $var, $expectedResult): void
26
    {
27
        $exportResult = Dumper::create($var)->asJsonObjectsMap();
28
        $this->assertEquals($expectedResult, $exportResult);
29
    }
30
31
    public static function asJsonObjectMapDataProvider(): iterable
32
    {
33
        $user = new stdClass();
34
        $user->id = 1;
35
        $objectId = spl_object_id($user);
36
37
        yield 'flat std class' => [
38
            $user,
39
            <<<S
40
            {"stdClass#{$objectId}":{"public \$id":1}}
41
            S,
42
        ];
43
44
        $decoratedUser = clone $user;
45
        $decoratedUser->name = 'Name';
46
        $decoratedUser->originalUser = $user;
47
        $decoratedObjectId = spl_object_id($decoratedUser);
48
49
        yield 'nested std class' => [
50
            $decoratedUser,
51
            <<<S
52
            {"stdClass#{$decoratedObjectId}":{"public \$id":1,"public \$name":"Name","public \$originalUser":"object@stdClass#{$objectId}"},"stdClass#{$objectId}":{"public \$id":1}}
53
            S,
54
        ];
55
56
        $closureInsideObject = new stdClass();
57
        $closureObject = fn () => true;
58
        $closureObjectId = spl_object_id($closureObject);
59
        $closureInsideObject->closure = $closureObject;
60
        $closureInsideObjectId = spl_object_id($closureInsideObject);
61
62
        yield 'closure inside std class' => [
63
            $closureInsideObject,
64
            <<<S
65
            {"stdClass#{$closureInsideObjectId}":{"public \$closure":"fn () => true"},"Closure#{$closureObjectId}":"fn () => true"}
66
            S,
67
        ];
68
    }
69
70
    /**
71
     * @dataProvider jsonDataProvider()
72
     */
73
    public function testAsJson($variable, string $result): void
74
    {
75
        $output = Dumper::create($variable)->asJson();
76
        $this->assertEqualsWithoutLE($result, $output);
77
    }
78
79
    public function testCacheDoesNotCoversObjectOutOfDumpDepth(): void
80
    {
81
        $object1 = new stdClass();
82
        $object1Id = spl_object_id($object1);
83
        $object2 = new stdClass();
84
85
        $variable = [$object1, [[$object2]]];
86
        $expectedResult = sprintf('["object@stdClass#%d",["array (1 item) [...]"]]', $object1Id);
87
88
        $dumper = Dumper::create($variable);
89
        $actualResult = $dumper->asJson(2);
90
        $this->assertEqualsWithoutLE($expectedResult, $actualResult);
91
92
        $map = $dumper->asJsonObjectsMap(2);
93
        $this->assertEqualsWithoutLE(
94
            <<<S
95
            {"stdClass#{$object1Id}":"{stateless object}"}
96
            S,
97
            $map,
98
        );
99
    }
100
101
    public function testDepthLimitInObjectMap(): void
102
    {
103
        $variable = [1, []];
104
        $expectedResult = '"array (2 items) [...]"';
105
106
        $dumper = Dumper::create($variable);
107
        $actualResult = $dumper->asJson(0);
108
        $this->assertEqualsWithoutLE($expectedResult, $actualResult);
109
110
        $map = $dumper->asJsonObjectsMap(0);
111
        $this->assertEqualsWithoutLE('[]', $map);
112
    }
113
114
    public function testObjectProvidesDebugInfoMethod(): void
115
    {
116
        $variable = new class () {
117
            public function __debugInfo(): array
118
            {
119
                return ['test' => 'ok'];
120
            }
121
        };
122
        $expectedResult = sprintf(
123
            '{"class@anonymous#%d":{"public $test":"ok"}}',
124
            spl_object_id($variable),
125
        );
126
127
        $dumper = Dumper::create($variable);
128
        $actualResult = $dumper->asJson(2);
129
        $this->assertEqualsWithoutLE($expectedResult, $actualResult);
130
131
        $map = $dumper->asJsonObjectsMap(2);
132
        $this->assertEqualsWithoutLE($expectedResult, $map);
133
    }
134
135
    public function testStatelessObjectInlined(): void
136
    {
137
        $statelessObject = new stdClass();
138
        $statelessObjectId = spl_object_id($statelessObject);
139
140
        $statefulObject = new stdClass();
141
        $statefulObject->id = 1;
142
        $statefulObjectId = spl_object_id($statefulObject);
143
144
        $variable = [$statelessObject, [$statefulObject]];
145
        $expectedResult = sprintf(
146
            '["object@stdClass#%d",["stdClass#%d (...)"]]',
147
            $statelessObjectId,
148
            $statefulObjectId
149
        );
150
151
        $dumper = Dumper::create($variable);
152
        $actualResult = $dumper->asJson(2);
153
        $this->assertEqualsWithoutLE($expectedResult, $actualResult);
154
155
        $map = $dumper->asJsonObjectsMap(3);
156
        $this->assertEqualsWithoutLE(
157
            <<<S
158
            {"stdClass#{$statelessObjectId}":"{stateless object}","stdClass#{$statefulObjectId}":{"public \$id":1}}
159
            S,
160
            $map,
161
        );
162
    }
163
164
    /**
165
     * @dataProvider dataDeepNestedArray
166
     */
167
    public function testDeepNestedArray(array $variable, string $expectedResult): void
168
    {
169
        $actualResult = Dumper::create($variable)->asJson(2);
170
        $this->assertEqualsWithoutLE($expectedResult, $actualResult);
171
    }
172
173
    public static function dataDeepNestedArray(): iterable
174
    {
175
        yield 'singular' => [
176
            [[['test']]],
177
            '[["array (1 item) [...]"]]',
178
        ];
179
180
        yield 'plural' => [
181
            [[['test', 'test'], ['test']]],
182
            '[["array (2 items) [...]","array (1 item) [...]"]]',
183
        ];
184
    }
185
186
    public function testDeepNestedObject(): void
187
    {
188
        $object = new ThreeProperties();
189
        $object->first = $object;
190
        $variable = [[$object]];
191
192
        $output = Dumper::create($variable)->asJson(2);
193
        $result = sprintf(
194
            '[["%s#%d (...)"]]',
195
            str_replace('\\', '\\\\', ThreeProperties::class),
196
            spl_object_id($object),
197
        );
198
        $this->assertEqualsWithoutLE($result, $output);
199
    }
200
201
    public function testObjectVisibilityProperties(): void
202
    {
203
        $variable = new ThreeProperties();
204
205
        $output = Dumper::create($variable)->asJson(2);
206
        $result = sprintf(
207
            '{"%s#%d":{"public $first":"first","protected $second":"second","private $third":"third"}}',
208
            str_replace('\\', '\\\\', ThreeProperties::class),
209
            spl_object_id($variable),
210
        );
211
        $this->assertEqualsWithoutLE($result, $output);
212
    }
213
214
    public function testFormatJson(): void
215
    {
216
        $variable = [['test']];
217
218
        $output = Dumper::create($variable)->asJson(2, true);
219
        $result = <<<S
220
        [
221
            [
222
                "test"
223
            ]
224
        ]
225
        S;
226
        $this->assertEqualsWithoutLE($result, $output);
227
    }
228
229
    public function testExcludedClasses(): void
230
    {
231
        $object1 = new stdClass();
232
        $object1Id = spl_object_id($object1);
233
        $object1Class = $object1::class;
234
235
        $object2 = new DateTimeZone('UTC');
236
        $object2Id = spl_object_id($object2);
237
        $object2Class = $object2::class;
238
239
        $actualResult = Dumper::create([$object1, $object2], [$object1Class])->asJson(2, true);
240
        $expectedResult = <<<S
241
        [
242
            "{$object1Class}#{$object1Id} (...)",
243
            "object@{$object2Class}#{$object2Id}"
244
        ]
245
        S;
246
247
        $this->assertEqualsWithoutLE($expectedResult, $actualResult);
248
    }
249
250
    public static function jsonDataProvider(): iterable
251
    {
252
        $emptyObject = new stdClass();
253
        $emptyObjectId = spl_object_id($emptyObject);
254
255
        yield 'empty object' => [
256
            $emptyObject,
257
            <<<S
258
                {"stdClass#{$emptyObjectId}":"{stateless object}"}
259
                S,
260
        ];
261
262
        // @formatter:off
263
        $shortFunctionObject = fn () => 1;
264
        // @formatter:on
265
        $shortFunctionObjectId = spl_object_id($shortFunctionObject);
266
267
        yield 'short function' => [
268
            $shortFunctionObject,
269
            <<<S
270
                {"Closure#{$shortFunctionObjectId}":"fn () => 1"}
271
                S,
272
        ];
273
274
        // @formatter:off
275
        $staticShortFunctionObject = static fn () => 1;
276
        // @formatter:on
277
        $staticShortFunctionObjectId = spl_object_id($staticShortFunctionObject);
278
279
        yield 'short static function' => [
280
            $staticShortFunctionObject,
281
            <<<S
282
                {"Closure#{$staticShortFunctionObjectId}":"static fn () => 1"}
283
                S,
284
        ];
285
286
        // @formatter:off
287
        $functionObject = function () {
288
            return 1;
289
        };
290
        // @formatter:on
291
        $functionObjectId = spl_object_id($functionObject);
292
293
        yield 'function' => [
294
            $functionObject,
295
            <<<S
296
                {"Closure#{$functionObjectId}":"function () {\\n    return 1;\\n}"}
297
                S,
298
        ];
299
300
        // @formatter:off
301
        $staticFunctionObject = static function () {
302
            return 1;
303
        };
304
        // @formatter:on
305
        $staticFunctionObjectId = spl_object_id($staticFunctionObject);
306
307
        yield 'static function' => [
308
            $staticFunctionObject,
309
            <<<S
310
                {"Closure#{$staticFunctionObjectId}":"static function () {\\n    return 1;\\n}"}
311
                S,
312
        ];
313
        yield 'string' => [
314
            'Hello, Yii!',
315
            '"Hello, Yii!"',
316
        ];
317
        yield 'empty string' => [
318
            '',
319
            '""',
320
        ];
321
        yield 'null' => [
322
            null,
323
            'null',
324
        ];
325
        yield 'integer' => [
326
            1,
327
            '1',
328
        ];
329
        yield 'integer with separator' => [
330
            1_23_456,
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_STRING, expecting ',' or ']' on line 330 at column 13
Loading history...
331
            '123456',
332
        ];
333
        yield 'boolean' => [
334
            true,
335
            'true',
336
        ];
337
        yield 'fileResource' => [
338
            fopen('php://input', 'rb'),
339
            '{"timed_out":false,"blocked":true,"eof":false,"wrapper_type":"PHP","stream_type":"Input","mode":"rb","unread_bytes":0,"seekable":true,"uri":"php:\/\/input"}',
340
        ];
341
        yield 'empty array' => [
342
            [],
343
            '[]',
344
        ];
345
        yield 'array of 3 elements, automatic keys' => [
346
            [
347
                'one',
348
                'two',
349
                'three',
350
            ],
351
            '["one","two","three"]',
352
        ];
353
        yield 'array of 3 elements, custom keys' => [
354
            [
355
                2 => 'one',
356
                'two' => 'two',
357
                0 => 'three',
358
            ],
359
            '{"2":"one","two":"two","0":"three"}',
360
        ];
361
362
        // @formatter:off
363
        $closureInArrayObject = fn () => new \DateTimeZone('');
364
        // @formatter:on
365
        $closureInArrayObjectId = spl_object_id($closureInArrayObject);
366
367
        yield 'closure in array' => [
368
            // @formatter:off
369
            [$closureInArrayObject],
370
            // @formatter:on
371
            <<<S
372
                [{"Closure#{$closureInArrayObjectId}":"fn () => new \\\DateTimeZone('')"}]
373
                S,
374
        ];
375
376
        // @formatter:off
377
        $closureWithUsualClassNameObject = fn (Dumper $date) => new \DateTimeZone('');
378
        // @formatter:on
379
        $closureWithUsualClassNameObjectId = spl_object_id($closureWithUsualClassNameObject);
380
381
        yield 'original class name' => [
382
            $closureWithUsualClassNameObject,
383
            <<<S
384
                {"Closure#{$closureWithUsualClassNameObjectId}":"fn (\\\Yiisoft\\\Yii\\\Debug\\\Dumper \$date) => new \\\DateTimeZone('')"}
385
                S,
386
        ];
387
388
        // @formatter:off
389
        $closureWithAliasedClassNameObject = fn (Dumper $date) => new \DateTimeZone('');
390
        // @formatter:on
391
        $closureWithAliasedClassNameObjectId = spl_object_id($closureWithAliasedClassNameObject);
392
393
        yield 'class alias' => [
394
            $closureWithAliasedClassNameObject,
395
            <<<S
396
                {"Closure#{$closureWithAliasedClassNameObjectId}":"fn (\\\Yiisoft\\\Yii\\\Debug\\\Dumper \$date) => new \\\DateTimeZone('')"}
397
                S,
398
        ];
399
400
        // @formatter:off
401
        $closureWithAliasedNamespaceObject = fn (D\Dumper $date) => new \DateTimeZone('');
402
        // @formatter:on
403
        $closureWithAliasedNamespaceObjectId = spl_object_id($closureWithAliasedNamespaceObject);
404
405
        yield 'namespace alias' => [
406
            $closureWithAliasedNamespaceObject,
407
            <<<S
408
                {"Closure#{$closureWithAliasedNamespaceObjectId}":"fn (\\\Yiisoft\\\Yii\\\Debug\\\Dumper \$date) => new \\\DateTimeZone('')"}
409
                S,
410
        ];
411
        // @formatter:off
412
        $closureWithNullCollisionOperatorObject = fn () => $_ENV['var'] ?? null;
413
        // @formatter:on
414
        $closureWithNullCollisionOperatorObjectId = spl_object_id($closureWithNullCollisionOperatorObject);
415
416
        yield 'closure with null-collision operator' => [
417
            $closureWithNullCollisionOperatorObject,
418
            <<<S
419
                {"Closure#{$closureWithNullCollisionOperatorObjectId}":"fn () => \$_ENV['var'] ?? null"}
420
                S,
421
        ];
422
        yield 'utf8 supported' => [
423
            '🤣',
424
            '"🤣"',
425
        ];
426
427
428
        $objectWithClosureInProperty = new stdClass();
429
        // @formatter:off
430
        $objectWithClosureInProperty->a = fn () => 1;
431
        // @formatter:on
432
        $objectWithClosureInPropertyId = spl_object_id($objectWithClosureInProperty);
433
        $objectWithClosureInPropertyClosureId = spl_object_id($objectWithClosureInProperty->a);
434
435
        yield 'closure in property supported' => [
436
            $objectWithClosureInProperty,
437
            <<<S
438
                {"stdClass#{$objectWithClosureInPropertyId}":{"public \$a":{"Closure#{$objectWithClosureInPropertyClosureId}":"fn () => 1"}}}
439
                S,
440
        ];
441
        yield 'binary string' => [
442
            pack('H*', md5('binary string')),
443
            '"ɍ��^��\u00191\u0017�]�-f�"',
444
        ];
445
446
447
        $fileResource = tmpfile();
448
        $fileResourceUri = stream_get_meta_data($fileResource)['uri'];
449
        $fileResourceUri = addcslashes($fileResourceUri, '/\\');
450
451
        yield 'file resource' => [
452
            $fileResource,
453
            <<<S
454
                {"timed_out":false,"blocked":true,"eof":false,"wrapper_type":"plainfile","stream_type":"STDIO","mode":"r+b","unread_bytes":0,"seekable":true,"uri":"{$fileResourceUri}"}
455
                S,
456
        ];
457
458
        $closedFileResource = tmpfile();
459
        fclose($closedFileResource);
460
461
        yield 'closed file resource' => [
462
            $closedFileResource,
463
            '"{closed resource}"',
464
        ];
465
466
        $socketResource = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
467
        $socketResourceId = spl_object_id($socketResource);
468
        yield 'socket resource' => [
469
            $socketResource,
470
            <<<S
471
            {"Socket#{$socketResourceId}":"{stateless object}"}
472
            S,
473
        ];
474
475
        $opendirResource = opendir(sys_get_temp_dir());
476
477
        yield 'opendir resource' => [
478
            $opendirResource,
479
            <<<S
480
                {"timed_out":false,"blocked":true,"eof":false,"wrapper_type":"plainfile","stream_type":"dir","mode":"r","unread_bytes":0,"seekable":true}
481
                S,
482
        ];
483
484
        $curlResource = curl_init('https://example.com');
485
        $curlResourceObjectId = spl_object_id($curlResource);
486
487
        yield 'curl resource' => [
488
            $curlResource,
489
            <<<S
490
                {"CurlHandle#{$curlResourceObjectId}":"{stateless object}"}
491
                S,
492
        ];
493
        yield 'stdout' => [
494
            STDOUT,
495
            <<<S
496
                {"timed_out":false,"blocked":true,"eof":false,"wrapper_type":"PHP","stream_type":"STDIO","mode":"wb","unread_bytes":0,"seekable":false,"uri":"php:\/\/stdout"}
497
                S,
498
        ];
499
        yield 'stderr' => [
500
            STDERR,
501
            <<<S
502
                {"timed_out":false,"blocked":true,"eof":false,"wrapper_type":"PHP","stream_type":"STDIO","mode":"wb","unread_bytes":0,"seekable":false,"uri":"php:\/\/stderr"}
503
                S,
504
        ];
505
        yield 'stdin' => [
506
            STDIN,
507
            <<<S
508
                {"timed_out":false,"blocked":true,"eof":false,"wrapper_type":"PHP","stream_type":"STDIO","mode":"rb","unread_bytes":0,"seekable":false,"uri":"php:\/\/stdin"}
509
                S,
510
        ];
511
    }
512
513
    /**
514
     * Asserting two strings equality ignoring line endings.
515
     */
516
    protected function assertEqualsWithoutLE(string $expected, string $actual, string $message = ''): void
517
    {
518
        $expected = str_replace(["\r\n", '\r\n'], ["\n", '\n'], $expected);
519
        $actual = str_replace(["\r\n", '\r\n'], ["\n", '\n'], $actual);
520
        $this->assertEquals($expected, $actual, $message);
521
    }
522
}
523