can_leave_off_default_constructor_argument()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
nc 1
nop 0
dl 0
loc 8
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Zenstruck\Foundry\Tests\Unit;
4
5
use PHPUnit\Framework\TestCase;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\TestCase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
7
use Zenstruck\Foundry\Instantiator;
8
9
/**
10
 * @author Kevin Bond <[email protected]>
11
 */
12
final class InstantiatorTest extends TestCase
13
{
14
    use ExpectDeprecationTrait;
15
16
    /**
17
     * @test
18
     */
19
    public function default_instantiate(): void
20
    {
21
        $object = (new Instantiator())([
22
            'propA' => 'A',
23
            'propB' => 'B',
24
            'propC' => 'C',
25
            'propD' => 'D',
26
        ], InstantiatorDummy::class);
27
28
        $this->assertSame('A', $object->propA);
29
        $this->assertSame('A', $object->getPropA());
30
        $this->assertSame('constructor B', $object->getPropB());
31
        $this->assertSame('constructor C', $object->getPropC());
32
        $this->assertSame('setter D', $object->getPropD());
33
    }
34
35
    /**
36
     * @test
37
     * @group legacy
38
     */
39
    public function can_use_snake_case_attributes(): void
40
    {
41
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using a differently cased attribute is deprecated, use the same case as the object property instead.');
42
43
        $object = (new Instantiator())([
44
            'prop_a' => 'A',
45
            'prop_b' => 'B',
46
            'prop_c' => 'C',
47
            'prop_d' => 'D',
48
        ], InstantiatorDummy::class);
49
50
        $this->assertSame('A', $object->propA);
51
        $this->assertSame('A', $object->getPropA());
52
        $this->assertSame('constructor B', $object->getPropB());
53
        $this->assertSame('constructor C', $object->getPropC());
54
        $this->assertSame('setter D', $object->getPropD());
55
    }
56
57
    /**
58
     * @test
59
     * @group legacy
60
     */
61
    public function can_use_kebab_case_attributes(): void
62
    {
63
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using a differently cased attribute is deprecated, use the same case as the object property instead.');
64
65
        $object = (new Instantiator())([
66
            'prop-a' => 'A',
67
            'prop-b' => 'B',
68
            'prop-c' => 'C',
69
            'prop-d' => 'D',
70
        ], InstantiatorDummy::class);
71
72
        $this->assertSame('A', $object->propA);
73
        $this->assertSame('A', $object->getPropA());
74
        $this->assertSame('constructor B', $object->getPropB());
75
        $this->assertSame('constructor C', $object->getPropC());
76
        $this->assertSame('setter D', $object->getPropD());
77
    }
78
79
    /**
80
     * @test
81
     */
82
    public function can_leave_off_default_constructor_argument(): void
83
    {
84
        $object = (new Instantiator())([
85
            'propB' => 'B',
86
        ], InstantiatorDummy::class);
87
88
        $this->assertSame('constructor B', $object->getPropB());
89
        $this->assertNull($object->getPropC());
90
    }
91
92
    /**
93
     * @test
94
     */
95
    public function can_instantiate_object_with_private_constructor(): void
96
    {
97
        $object = (new Instantiator())([
98
            'propA' => 'A',
99
            'propB' => 'B',
100
            'propC' => 'C',
101
            'propD' => 'D',
102
        ], PrivateConstructorInstantiatorDummy::class);
103
104
        $this->assertSame('A', $object->propA);
105
        $this->assertSame('A', $object->getPropA());
106
        $this->assertSame('setter B', $object->getPropB());
107
        $this->assertSame('setter C', $object->getPropC());
108
        $this->assertSame('setter D', $object->getPropD());
109
    }
110
111
    /**
112
     * @test
113
     */
114
    public function missing_constructor_argument_throws_exception(): void
115
    {
116
        $this->expectException(\InvalidArgumentException::class);
117
        $this->expectExceptionMessage('Missing constructor argument "propB" for "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy".');
118
119
        (new Instantiator())([], InstantiatorDummy::class);
120
    }
121
122
    /**
123
     * @test
124
     */
125
    public function extra_attributes_throws_exception(): void
126
    {
127
        $this->expectException(\InvalidArgumentException::class);
128
        $this->expectExceptionMessage('Cannot set attribute "extra" for object "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" (not public and no setter).');
129
130
        (new Instantiator())([
131
            'propB' => 'B',
132
            'extra' => 'foo',
133
        ], InstantiatorDummy::class);
134
    }
135
136
    /**
137
     * @test
138
     */
139
    public function can_set_attributes_that_should_be_optional(): void
140
    {
141
        $object = (new Instantiator())->allowExtraAttributes(['extra'])([
142
            'propB' => 'B',
143
            'extra' => 'foo',
144
        ], InstantiatorDummy::class);
145
146
        $this->assertSame('constructor B', $object->getPropB());
147
    }
148
149
    /**
150
     * @test
151
     */
152
    public function extra_attributes_not_defined_throws_exception(): void
153
    {
154
        $this->expectException(\InvalidArgumentException::class);
155
        $this->expectExceptionMessage('Cannot set attribute "extra2" for object "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" (not public and no setter).');
156
157
        (new Instantiator())->allowExtraAttributes(['extra1'])([
158
            'propB' => 'B',
159
            'extra1' => 'foo',
160
            'extra2' => 'bar',
161
        ], InstantiatorDummy::class);
162
    }
163
164
    /**
165
     * @test
166
     * @group legacy
167
     */
168
    public function can_prefix_extra_attribute_key_with_optional_to_avoid_exception(): void
169
    {
170
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "optional:" attribute prefixes is deprecated, use Instantiator::allowExtraAttributes() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).');
171
172
        $object = (new Instantiator())([
173
            'propB' => 'B',
174
            'optional:extra' => 'foo',
175
        ], InstantiatorDummy::class);
176
177
        $this->assertSame('constructor B', $object->getPropB());
178
    }
179
180
    /**
181
     * @test
182
     */
183
    public function can_always_allow_extra_attributes(): void
184
    {
185
        $object = (new Instantiator())->allowExtraAttributes()([
186
            'propB' => 'B',
187
            'extra' => 'foo',
188
        ], InstantiatorDummy::class);
189
190
        $this->assertSame('constructor B', $object->getPropB());
191
    }
192
193
    /**
194
     * @test
195
     */
196
    public function can_disable_constructor(): void
197
    {
198
        $object = (new Instantiator())->withoutConstructor()([
199
            'propA' => 'A',
200
            'propB' => 'B',
201
            'propC' => 'C',
202
            'propD' => 'D',
203
        ], InstantiatorDummy::class);
204
205
        $this->assertSame('A', $object->propA);
206
        $this->assertSame('A', $object->getPropA());
207
        $this->assertSame('setter B', $object->getPropB());
208
        $this->assertSame('setter C', $object->getPropC());
209
        $this->assertSame('setter D', $object->getPropD());
210
    }
211
212
    /**
213
     * @test
214
     */
215
    public function can_set_attributes_that_should_be_force_set(): void
216
    {
217
        $object = (new Instantiator())->withoutConstructor()->alwaysForceProperties(['propD'])([
218
            'propB' => 'B',
219
            'propD' => 'D',
220
        ], InstantiatorDummy::class);
221
222
        $this->assertSame('setter B', $object->getPropB());
223
        $this->assertSame('D', $object->getPropD());
224
    }
225
226
    /**
227
     * @test
228
     * @group legacy
229
     */
230
    public function prefixing_attribute_key_with_force_sets_the_property_directly(): void
231
    {
232
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "force:" property prefixes is deprecated, use Instantiator::alwaysForceProperties() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).');
233
234
        $object = (new Instantiator())([
235
            'propA' => 'A',
236
            'propB' => 'B',
237
            'propC' => 'C',
238
            'force:propD' => 'D',
239
        ], InstantiatorDummy::class);
240
241
        $this->assertSame('A', $object->propA);
242
        $this->assertSame('A', $object->getPropA());
243
        $this->assertSame('constructor B', $object->getPropB());
244
        $this->assertSame('constructor C', $object->getPropC());
245
        $this->assertSame('D', $object->getPropD());
246
    }
247
248
    /**
249
     * @test
250
     * @group legacy
251
     */
252
    public function prefixing_snake_case_attribute_key_with_force_sets_the_property_directly(): void
253
    {
254
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "force:" property prefixes is deprecated, use Instantiator::alwaysForceProperties() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).');
255
256
        $object = (new Instantiator())([
257
            'prop_a' => 'A',
258
            'prop_b' => 'B',
259
            'prop_c' => 'C',
260
            'force:prop_d' => 'D',
261
        ], InstantiatorDummy::class);
262
263
        $this->assertSame('A', $object->propA);
264
        $this->assertSame('A', $object->getPropA());
265
        $this->assertSame('constructor B', $object->getPropB());
266
        $this->assertSame('constructor C', $object->getPropC());
267
        $this->assertSame('D', $object->getPropD());
268
    }
269
270
    /**
271
     * @test
272
     * @group legacy
273
     */
274
    public function prefixing_kebab_case_attribute_key_with_force_sets_the_property_directly(): void
275
    {
276
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "force:" property prefixes is deprecated, use Instantiator::alwaysForceProperties() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).');
277
278
        $object = (new Instantiator())([
279
            'prop-a' => 'A',
280
            'prop-b' => 'B',
281
            'prop-c' => 'C',
282
            'force:prop-d' => 'D',
283
        ], InstantiatorDummy::class);
284
285
        $this->assertSame('A', $object->propA);
286
        $this->assertSame('A', $object->getPropA());
287
        $this->assertSame('constructor B', $object->getPropB());
288
        $this->assertSame('constructor C', $object->getPropC());
289
        $this->assertSame('D', $object->getPropD());
290
    }
291
292
    /**
293
     * @test
294
     * @group legacy
295
     */
296
    public function prefixing_invalid_attribute_key_with_force_throws_exception(): void
297
    {
298
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "force:" property prefixes is deprecated, use Instantiator::alwaysForceProperties() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).');
299
        $this->expectException(\InvalidArgumentException::class);
300
        $this->expectExceptionMessage('Class "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" does not have property "extra".');
301
302
        (new Instantiator())([
303
            'propB' => 'B',
304
            'force:extra' => 'foo',
305
        ], InstantiatorDummy::class);
306
    }
307
308
    /**
309
     * @test
310
     */
311
    public function can_use_force_set_and_get(): void
312
    {
313
        $object = new InstantiatorDummy('B');
314
315
        $this->assertNull(Instantiator::forceGet($object, 'propE'));
316
317
        Instantiator::forceSet($object, 'propE', 'value');
318
319
        $this->assertSame('value', Instantiator::forceGet($object, 'propE'));
320
    }
321
322
    /**
323
     * @test
324
     * @group legacy
325
     */
326
    public function can_use_force_set_and_get_with_kebab_and_snake_case(): void
327
    {
328
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using a differently cased attribute is deprecated, use the same case as the object property instead.');
329
330
        $object = new InstantiatorDummy('B');
331
332
        $this->assertNull(Instantiator::forceGet($object, 'propE'));
333
        $this->assertNull(Instantiator::forceGet($object, 'prop_e'));
334
        $this->assertNull(Instantiator::forceGet($object, 'prop-e'));
335
336
        Instantiator::forceSet($object, 'propE', 'camel');
337
338
        $this->assertSame('camel', Instantiator::forceGet($object, 'propE'));
339
        $this->assertSame('camel', Instantiator::forceGet($object, 'prop_e'));
340
        $this->assertSame('camel', Instantiator::forceGet($object, 'prop-e'));
341
342
        Instantiator::forceSet($object, 'prop_e', 'snake');
343
344
        $this->assertSame('snake', Instantiator::forceGet($object, 'propE'));
345
        $this->assertSame('snake', Instantiator::forceGet($object, 'prop_e'));
346
        $this->assertSame('snake', Instantiator::forceGet($object, 'prop-e'));
347
348
        Instantiator::forceSet($object, 'prop-e', 'kebab');
349
350
        $this->assertSame('kebab', Instantiator::forceGet($object, 'propE'));
351
        $this->assertSame('kebab', Instantiator::forceGet($object, 'prop_e'));
352
        $this->assertSame('kebab', Instantiator::forceGet($object, 'prop-e'));
353
    }
354
355
    /**
356
     * @test
357
     */
358
    public function force_set_throws_exception_for_invalid_property(): void
359
    {
360
        $this->expectException(\InvalidArgumentException::class);
361
        $this->expectExceptionMessage('Class "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" does not have property "invalid".');
362
363
        Instantiator::forceSet(new InstantiatorDummy('B'), 'invalid', 'value');
364
    }
365
366
    /**
367
     * @test
368
     */
369
    public function force_get_throws_exception_for_invalid_property(): void
370
    {
371
        $this->expectException(\InvalidArgumentException::class);
372
        $this->expectExceptionMessage('Class "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" does not have property "invalid".');
373
374
        Instantiator::forceGet(new InstantiatorDummy('B'), 'invalid');
375
    }
376
377
    /**
378
     * @test
379
     */
380
    public function can_use_always_force_mode(): void
381
    {
382
        $object = (new Instantiator())->alwaysForceProperties()([
383
            'propA' => 'A',
384
            'propB' => 'B',
385
            'propC' => 'C',
386
            'propD' => 'D',
387
        ], InstantiatorDummy::class);
388
389
        $this->assertSame('A', $object->propA);
390
        $this->assertSame('A', $object->getPropA());
391
        $this->assertSame('constructor B', $object->getPropB());
392
        $this->assertSame('constructor C', $object->getPropC());
393
        $this->assertSame('D', $object->getPropD());
394
    }
395
396
    /**
397
     * @test
398
     * @group legacy
399
     */
400
    public function can_use_always_force_mode_allows_snake_case(): void
401
    {
402
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using a differently cased attribute is deprecated, use the same case as the object property instead.');
403
404
        $object = (new Instantiator())->alwaysForceProperties()([
405
            'prop_a' => 'A',
406
            'prop_b' => 'B',
407
            'prop_c' => 'C',
408
            'prop_d' => 'D',
409
        ], InstantiatorDummy::class);
410
411
        $this->assertSame('A', $object->propA);
412
        $this->assertSame('A', $object->getPropA());
413
        $this->assertSame('constructor B', $object->getPropB());
414
        $this->assertSame('constructor C', $object->getPropC());
415
        $this->assertSame('D', $object->getPropD());
416
    }
417
418
    /**
419
     * @test
420
     * @group legacy
421
     */
422
    public function can_use_always_force_mode_allows_kebab_case(): void
423
    {
424
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using a differently cased attribute is deprecated, use the same case as the object property instead.');
425
426
        $object = (new Instantiator())->alwaysForceProperties()([
427
            'prop-a' => 'A',
428
            'prop-b' => 'B',
429
            'prop-c' => 'C',
430
            'prop-d' => 'D',
431
        ], InstantiatorDummy::class);
432
433
        $this->assertSame('A', $object->propA);
434
        $this->assertSame('A', $object->getPropA());
435
        $this->assertSame('constructor B', $object->getPropB());
436
        $this->assertSame('constructor C', $object->getPropC());
437
        $this->assertSame('D', $object->getPropD());
438
    }
439
440
    /**
441
     * @test
442
     */
443
    public function always_force_mode_throws_exception_for_extra_attributes(): void
444
    {
445
        $this->expectException(\InvalidArgumentException::class);
446
        $this->expectExceptionMessage('Class "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" does not have property "extra".');
447
448
        (new Instantiator())->alwaysForceProperties()([
449
            'propB' => 'B',
450
            'extra' => 'foo',
451
        ], InstantiatorDummy::class);
452
    }
453
454
    /**
455
     * @test
456
     * @group legacy
457
     */
458
    public function always_force_mode_allows_optional_attribute_name_prefix(): void
459
    {
460
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "optional:" attribute prefixes is deprecated, use Instantiator::allowExtraAttributes() instead (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#instantiation).');
461
462
        $object = (new Instantiator())->alwaysForceProperties()([
463
            'propB' => 'B',
464
            'propD' => 'D',
465
            'optional:extra' => 'foo',
466
        ], InstantiatorDummy::class);
467
468
        $this->assertSame('D', $object->getPropD());
469
    }
470
471
    /**
472
     * @test
473
     */
474
    public function always_force_mode_with_allow_extra_attributes_mode(): void
475
    {
476
        $object = (new Instantiator())->allowExtraAttributes()->alwaysForceProperties()([
477
            'propB' => 'B',
478
            'propD' => 'D',
479
            'extra' => 'foo',
480
        ], InstantiatorDummy::class);
481
482
        $this->assertSame('D', $object->getPropD());
483
    }
484
485
    /**
486
     * @test
487
     */
488
    public function always_force_mode_can_set_parent_class_properties(): void
489
    {
490
        $object = (new Instantiator())->alwaysForceProperties()([
491
            'propA' => 'A',
492
            'propB' => 'B',
493
            'propC' => 'C',
494
            'propD' => 'D',
495
            'propE' => 'E',
496
        ], ExtendedInstantiatorDummy::class);
497
498
        $this->assertSame('A', $object->propA);
499
        $this->assertSame('A', $object->getPropA());
500
        $this->assertSame('constructor B', $object->getPropB());
501
        $this->assertSame('constructor C', $object->getPropC());
502
        $this->assertSame('D', $object->getPropD());
503
        $this->assertSame('E', Instantiator::forceGet($object, 'propE'));
504
    }
505
506
    /**
507
     * @test
508
     */
509
    public function invalid_attribute_type_with_allow_extra_enabled_throws_exception(): void
510
    {
511
        $this->expectException(\Throwable::class);
512
513
        (new Instantiator())->allowExtraAttributes()([
514
            'propB' => 'B',
515
            'propF' => 'F',
516
        ], InstantiatorDummy::class);
517
    }
518
}
519
520
class InstantiatorDummy
521
{
522
    public $propA;
523
    public $propD;
524
    private $propB;
525
    private $propC;
526
    private $propE;
0 ignored issues
show
introduced by
The private property $propE is not used, and could be removed.
Loading history...
527
    private $propF;
528
529
    public function __construct($propB, $propC = null)
530
    {
531
        $this->propB = 'constructor '.$propB;
532
533
        if ($propC) {
534
            $this->propC = 'constructor '.$propC;
535
        }
536
    }
537
538
    public function getPropA()
539
    {
540
        return $this->propA;
541
    }
542
543
    public function getPropB()
544
    {
545
        return $this->propB;
546
    }
547
548
    public function setPropB($propB)
549
    {
550
        $this->propB = 'setter '.$propB;
551
    }
552
553
    public function getPropC()
554
    {
555
        return $this->propC;
556
    }
557
558
    public function setPropC($propC)
559
    {
560
        $this->propC = 'setter '.$propC;
561
    }
562
563
    public function getPropD()
564
    {
565
        return $this->propD;
566
    }
567
568
    public function setPropD($propD)
569
    {
570
        $this->propD = 'setter '.$propD;
571
    }
572
573
    public function setPropF(object $propF)
574
    {
575
        $this->propF = $propF;
576
    }
577
}
578
579
class ExtendedInstantiatorDummy extends InstantiatorDummy
580
{
581
}
582
583
class PrivateConstructorInstantiatorDummy extends InstantiatorDummy
584
{
585
    private function __construct()
586
    {
587
        parent::__construct('B', 'C');
588
    }
589
}
590