Issues (19)

src/MetadataFactory.php (5 issues)

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
The expression return $this->filterNull...className] = $metadata) also could return the type Metadata\ClassMetadata|Metadata\MergeableInterface which includes types incompatible with the return type mandated by Metadata\MetadataFactory...::getMetadataForClass() of Metadata\ClassHierarchyM...eableClassMetadata|null. Consider adding a type-check to rule them out.
Loading history...
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
The condition null === $metadata is always false.
Loading history...
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 ignore-call  annotation

157
                $metadata->/** @scrutinizer ignore-call */ 
158
                           merge($toAdd);

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.

Loading history...
158
            }
159
        } else {
160 2
            if (null === $metadata) {
0 ignored issues
show
The condition null === $metadata is always false.
Loading history...
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 ignore-call  annotation

165
            $metadata->/** @scrutinizer ignore-call */ 
166
                       addClassMetadata($toAdd);

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.

Loading history...
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