Completed
Pull Request — master (#513)
by Mantas
03:31
created

MetadataCollector::getMappingByNamespace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 13
rs 9.4286
cc 2
eloc 7
nc 2
nop 1
1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ONGR\ElasticsearchBundle\Mapping;
13
14
use Doctrine\Common\Cache\CacheProvider;
15
16
/**
17
 * DocumentParser wrapper for getting bundle documents mapping.
18
 */
19
class MetadataCollector
20
{
21
    /**
22
     * @var DocumentFinder
23
     */
24
    private $finder;
25
26
    /**
27
     * @var DocumentParser
28
     */
29
    private $parser;
30
31
    /**
32
     * @var CacheProvider
33
     */
34
    private $cache = null;
35
36
    /**
37
     * @var bool
38
     */
39
    private $enableCache = false;
40
41
    /**
42
     * Bundles mappings local cache container. Could be stored as the whole bundle or as single document.
43
     * e.g. AcmeDemoBundle, AcmeDemoBundle:Product.
44
     *
45
     * @var mixed
46
     */
47
    private $mappings = [];
48
49
    /**
50
     * @param DocumentFinder $finder For finding documents.
51
     * @param DocumentParser $parser For reading document annotations.
52
     * @param CacheProvider  $cache  Cache provider to store the meta data for later use.
53
     */
54
    public function __construct($finder, $parser, $cache = null)
55
    {
56
        $this->finder = $finder;
57
        $this->parser = $parser;
58
        $this->cache = $cache;
59
60
        if ($this->cache) {
61
            $this->mappings = $this->cache->fetch('ongr.metadata.mappings');
62
        }
63
    }
64
65
    /**
66
     * Enables metadata caching.
67
     *
68
     * @param bool $enableCache
69
     */
70
    public function setEnableCache($enableCache)
71
    {
72
        $this->enableCache = $enableCache;
73
    }
74
75
    /**
76
     * Fetches bundles mapping from documents.
77
     *
78
     * @param string[] $bundles Elasticsearch manager config. You can get bundles list from 'mappings' node.
79
     * @return array
80
     */
81
    public function getMappings(array $bundles)
82
    {
83
        $output = [];
84
        foreach ($bundles as $bundle) {
85
            $mappings = $this->getBundleMapping($bundle);
86
87
            $alreadyDefinedTypes = array_intersect_key($mappings, $output);
88
            if (count($alreadyDefinedTypes)) {
89
                throw new \LogicException(
90
                    implode(',', array_keys($alreadyDefinedTypes)) .
91
                    ' type(s) already defined in other document, you can use the same ' .
92
                    'type only once in a manager definition.'
93
                );
94
            }
95
96
            $output = array_merge($output, $mappings);
97
        }
98
99
        return $output;
100
    }
101
102
    /**
103
     * Searches for documents in the bundle and tries to read them.
104
     *
105
     * @param string $name
106
     *
107
     * @return array Empty array on containing zero documents.
108
     */
109
    public function getBundleMapping($name)
110
    {
111
        if (!is_string($name)) {
112
            throw new \LogicException('getBundleMapping() in the Metadata collector expects a string argument only!');
113
        }
114
115
        if (isset($this->mappings[$name])) {
116
            return $this->mappings[$name];
117
        }
118
119
        // Checks if is mapped document or bundle.
120
        if (strpos($name, ':') !== false) {
121
            $bundleInfo = explode(':', $name);
122
            $bundle = $bundleInfo[0];
123
            $documentClass = $bundleInfo[1];
124
125
            $documents = $this->finder->getBundleDocumentPaths($bundle);
126
            $documents = array_filter(
127
                $documents,
128
                function ($document) use ($documentClass) {
129
                    if (pathinfo($document, PATHINFO_FILENAME) == $documentClass) {
130
                        return true;
131
                    }
132
                }
133
            );
134
        } else {
135
            $documents = $this->finder->getBundleDocumentPaths($name);
136
            $bundle = $name;
137
        }
138
139
        $mappings = [];
140
        $bundleNamespace = $this->finder->getBundleClass($bundle);
141
        $bundleNamespace = substr($bundleNamespace, 0, strrpos($bundleNamespace, '\\'));
142
143
        if (!count($documents)) {
144
            return [];
145
        }
146
147
        // Loop through documents found in bundle.
148
        foreach ($documents as $document) {
149
            $documentReflection = new \ReflectionClass(
150
                $bundleNamespace .
151
                '\\' . DocumentFinder::DOCUMENT_DIR .
152
                '\\' . pathinfo($document, PATHINFO_FILENAME)
153
            );
154
155
            $documentMapping = $this->getDocumentReflectionMapping($documentReflection);
156
157
            if (!array_key_exists('type', $documentMapping)) {
158
                continue;
159
            }
160
161
            if (!array_key_exists($documentMapping['type'], $mappings)) {
162
                $documentMapping['bundle'] = $bundle;
163
                $mappings = array_merge($mappings, [$documentMapping['type'] => $documentMapping]);
164
            } else {
165
                throw new \LogicException(
166
                    $bundle . ' has 2 same type names defined in the documents. ' .
167
                    'Type names must be unique!'
168
                );
169
            }
170
        }
171
172
        $this->cacheBundle($name, $mappings);
173
174
        return $mappings;
175
    }
176
177
    /**
178
     * @param array $manager
179
     *
180
     * @return array
181
     */
182
    public function getManagerTypes($manager)
183
    {
184
        $mapping = $this->getMappings($manager['mappings']);
185
186
        return array_keys($mapping);
187
    }
188
189
    /**
190
     * Resolves document elasticsearch type, use format: SomeBarBundle:AcmeDocument.
191
     *
192
     * @param string $document
193
     *
194
     * @return string
195
     */
196
    public function getDocumentType($document)
197
    {
198
        $mapping = $this->getMappingByNamespace($document);
199
200
        return $mapping['type'];
201
    }
202
203
    /**
204
     * Retrieves document mapping by namespace.
205
     *
206
     * @param string $namespace Document namespace.
207
     *
208
     * @return array|null
209
     */
210
    public function getMappingByNamespace($namespace)
211
    {
212
        $namespace = $this->getClassName($namespace);
213
214
        if (isset($this->mappings[$namespace])) {
215
            return $this->mappings[$namespace];
216
        }
217
218
        $mapping = $this->getDocumentReflectionMapping(new \ReflectionClass($namespace));
219
        $this->cacheBundle($namespace, $mapping);
220
221
        return $mapping;
222
    }
223
224
    /**
225
     * Retrieves prepared mapping to sent to the elasticsearch client.
226
     *
227
     * @param array $bundles Manager config.
228
     *
229
     * @return array|null
230
     */
231
    public function getClientMapping(array $bundles)
232
    {
233
        /** @var array $typesMapping Array of filtered mappings for the elasticsearch client*/
234
        $typesMapping = null;
235
236
        /** @var array $mappings All mapping info */
237
        $mappings = $this->getMappings($bundles);
238
239
        foreach ($mappings as $type => $mapping) {
240
            if (!empty($mapping['properties'])) {
241
                $typesMapping[$type] = array_filter(
242
                    array_merge(
243
                        ['properties' => $mapping['properties']],
244
                        $mapping['fields']
245
                    ),
246
                    function ($value) {
247
                        return (bool)$value || is_bool($value);
248
                    }
249
                );
250
            }
251
        }
252
253
        return $typesMapping;
254
    }
255
256
    /**
257
     * Gathers annotation data from class.
258
     *
259
     * @param \ReflectionClass $reflectionClass Document reflection class to read mapping from.
260
     *
261
     * @return array
262
     */
263
    private function getDocumentReflectionMapping(\ReflectionClass $reflectionClass)
264
    {
265
        return $this->parser->parse($reflectionClass);
266
    }
267
268
    /**
269
     * Retrieves mapping from document.
270
     *
271
     * @param object $document
272
     *
273
     * @return array
274
     */
275
    public function getDocumentMapping($document)
276
    {
277
        if (!is_object($document)) {
278
            throw new \InvalidArgumentException('Document must be an object.');
279
        }
280
281
        return $this->getDocumentReflectionMapping(new \ReflectionObject($document));
282
    }
283
284
    /**
285
     * Returns mapping with metadata.
286
     *
287
     * @param string $namespace Bundle or document namespace.
288
     *
289
     * @return array
290
     */
291
    public function getMapping($namespace)
292
    {
293
        if (strpos($namespace, ':') === false) {
294
            return $this->getBundleMapping($namespace);
295
        }
296
        $mapping = $this->getMappingByNamespace($namespace);
297
298
        return $mapping === null ? [] : $mapping;
299
    }
300
301
    /**
302
     * Adds metadata information to the cache storage.
303
     *
304
     * @param string $bundle
305
     * @param array  $mapping
306
     */
307
    private function cacheBundle($bundle, array $mapping)
308
    {
309
        if ($this->enableCache) {
310
            $this->mappings[$bundle] = $mapping;
311
            $this->cache->save('ongr.metadata.mappings', $this->mappings);
312
        }
313
    }
314
315
    /**
316
     * Returns fully qualified class name.
317
     *
318
     * @param string $className
319
     *
320
     * @return string
321
     */
322
    public function getClassName($className)
323
    {
324
        return $this->finder->getNamespace($className);
325
    }
326
}
327