Completed
Push — 2.8.x ( 60c486...31f4dd )
by Benjamin
13:21 queued 06:28
created

ValueObjectsTest::testEmbeddableIsNotTransient()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\ORM\Functional;
4
use Doctrine\Common\Reflection\RuntimePublicReflectionProperty;
5
use Doctrine\ORM\Mapping\MappingException;
6
use Doctrine\ORM\Mapping\ReflectionEmbeddedProperty;
7
use Doctrine\ORM\Query\QueryException;
8
use Doctrine\Tests\OrmFunctionalTestCase;
9
10
/**
11
 * @group DDC-93
12
 */
13
class ValueObjectsTest extends OrmFunctionalTestCase
14
{
15
    public function setUp()
16
    {
17
        parent::setUp();
18
19
        try {
20
            $this->_schemaTool->createSchema(
21
                [
22
                $this->_em->getClassMetadata(DDC93Person::class),
23
                $this->_em->getClassMetadata(DDC93Address::class),
24
                $this->_em->getClassMetadata(DDC93Vehicle::class),
25
                $this->_em->getClassMetadata(DDC93Car::class),
26
                $this->_em->getClassMetadata(DDC3027Animal::class),
27
                $this->_em->getClassMetadata(DDC3027Dog::class),
28
                ]
29
            );
30
        } catch(\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
31
        }
32
    }
33
34
    public function testMetadataHasReflectionEmbeddablesAccessible()
35
    {
36
        $classMetadata = $this->_em->getClassMetadata(DDC93Person::class);
37
38
        $this->assertInstanceOf(RuntimePublicReflectionProperty::class, $classMetadata->getReflectionProperty('address'));
39
        $this->assertInstanceOf(ReflectionEmbeddedProperty::class, $classMetadata->getReflectionProperty('address.street'));
40
    }
41
42
    public function testCRUD()
43
    {
44
        $person = new DDC93Person();
45
        $person->name = "Tara";
46
        $person->address = new DDC93Address();
47
        $person->address->street = "United States of Tara Street";
48
        $person->address->zip = "12345";
49
        $person->address->city = "funkytown";
50
        $person->address->country = new DDC93Country('Germany');
51
52
        // 1. check saving value objects works
53
        $this->_em->persist($person);
54
        $this->_em->flush();
55
56
        $this->_em->clear();
57
58
        // 2. check loading value objects works
59
        $person = $this->_em->find(DDC93Person::class, $person->id);
60
61
        $this->assertInstanceOf(DDC93Address::class, $person->address);
62
        $this->assertEquals('United States of Tara Street', $person->address->street);
63
        $this->assertEquals('12345', $person->address->zip);
64
        $this->assertEquals('funkytown', $person->address->city);
65
        $this->assertInstanceOf(DDC93Country::class, $person->address->country);
66
        $this->assertEquals('Germany', $person->address->country->name);
67
68
        // 3. check changing value objects works
69
        $person->address->street = "Street";
70
        $person->address->zip = "54321";
71
        $person->address->city = "another town";
72
        $person->address->country->name = "United States of America";
73
        $this->_em->flush();
74
75
        $this->_em->clear();
76
77
        $person = $this->_em->find(DDC93Person::class, $person->id);
78
79
        $this->assertEquals('Street', $person->address->street);
80
        $this->assertEquals('54321', $person->address->zip);
81
        $this->assertEquals('another town', $person->address->city);
82
        $this->assertEquals('United States of America', $person->address->country->name);
83
84
        // 4. check deleting works
85
        $personId = $person->id;;
86
        $this->_em->remove($person);
87
        $this->_em->flush();
88
89
        $this->assertNull($this->_em->find(DDC93Person::class, $personId));
90
    }
91
92
    public function testLoadDql()
93
    {
94
        for ($i = 0; $i < 3; $i++) {
95
            $person = new DDC93Person();
96
            $person->name = "Donkey Kong$i";
97
            $person->address = new DDC93Address();
98
            $person->address->street = "Tree";
99
            $person->address->zip = "12345";
100
            $person->address->city = "funkytown";
101
            $person->address->country = new DDC93Country('United States of America');
102
103
            $this->_em->persist($person);
104
        }
105
106
        $this->_em->flush();
107
        $this->_em->clear();
108
109
        $dql = "SELECT p FROM " . __NAMESPACE__ . "\DDC93Person p";
110
        $persons = $this->_em->createQuery($dql)->getResult();
111
112
        $this->assertCount(3, $persons);
113
        foreach ($persons as $person) {
114
            $this->assertInstanceOf(DDC93Address::class, $person->address);
115
            $this->assertEquals('Tree', $person->address->street);
116
            $this->assertEquals('12345', $person->address->zip);
117
            $this->assertEquals('funkytown', $person->address->city);
118
            $this->assertInstanceOf(DDC93Country::class, $person->address->country);
119
            $this->assertEquals('United States of America', $person->address->country->name);
120
        }
121
122
        $dql = "SELECT p FROM " . __NAMESPACE__ . "\DDC93Person p";
123
        $persons = $this->_em->createQuery($dql)->getArrayResult();
124
125
        foreach ($persons as $person) {
126
            $this->assertEquals('Tree', $person['address.street']);
127
            $this->assertEquals('12345', $person['address.zip']);
128
            $this->assertEquals('funkytown', $person['address.city']);
129
            $this->assertEquals('United States of America', $person['address.country.name']);
130
        }
131
    }
132
133
    /**
134
     * @group dql
135
     */
136
    public function testDqlOnEmbeddedObjectsField()
137
    {
138
        if ($this->isSecondLevelCacheEnabled) {
139
            $this->markTestSkipped('SLC does not work with UPDATE/DELETE queries through EM.');
140
        }
141
142
        $person = new DDC93Person('Johannes', new DDC93Address('Moo', '12345', 'Karlsruhe', new DDC93Country('Germany')));
143
        $this->_em->persist($person);
144
        $this->_em->flush();
145
146
        // SELECT
147
        $selectDql = "SELECT p FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.address.city = :city AND p.address.country.name = :country";
148
        $loadedPerson = $this->_em->createQuery($selectDql)
149
            ->setParameter('city', 'Karlsruhe')
150
            ->setParameter('country', 'Germany')
151
            ->getSingleResult();
152
        $this->assertEquals($person, $loadedPerson);
153
154
        $this->assertNull(
155
            $this->_em->createQuery($selectDql)
156
                ->setParameter('city', 'asdf')
157
                ->setParameter('country', 'Germany')
158
                ->getOneOrNullResult()
159
        );
160
161
        // UPDATE
162
        $updateDql = "UPDATE " . __NAMESPACE__ . "\\DDC93Person p SET p.address.street = :street, p.address.country.name = :country WHERE p.address.city = :city";
163
        $this->_em->createQuery($updateDql)
164
            ->setParameter('street', 'Boo')
165
            ->setParameter('country', 'DE')
166
            ->setParameter('city', 'Karlsruhe')
167
            ->execute();
168
169
        $this->_em->refresh($person);
170
        $this->assertEquals('Boo', $person->address->street);
171
        $this->assertEquals('DE', $person->address->country->name);
172
173
        // DELETE
174
        $this->_em->createQuery("DELETE " . __NAMESPACE__ . "\\DDC93Person p WHERE p.address.city = :city AND p.address.country.name = :country")
175
            ->setParameter('city', 'Karlsruhe')
176
            ->setParameter('country', 'DE')
177
            ->execute();
178
179
        $this->_em->clear();
180
        $this->assertNull($this->_em->find(DDC93Person::class, $person->id));
181
    }
182
183
    public function testPartialDqlOnEmbeddedObjectsField()
184
    {
185
        $person = new DDC93Person('Karl', new DDC93Address('Foo', '12345', 'Gosport', new DDC93Country('England')));
186
        $this->_em->persist($person);
187
        $this->_em->flush();
188
        $this->_em->clear();
189
190
        // Prove that the entity was persisted correctly.
191
        $dql = "SELECT p FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.name = :name";
192
193
        $person = $this->_em->createQuery($dql)
194
            ->setParameter('name', 'Karl')
195
            ->getSingleResult();
196
197
        $this->assertEquals('Gosport', $person->address->city);
198
        $this->assertEquals('Foo', $person->address->street);
199
        $this->assertEquals('12345', $person->address->zip);
200
        $this->assertEquals('England', $person->address->country->name);
201
202
        // Clear the EM and prove that the embeddable can be the subject of a partial query.
203
        $this->_em->clear();
204
205
        $dql = "SELECT PARTIAL p.{id,address.city} FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.name = :name";
206
207
        $person = $this->_em->createQuery($dql)
208
            ->setParameter('name', 'Karl')
209
            ->getSingleResult();
210
211
        // Selected field must be equal, all other fields must be null.
212
        $this->assertEquals('Gosport', $person->address->city);
213
        $this->assertNull($person->address->street);
214
        $this->assertNull($person->address->zip);
215
        $this->assertNull($person->address->country);
216
        $this->assertNull($person->name);
217
218
        // Clear the EM and prove that the embeddable can be the subject of a partial query regardless of attributes positions.
219
        $this->_em->clear();
220
221
        $dql = "SELECT PARTIAL p.{address.city, id} FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.name = :name";
222
223
        $person = $this->_em->createQuery($dql)
224
            ->setParameter('name', 'Karl')
225
            ->getSingleResult();
226
227
        // Selected field must be equal, all other fields must be null.
228
        $this->assertEquals('Gosport', $person->address->city);
229
        $this->assertNull($person->address->street);
230
        $this->assertNull($person->address->zip);
231
        $this->assertNull($person->address->country);
232
        $this->assertNull($person->name);
233
    }
234
235
    public function testDqlWithNonExistentEmbeddableField()
236
    {
237
        $this->expectException(QueryException::class);
238
        $this->expectExceptionMessage('no field or association named address.asdfasdf');
239
240
        $this->_em->createQuery("SELECT p FROM " . __NAMESPACE__ . "\\DDC93Person p WHERE p.address.asdfasdf IS NULL")
241
            ->execute();
242
    }
243
244
    public function testPartialDqlWithNonExistentEmbeddableField()
245
    {
246
        $this->expectException(QueryException::class);
247
        $this->expectExceptionMessage("no mapped field named 'address.asdfasdf'");
248
249
        $this->_em->createQuery("SELECT PARTIAL p.{id,address.asdfasdf} FROM " . __NAMESPACE__ . "\\DDC93Person p")
250
            ->execute();
251
    }
252
253
    public function testEmbeddableWithInheritance()
254
    {
255
        $car = new DDC93Car(new DDC93Address('Foo', '12345', 'Asdf'));
256
        $this->_em->persist($car);
257
        $this->_em->flush();
258
259
        $reloadedCar = $this->_em->find(DDC93Car::class, $car->id);
260
        $this->assertEquals($car, $reloadedCar);
261
    }
262
263
    public function testInlineEmbeddableWithPrefix()
264
    {
265
        $metadata = $this->_em->getClassMetadata(DDC3028PersonWithPrefix::class);
266
267
        $this->assertEquals('foobar_id', $metadata->getColumnName('id.id'));
268
        $this->assertEquals('bloo_foo_id', $metadata->getColumnName('nested.nestedWithPrefix.id'));
269
        $this->assertEquals('bloo_nestedWithEmptyPrefix_id', $metadata->getColumnName('nested.nestedWithEmptyPrefix.id'));
270
        $this->assertEquals('bloo_id', $metadata->getColumnName('nested.nestedWithPrefixFalse.id'));
271
    }
272
273
    public function testInlineEmbeddableEmptyPrefix()
274
    {
275
        $metadata = $this->_em->getClassMetadata(DDC3028PersonEmptyPrefix::class);
276
277
        $this->assertEquals('id_id', $metadata->getColumnName('id.id'));
278
        $this->assertEquals('nested_foo_id', $metadata->getColumnName('nested.nestedWithPrefix.id'));
279
        $this->assertEquals('nested_nestedWithEmptyPrefix_id', $metadata->getColumnName('nested.nestedWithEmptyPrefix.id'));
280
        $this->assertEquals('nested_id', $metadata->getColumnName('nested.nestedWithPrefixFalse.id'));
281
    }
282
283
    public function testInlineEmbeddablePrefixFalse()
284
    {
285
        $expectedColumnName = 'id';
286
287
        $actualColumnName = $this->_em
288
            ->getClassMetadata(DDC3028PersonPrefixFalse::class)
289
            ->getColumnName('id.id');
290
291
        $this->assertEquals($expectedColumnName, $actualColumnName);
292
    }
293
294
    public function testInlineEmbeddableInMappedSuperClass()
295
    {
296
        $isFieldMapped = $this->_em
297
            ->getClassMetadata(DDC3027Dog::class)
298
            ->hasField('address.street');
299
300
        $this->assertTrue($isFieldMapped);
301
    }
302
303
    /**
304
     * @dataProvider getInfiniteEmbeddableNestingData
305
     */
306
    public function testThrowsExceptionOnInfiniteEmbeddableNesting($embeddableClassName, $declaredEmbeddableClassName)
307
    {
308
        $this->expectException(MappingException::class);
309
        $this->expectExceptionMessage(
310
            sprintf(
311
                'Infinite nesting detected for embedded property %s::nested. ' .
312
                'You cannot embed an embeddable from the same type inside an embeddable.',
313
                __NAMESPACE__ . '\\' . $declaredEmbeddableClassName
314
            )
315
        );
316
317
        $this->_schemaTool->createSchema(
318
            [
319
            $this->_em->getClassMetadata(__NAMESPACE__ . '\\' . $embeddableClassName),
320
            ]
321
        );
322
    }
323
324
    public function getInfiniteEmbeddableNestingData()
325
    {
326
        return [
327
            ['DDCInfiniteNestingEmbeddable', 'DDCInfiniteNestingEmbeddable'],
328
            ['DDCNestingEmbeddable1', 'DDCNestingEmbeddable4'],
329
        ];
330
    }
331
332
    public function testEmbeddableIsNotTransient()
333
    {
334
        $this->assertFalse($this->_em->getMetadataFactory()->isTransient(DDC93Address::class));
335
    }
336
}
337
338
339
/**
340
 * @Entity
341
 */
342
class DDC93Person
343
{
344
    /** @Id @GeneratedValue @Column(type="integer") */
345
    public $id;
346
347
    /** @Column(type="string") */
348
    public $name;
349
350
    /** @Embedded(class="DDC93Address") */
351
    public $address;
352
353
    /** @Embedded(class = "DDC93Timestamps") */
354
    public $timestamps;
355
356
    public function __construct($name = null, DDC93Address $address = null)
357
    {
358
        $this->name = $name;
359
        $this->address = $address;
360
        $this->timestamps = new DDC93Timestamps(new \DateTime);
361
    }
362
}
363
364
/**
365
 * @Embeddable
366
 */
367
class DDC93Timestamps
368
{
369
    /** @Column(type = "datetime") */
370
    public $createdAt;
371
372
    public function __construct(\DateTime $createdAt)
373
    {
374
        $this->createdAt = $createdAt;
375
    }
376
}
377
378
/**
379
 * @Entity
380
 *
381
 * @InheritanceType("SINGLE_TABLE")
382
 * @DiscriminatorColumn(name = "t", type = "string", length = 10)
383
 * @DiscriminatorMap({
384
 *     "v" = "Doctrine\Tests\ORM\Functional\DDC93Car",
385
 * })
386
 */
387
abstract class DDC93Vehicle
388
{
389
    /** @Id @GeneratedValue(strategy = "AUTO") @Column(type = "integer") */
390
    public $id;
391
392
    /** @Embedded(class = "DDC93Address") */
393
    public $address;
394
395
    public function __construct(DDC93Address $address)
396
    {
397
        $this->address = $address;
398
    }
399
}
400
401
/**
402
 * @Entity
403
 */
404
class DDC93Car extends DDC93Vehicle
405
{
406
}
407
408
/**
409
 * @Embeddable
410
 */
411
class DDC93Country
412
{
413
    /**
414
     * @Column(type="string", nullable=true)
415
     */
416
    public $name;
417
418
    public function __construct($name = null)
419
    {
420
        $this->name = $name;
421
    }
422
}
423
424
/**
425
 * @Embeddable
426
 */
427
class DDC93Address
428
{
429
    /**
430
     * @Column(type="string")
431
     */
432
    public $street;
433
    /**
434
     * @Column(type="string")
435
     */
436
    public $zip;
437
    /**
438
     * @Column(type="string")
439
     */
440
    public $city;
441
    /** @Embedded(class = "DDC93Country") */
442
    public $country;
443
444
    public function __construct($street = null, $zip = null, $city = null, DDC93Country $country = null)
445
    {
446
        $this->street = $street;
447
        $this->zip = $zip;
448
        $this->city = $city;
449
        $this->country = $country;
450
    }
451
}
452
453
/** @Entity */
454
class DDC93Customer
455
{
456
    /** @Id @GeneratedValue @Column(type="integer") */
457
    private $id;
458
459
    /** @Embedded(class = "DDC93ContactInfo", columnPrefix = "contact_info_") */
460
    private $contactInfo;
0 ignored issues
show
introduced by
The private property $contactInfo is not used, and could be removed.
Loading history...
461
}
462
463
/** @Embeddable */
464
class DDC93ContactInfo
465
{
466
    /**
467
     * @Column(type="string")
468
     */
469
    public $email;
470
    /** @Embedded(class = "DDC93Address") */
471
    public $address;
472
}
473
474
/**
475
 * @Entity
476
 */
477
class DDC3028PersonWithPrefix
478
{
479
    /** @Embedded(class="DDC3028Id", columnPrefix = "foobar_") */
480
    public $id;
481
482
    /** @Embedded(class="DDC3028NestedEmbeddable", columnPrefix = "bloo_") */
483
    public $nested;
484
485
    public function __construct(DDC3028Id $id = null, DDC3028NestedEmbeddable $nested = null)
486
    {
487
        $this->id = $id;
488
        $this->nested = $nested;
489
    }
490
}
491
492
/**
493
 * @Entity
494
 */
495
class DDC3028PersonEmptyPrefix
496
{
497
    /** @Embedded(class="DDC3028Id", columnPrefix = "") */
498
    public $id;
499
500
    /** @Embedded(class="DDC3028NestedEmbeddable", columnPrefix = "") */
501
    public $nested;
502
503
    public function __construct(DDC3028Id $id = null, DDC3028NestedEmbeddable $nested = null)
504
    {
505
        $this->id = $id;
506
        $this->nested = $nested;
507
    }
508
}
509
510
/**
511
 * @Entity
512
 */
513
class DDC3028PersonPrefixFalse
514
{
515
    /** @Embedded(class="DDC3028Id", columnPrefix = false) */
516
    public $id;
517
518
    public function __construct(DDC3028Id $id = null)
519
    {
520
        $this->id = $id;
521
    }
522
}
523
524
/**
525
 * @Embeddable
526
 */
527
class DDC3028Id
528
{
529
    /**
530
     * @Id @Column(type="string")
531
     */
532
    public $id;
533
534
    public function __construct($id = null)
535
    {
536
        $this->id = $id;
537
    }
538
}
539
540
/**
541
 * @Embeddable
542
 */
543
class DDC3028NestedEmbeddable
544
{
545
    /** @Embedded(class="DDC3028Id", columnPrefix = "foo_") */
546
    public $nestedWithPrefix;
547
548
    /** @Embedded(class="DDC3028Id", columnPrefix = "") */
549
    public $nestedWithEmptyPrefix;
550
551
    /** @Embedded(class="DDC3028Id", columnPrefix = false) */
552
    public $nestedWithPrefixFalse;
553
554
    public function __construct(
555
        DDC3028Id $nestedWithPrefix = null,
556
        DDC3028Id $nestedWithEmptyPrefix = null,
557
        DDC3028Id $nestedWithPrefixFalse = null
558
    ) {
559
        $this->nestedWithPrefix = $nestedWithPrefix;
560
        $this->nestedWithEmptyPrefix = $nestedWithEmptyPrefix;
561
        $this->nestedWithPrefixFalse = $nestedWithPrefixFalse;
562
    }
563
}
564
565
/**
566
 * @MappedSuperclass
567
 */
568
abstract class DDC3027Animal
569
{
570
    /** @Id @GeneratedValue(strategy = "AUTO") @Column(type = "integer") */
571
    public $id;
572
573
    /** @Embedded(class = "DDC93Address") */
574
    public $address;
575
}
576
577
/**
578
 * @Entity
579
 */
580
class DDC3027Dog extends DDC3027Animal
581
{
582
}
583
584
/**
585
 * @Embeddable
586
 */
587
class DDCInfiniteNestingEmbeddable
588
{
589
    /** @Embedded(class="DDCInfiniteNestingEmbeddable") */
590
    public $nested;
591
}
592
593
/**
594
 * @Embeddable
595
 */
596
class DDCNestingEmbeddable1
597
{
598
    /** @Embedded(class="DDC3028Id") */
599
    public $id1;
600
601
    /** @Embedded(class="DDC3028Id") */
602
    public $id2;
603
604
    /** @Embedded(class="DDCNestingEmbeddable2") */
605
    public $nested;
606
}
607
608
/**
609
 * @Embeddable
610
 */
611
class DDCNestingEmbeddable2
612
{
613
    /** @Embedded(class="DDC3028Id") */
614
    public $id1;
615
616
    /** @Embedded(class="DDC3028Id") */
617
    public $id2;
618
619
    /** @Embedded(class="DDCNestingEmbeddable3") */
620
    public $nested;
621
}
622
623
/**
624
 * @Embeddable
625
 */
626
class DDCNestingEmbeddable3
627
{
628
    /** @Embedded(class="DDC3028Id") */
629
    public $id1;
630
631
    /** @Embedded(class="DDC3028Id") */
632
    public $id2;
633
634
    /** @Embedded(class="DDCNestingEmbeddable4") */
635
    public $nested;
636
}
637
638
/**
639
 * @Embeddable
640
 */
641
class DDCNestingEmbeddable4
642
{
643
    /** @Embedded(class="DDC3028Id") */
644
    public $id1;
645
646
    /** @Embedded(class="DDC3028Id") */
647
    public $id2;
648
649
    /** @Embedded(class="DDCNestingEmbeddable1") */
650
    public $nested;
651
}
652