1<?php
2
3declare(strict_types=1);
4
5namespace Metadata\Tests;
6
7use Metadata\ClassMetadata;
8use Metadata\MergeableClassMetadata;
9use Metadata\MetadataFactory;
10use Metadata\PropertyMetadata;
11use PHPUnit\Framework\TestCase;
12
13class MetadataFactoryTest extends TestCase
14{
15    public function testGetMetadataForClass()
16    {
17        $driver = $this->createMock('Metadata\Driver\DriverInterface');
18
19        $driver
20            ->expects($this->at(0))
21            ->method('loadMetadataForClass')
22            ->with($this->equalTo(new \ReflectionClass('Metadata\Tests\Fixtures\TestObject')))
23            ->will($this->returnCallback(function ($class) {
24                return new ClassMetadata($class->getName());
25            }))
26        ;
27        $driver
28            ->expects($this->at(1))
29            ->method('loadMetadataForClass')
30            ->with($this->equalTo(new \ReflectionClass('Metadata\Tests\Fixtures\TestParent')))
31            ->will($this->returnCallback(function ($class) {
32                return new ClassMetadata($class->getName());
33            }))
34        ;
35
36        $factory = new MetadataFactory($driver);
37        $metadata = $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestParent');
38
39        $this->assertInstanceOf('Metadata\ClassHierarchyMetadata', $metadata);
40        $this->assertEquals(2, count($metadata->classMetadata));
41    }
42
43    public function testGetMetadataForClassWhenMergeable()
44    {
45        $driver = $this->createMock('Metadata\Driver\DriverInterface');
46
47        $driver
48            ->expects($this->at(0))
49            ->method('loadMetadataForClass')
50            ->with($this->equalTo(new \ReflectionClass('Metadata\Tests\Fixtures\TestObject')))
51            ->will($this->returnCallback(function ($class) {
52                return new MergeableClassMetadata($class->getName());
53            }))
54        ;
55        $driver
56            ->expects($this->at(1))
57            ->method('loadMetadataForClass')
58            ->with($this->equalTo(new \ReflectionClass('Metadata\Tests\Fixtures\TestParent')))
59            ->will($this->returnCallback(function ($class) {
60                return new MergeableClassMetadata($class->getName());
61            }))
62        ;
63
64        $factory = new MetadataFactory($driver);
65        $metadata = $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestParent');
66
67        $this->assertInstanceOf('Metadata\MergeableClassMetadata', $metadata);
68        $this->assertEquals('Metadata\Tests\Fixtures\TestParent', $metadata->name);
69    }
70
71    public function testGetMetadataWithComplexHierarchy()
72    {
73        $driver = $this->createMock('Metadata\Driver\DriverInterface');
74
75        $driver
76            ->expects($this->any())
77            ->method('loadMetadataForClass')
78            ->will($this->returnCallback(function ($class) {
79                $metadata = new MergeableClassMetadata($class->name);
80
81                switch ($class->name) {
82                    case 'Metadata\Tests\Fixtures\ComplexHierarchy\BaseClass':
83                        $metadata->propertyMetadata['foo'] = new PropertyMetadata($class->name, 'foo');
84                        break;
85
86                    case 'Metadata\Tests\Fixtures\ComplexHierarchy\SubClassA':
87                        $metadata->propertyMetadata['bar'] = new PropertyMetadata($class->name, 'bar');
88                        break;
89
90                    case 'Metadata\Tests\Fixtures\ComplexHierarchy\SubClassB':
91                        $metadata->propertyMetadata['baz'] = new PropertyMetadata($class->name, 'baz');
92                        break;
93
94                    default:
95                        throw new \RuntimeException(sprintf('Unsupported class "%s".', $class->name));
96                }
97
98                return $metadata;
99            }))
100        ;
101
102        $factory = new MetadataFactory($driver);
103
104        $subClassA = $factory->getMetadataForClass('Metadata\Tests\Fixtures\ComplexHierarchy\SubClassA');
105        $this->assertInstanceOf('Metadata\MergeableClassMetadata', $subClassA);
106        $this->assertEquals(['foo', 'bar'], array_keys($subClassA->propertyMetadata));
107
108        $subClassB = $factory->getMetadataForClass('Metadata\Tests\Fixtures\ComplexHierarchy\SubClassB');
109        $this->assertInstanceOf('Metadata\MergeableClassMetadata', $subClassB);
110        $this->assertEquals(['foo', 'baz'], array_keys($subClassB->propertyMetadata));
111    }
112
113    public function testGetMetadataWithCache()
114    {
115        $driver = $this->createMock('Metadata\Driver\DriverInterface');
116        $driver
117            ->expects($this->once())
118            ->method('loadMetadataForClass')
119            ->will($this->returnValue($metadata = new ClassMetadata('Metadata\Tests\Fixtures\TestObject')))
120        ;
121
122        $factory = new MetadataFactory($driver);
123
124        $cache = $this->createMock('Metadata\Cache\CacheInterface');
125        $cache
126            ->expects($this->once())
127            ->method('load')
128            ->with($this->equalTo('Metadata\Tests\Fixtures\TestObject'))
129            ->will($this->returnValue(null))
130        ;
131        $cache
132            ->expects($this->once())
133            ->method('put')
134            ->with($this->equalTo($metadata))
135        ;
136        $factory->setCache($cache);
137
138        $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject');
139        $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject');
140        $this->assertSame($metadata, reset($factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject')->classMetadata));
141    }
142
143    public function testGetMetadataReturnsNullIfNoMetadataIsFound()
144    {
145        $driver = $this->createMock('Metadata\Driver\DriverInterface');
146        $driver
147            ->expects($this->once())
148            ->method('loadMetadataForClass')
149            ->will($this->returnValue(null))
150        ;
151
152        $factory = new MetadataFactory($driver);
153
154        $this->assertNull($factory->getMetadataForClass('stdClass'));
155    }
156
157    public function testGetMetadataWithInterfaces()
158    {
159        $driver = $this->createMock('Metadata\Driver\DriverInterface');
160
161        $driver
162            ->expects($this->at(3))
163            ->method('loadMetadataForClass')
164            ->with($this->equalTo(new \ReflectionClass('Metadata\Tests\Fixtures\ComplexHierarchy\SubClassA')))
165        ;
166        $driver
167            ->expects($this->at(2))
168            ->method('loadMetadataForClass')
169            ->with($this->equalTo(new \ReflectionClass('Metadata\Tests\Fixtures\ComplexHierarchy\InterfaceB')))
170        ;
171        $driver
172            ->expects($this->at(1))
173            ->method('loadMetadataForClass')
174            ->with($this->equalTo(new \ReflectionClass('Metadata\Tests\Fixtures\ComplexHierarchy\BaseClass')))
175        ;
176        $driver
177            ->expects($this->at(0))
178            ->method('loadMetadataForClass')
179            ->with($this->equalTo(new \ReflectionClass('Metadata\Tests\Fixtures\ComplexHierarchy\InterfaceA')))
180        ;
181
182        $factory = new MetadataFactory($driver);
183        $factory->setIncludeInterfaces(true);
184
185        $factory->getMetadataForClass('Metadata\Tests\Fixtures\ComplexHierarchy\SubClassA');
186    }
187
188    public function testGetAllClassNames()
189    {
190        $driver = $this->createMock('Metadata\Driver\AdvancedDriverInterface');
191        $driver
192            ->expects($this->once())
193            ->method('getAllClassNames')
194            ->will($this->returnValue([]));
195
196        $factory = new MetadataFactory($driver);
197        $this->assertSame([], $factory->getAllClassNames());
198    }
199
200    public function testGetAllClassNamesThrowsException()
201    {
202        $this->expectException('RuntimeException');
203        $factory = new MetadataFactory($this->createMock('Metadata\Driver\DriverInterface'));
204        $factory->getAllClassNames();
205    }
206
207    public function testNotFoundMetadataIsCached()
208    {
209        $driver = $this->createMock('Metadata\Driver\DriverInterface');
210        $driver
211            ->expects($this->once()) // This is the important part of this test
212            ->method('loadMetadataForClass')
213            ->will($this->returnValue(null))
214        ;
215
216        $cachedMetadata = null;
217        $cache = $this->createMock('Metadata\Cache\CacheInterface');
218        $cache
219            ->expects($this->any())
220            ->method('load')
221            ->with($this->equalTo('Metadata\Tests\Fixtures\TestObject'))
222            ->will($this->returnCallback(function () use (&$cachedMetadata) {
223                return $cachedMetadata;
224            }))
225        ;
226        $cache
227            ->expects($this->once())
228            ->method('put')
229            ->will($this->returnCallback(function ($metadata) use (&$cachedMetadata) {
230                $cachedMetadata = $metadata;
231            }))
232        ;
233
234        $factory = new MetadataFactory($driver);
235        $factory->setCache($cache);
236        $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject');
237        $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject');
238        $this->assertNull($factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject'));
239
240        // We use another factory with the same cache, to simulate another request and skip the in memory
241        $factory = new MetadataFactory($driver);
242        $factory->setCache($cache);
243        $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject');
244        $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject');
245        $this->assertNull($factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject'));
246    }
247
248    public function testNotFoundMetadataIsNotCachedInDebug()
249    {
250        $driver = $this->createMock('Metadata\Driver\DriverInterface');
251        $driver
252            ->expects($this->exactly(2))
253            ->method('loadMetadataForClass')
254            ->will($this->returnValue(null))
255        ;
256
257        $cachedMetadata = null;
258        $cache = $this->createMock('Metadata\Cache\CacheInterface');
259        $cache
260            ->expects($this->any())
261            ->method('load')
262            ->with($this->equalTo('Metadata\Tests\Fixtures\TestObject'))
263            ->will($this->returnValue(null))
264        ;
265        $cache
266            ->expects($this->never())
267            ->method('put')
268        ;
269
270        $factory = new MetadataFactory($driver, 'Metadata\ClassHierarchyMetadata', true);
271        $factory->setCache($cache);
272        $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject');
273        $this->assertNull($factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject'));
274
275        // We use another factory with the same cache, to simulate another request and skip the in memory
276        $factory = new MetadataFactory($driver, 'Metadata\ClassHierarchyMetadata', true);
277        $factory->setCache($cache);
278        $factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject');
279        $this->assertNull($factory->getMetadataForClass('Metadata\Tests\Fixtures\TestObject'));
280    }
281}
282