Passed
Pull Request — master (#81)
by Kevin
18:14
created

extra_attributes_not_defined_throws_exception()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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