1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace Metadata; |
||||||
6 | |||||||
7 | use Metadata\Cache\CacheInterface; |
||||||
8 | use Metadata\Driver\AdvancedDriverInterface; |
||||||
9 | use Metadata\Driver\DriverInterface; |
||||||
10 | |||||||
11 | class MetadataFactory implements AdvancedMetadataFactoryInterface |
||||||
12 | { |
||||||
13 | /** |
||||||
14 | * @var DriverInterface |
||||||
15 | */ |
||||||
16 | private $driver; |
||||||
17 | |||||||
18 | /** |
||||||
19 | * @var CacheInterface |
||||||
20 | */ |
||||||
21 | private $cache; |
||||||
22 | |||||||
23 | /** |
||||||
24 | * @var ClassMetadata[] |
||||||
25 | */ |
||||||
26 | private $loadedMetadata = []; |
||||||
27 | |||||||
28 | /** |
||||||
29 | * @var ClassMetadata[] |
||||||
30 | */ |
||||||
31 | private $loadedClassMetadata = []; |
||||||
32 | |||||||
33 | /** |
||||||
34 | * @var string|null |
||||||
35 | */ |
||||||
36 | private $hierarchyMetadataClass; |
||||||
37 | |||||||
38 | /** |
||||||
39 | * @var bool |
||||||
40 | */ |
||||||
41 | private $includeInterfaces = false; |
||||||
42 | |||||||
43 | /** |
||||||
44 | * @var bool |
||||||
45 | */ |
||||||
46 | private $debug = false; |
||||||
47 | |||||||
48 | 10 | public function __construct(DriverInterface $driver, ?string $hierarchyMetadataClass = 'Metadata\ClassHierarchyMetadata', bool $debug = false) |
|||||
49 | { |
||||||
50 | 10 | $this->driver = $driver; |
|||||
51 | 10 | $this->hierarchyMetadataClass = $hierarchyMetadataClass; |
|||||
52 | 10 | $this->debug = $debug; |
|||||
53 | 10 | } |
|||||
54 | |||||||
55 | 1 | public function setIncludeInterfaces(bool $include): void |
|||||
56 | { |
||||||
57 | 1 | $this->includeInterfaces = $include; |
|||||
58 | 1 | } |
|||||
59 | |||||||
60 | 3 | public function setCache(CacheInterface $cache): void |
|||||
61 | { |
||||||
62 | 3 | $this->cache = $cache; |
|||||
63 | 3 | } |
|||||
64 | |||||||
65 | /** |
||||||
66 | * {@inheritDoc} |
||||||
67 | */ |
||||||
68 | 8 | public function getMetadataForClass(string $className) |
|||||
69 | { |
||||||
70 | 8 | if (isset($this->loadedMetadata[$className])) { |
|||||
71 | 3 | return $this->filterNullMetadata($this->loadedMetadata[$className]); |
|||||
72 | } |
||||||
73 | |||||||
74 | 8 | $metadata = null; |
|||||
75 | 8 | foreach ($this->getClassHierarchy($className) as $class) { |
|||||
76 | 8 | if (isset($this->loadedClassMetadata[$name = $class->getName()])) { |
|||||
77 | 1 | if (null !== $classMetadata = $this->filterNullMetadata($this->loadedClassMetadata[$name])) { |
|||||
78 | 1 | $this->addClassMetadata($metadata, $classMetadata); |
|||||
79 | } |
||||||
80 | |||||||
81 | 1 | continue; |
|||||
82 | } |
||||||
83 | |||||||
84 | // check the cache |
||||||
85 | 8 | if (null !== $this->cache) { |
|||||
86 | 3 | if (($classMetadata = $this->cache->load($class->getName())) instanceof NullMetadata) { |
|||||
87 | 1 | $this->loadedClassMetadata[$name] = $classMetadata; |
|||||
88 | 1 | continue; |
|||||
89 | } |
||||||
90 | |||||||
91 | 3 | if (null !== $classMetadata) { |
|||||
92 | if (!$classMetadata instanceof ClassMetadata) { |
||||||
93 | throw new \LogicException(sprintf( |
||||||
94 | 'The cache must return instances of ClassMetadata for class %s, but got %s.', |
||||||
95 | $className, |
||||||
96 | var_export($classMetadata, true) |
||||||
97 | )); |
||||||
98 | } |
||||||
99 | |||||||
100 | if ($this->debug && !$classMetadata->isFresh()) { |
||||||
101 | $this->cache->evict($classMetadata->name); |
||||||
102 | } else { |
||||||
103 | $this->loadedClassMetadata[$name] = $classMetadata; |
||||||
104 | $this->addClassMetadata($metadata, $classMetadata); |
||||||
105 | continue; |
||||||
106 | } |
||||||
107 | } |
||||||
108 | } |
||||||
109 | |||||||
110 | // load from source |
||||||
111 | 8 | if (null !== $classMetadata = $this->driver->loadMetadataForClass($class)) { |
|||||
112 | 4 | $this->loadedClassMetadata[$name] = $classMetadata; |
|||||
113 | 4 | $this->addClassMetadata($metadata, $classMetadata); |
|||||
114 | |||||||
115 | 4 | if (null !== $this->cache) { |
|||||
116 | 1 | $this->cache->put($classMetadata); |
|||||
117 | } |
||||||
118 | |||||||
119 | 4 | continue; |
|||||
120 | } |
||||||
121 | |||||||
122 | 4 | if (null !== $this->cache && !$this->debug) { |
|||||
123 | 1 | $this->cache->put(new NullMetadata($class->getName())); |
|||||
124 | } |
||||||
125 | } |
||||||
126 | |||||||
127 | 8 | if (null === $metadata) { |
|||||
128 | 4 | $metadata = new NullMetadata($className); |
|||||
129 | } |
||||||
130 | |||||||
131 | 8 | return $this->filterNullMetadata($this->loadedMetadata[$className] = $metadata); |
|||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||
132 | } |
||||||
133 | |||||||
134 | /** |
||||||
135 | * {@inheritDoc} |
||||||
136 | */ |
||||||
137 | 2 | public function getAllClassNames(): array |
|||||
138 | { |
||||||
139 | 2 | if (!$this->driver instanceof AdvancedDriverInterface) { |
|||||
140 | 1 | throw new \RuntimeException( |
|||||
141 | 1 | sprintf('Driver "%s" must be an instance of "AdvancedDriverInterface".', get_class($this->driver)) |
|||||
142 | ); |
||||||
143 | } |
||||||
144 | |||||||
145 | 1 | return $this->driver->getAllClassNames(); |
|||||
146 | } |
||||||
147 | |||||||
148 | /** |
||||||
149 | * @param MergeableInterface|ClassHierarchyMetadata $metadata |
||||||
150 | */ |
||||||
151 | 4 | private function addClassMetadata(&$metadata, ClassMetadata $toAdd): void |
|||||
152 | { |
||||||
153 | 4 | if ($toAdd instanceof MergeableInterface) { |
|||||
154 | 2 | if (null === $metadata) { |
|||||
0 ignored issues
–
show
|
|||||||
155 | 2 | $metadata = clone $toAdd; |
|||||
156 | } else { |
||||||
157 | 2 | $metadata->merge($toAdd); |
|||||
0 ignored issues
–
show
The method
merge() does not exist on Metadata\ClassHierarchyMetadata .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
158 | } |
||||||
159 | } else { |
||||||
160 | 2 | if (null === $metadata) { |
|||||
0 ignored issues
–
show
|
|||||||
161 | 2 | $class = $this->hierarchyMetadataClass; |
|||||
162 | 2 | $metadata = new $class(); |
|||||
163 | } |
||||||
164 | |||||||
165 | 2 | $metadata->addClassMetadata($toAdd); |
|||||
0 ignored issues
–
show
The method
addClassMetadata() does not exist on Metadata\MergeableInterface .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
166 | } |
||||||
167 | 4 | } |
|||||
168 | |||||||
169 | /** |
||||||
170 | * @return \ReflectionClass[] |
||||||
171 | */ |
||||||
172 | 8 | private function getClassHierarchy(string $class): array |
|||||
173 | { |
||||||
174 | 8 | $classes = []; |
|||||
175 | 8 | $refl = new \ReflectionClass($class); |
|||||
176 | |||||||
177 | do { |
||||||
178 | 8 | $classes[] = $refl; |
|||||
179 | 8 | $refl = $refl->getParentClass(); |
|||||
180 | 8 | } while (false !== $refl); |
|||||
181 | |||||||
182 | 8 | $classes = array_reverse($classes, false); |
|||||
183 | |||||||
184 | 8 | if (!$this->includeInterfaces) { |
|||||
185 | 7 | return $classes; |
|||||
186 | } |
||||||
187 | |||||||
188 | 1 | $addedInterfaces = []; |
|||||
189 | 1 | $newHierarchy = []; |
|||||
190 | |||||||
191 | 1 | foreach ($classes as $class) { |
|||||
192 | 1 | foreach ($class->getInterfaces() as $interface) { |
|||||
193 | 1 | if (isset($addedInterfaces[$interface->getName()])) { |
|||||
194 | 1 | continue; |
|||||
195 | } |
||||||
196 | |||||||
197 | 1 | $addedInterfaces[$interface->getName()] = true; |
|||||
198 | |||||||
199 | 1 | $newHierarchy[] = $interface; |
|||||
200 | } |
||||||
201 | |||||||
202 | 1 | $newHierarchy[] = $class; |
|||||
203 | } |
||||||
204 | |||||||
205 | 1 | return $newHierarchy; |
|||||
206 | } |
||||||
207 | |||||||
208 | /** |
||||||
209 | * @param ClassMetadata|ClassHierarchyMetadata|MergeableInterface $metadata |
||||||
210 | * |
||||||
211 | * @return ClassMetadata|ClassHierarchyMetadata|MergeableInterface |
||||||
212 | */ |
||||||
213 | 8 | private function filterNullMetadata($metadata = null) |
|||||
214 | { |
||||||
215 | 8 | return !$metadata instanceof NullMetadata ? $metadata : null; |
|||||
216 | } |
||||||
217 | } |
||||||
218 |