Completed
Push — master ( bb241f...f9eb32 )
by Simonas
04:41 queued 01:25
created

MetadataCollector::getAnalysisNodeConfiguration()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 10
nc 4
nop 4
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
use ONGR\ElasticsearchBundle\Exception\DocumentParserException;
16
use ONGR\ElasticsearchBundle\Exception\MissingDocumentAnnotationException;
17
18
/**
19
 * DocumentParser wrapper for getting bundle documents mapping.
20
 */
21
class MetadataCollector
22
{
23
    /**
24
     * @var DocumentFinder
25
     */
26
    private $finder;
27
28
    /**
29
     * @var DocumentParser
30
     */
31
    private $parser;
32
33
    /**
34
     * @var CacheProvider
35
     */
36
    private $cache = null;
37
38
    /**
39
     * @var bool
40
     */
41
    private $enableCache = false;
42
43
    /**
44
     * @param DocumentFinder $finder For finding documents.
45
     * @param DocumentParser $parser For reading document annotations.
46
     * @param CacheProvider  $cache  Cache provider to store the meta data for later use.
47
     */
48
    public function __construct($finder, $parser, $cache = null)
49
    {
50
        $this->finder = $finder;
51
        $this->parser = $parser;
52
        $this->cache = $cache;
53
    }
54
55
    /**
56
     * Enables metadata caching.
57
     *
58
     * @param bool $enableCache
59
     */
60
    public function setEnableCache($enableCache)
61
    {
62
        $this->enableCache = $enableCache;
63
    }
64
65
    /**
66
     * Fetches bundles mapping from documents.
67
     *
68
     * @param string[] $bundles Elasticsearch manager config. You can get bundles list from 'mappings' node.
69
     * @return array
70
     */
71
    public function getMappings(array $bundles)
72
    {
73
        $output = [];
74
        foreach ($bundles as $name => $bundleConfig) {
75
            // Backward compatibility hack for support.
76
            if (!is_array($bundleConfig)) {
77
                $name = $bundleConfig;
78
                $bundleConfig = [];
79
            }
80
            $mappings = $this->getBundleMapping($name, $bundleConfig);
81
82
            $alreadyDefinedTypes = array_intersect_key($mappings, $output);
83
            if (count($alreadyDefinedTypes)) {
84
                throw new \LogicException(
85
                    implode(',', array_keys($alreadyDefinedTypes)) .
86
                    ' type(s) already defined in other document, you can use the same ' .
87
                    'type only once in a manager definition.'
88
                );
89
            }
90
91
            $output = array_merge($output, $mappings);
92
        }
93
94
        return $output;
95
    }
96
97
    /**
98
     * Searches for documents in the bundle and tries to read them.
99
     *
100
     * @param string $name
101
     * @param array $config Bundle configuration
102
     *
103
     * @return array Empty array on containing zero documents.
104
     */
105
    public function getBundleMapping($name, $config = [])
106
    {
107
        if (!is_string($name)) {
108
            throw new \LogicException('getBundleMapping() in the Metadata collector expects a string argument only!');
109
        }
110
111
        $cacheName =  'ongr.metadata.mapping.' . md5($name.serialize($config));
112
113
        $this->enableCache && $mappings = $this->cache->contains($cacheName);
114
115
        if (isset($mappings)) {
116
            return $mappings;
117
        }
118
119
        $mappings = [];
120
        $documentDir = isset($config['document_dir']) ? $config['document_dir'] : DocumentFinder::DOCUMENT_DIR;
121
122
        // Handle the case when single document mapping requested
123
        // Usage od ":" in name is deprecated. This if is only for BC.
124
        if (strpos($name, ':') !== false) {
125
            list($bundle, $documentClass) = explode(':', $name);
126
            $documents = $this->finder->getBundleDocumentClasses($bundle);
127
            $documents = in_array($documentClass, $documents) ? [$documentClass] : [];
128
        } else {
129
            $documents = $this->finder->getBundleDocumentClasses($name, $documentDir);
130
            $bundle = $name;
131
        }
132
133
        $bundleNamespace = $this->finder->getBundleClass($bundle);
134
        $bundleNamespace = substr($bundleNamespace, 0, strrpos($bundleNamespace, '\\'));
135
136
        if (!count($documents)) {
137
            return [];
138
        }
139
140
        // Loop through documents found in bundle.
141
        foreach ($documents as $document) {
142
            $documentReflection = new \ReflectionClass(
143
                $bundleNamespace .
144
                '\\' . $documentDir .
145
                '\\' . $document
146
            );
147
148
            try {
149
                $documentMapping = $this->getDocumentReflectionMapping($documentReflection);
150
            } catch (MissingDocumentAnnotationException $exception) {
151
                // Not a document, just ignore
152
                continue;
153
            }
154
155
            if (!array_key_exists($documentMapping['type'], $mappings)) {
156
                $documentMapping['bundle'] = $bundle;
157
                $mappings = array_merge($mappings, [$documentMapping['type'] => $documentMapping]);
158
            } else {
159
                throw new \LogicException(
160
                    $bundle . ' has 2 same type names defined in the documents. ' .
161
                    'Type names must be unique!'
162
                );
163
            }
164
        }
165
166
        $this->enableCache && $this->cache->save($cacheName, $mappings);
167
168
        return $mappings;
169
    }
170
171
    /**
172
     * @param array $manager
173
     *
174
     * @return array
175
     */
176
    public function getManagerTypes($manager)
177
    {
178
        $mapping = $this->getMappings($manager['mappings']);
179
180
        return array_keys($mapping);
181
    }
182
183
    /**
184
     * Resolves Elasticsearch type by document class.
185
     *
186
     * @param string $className FQCN or string in AppBundle:Document format
187
     *
188
     * @return string
189
     */
190
    public function getDocumentType($className)
191
    {
192
        $mapping = $this->getMapping($className);
193
194
        return $mapping['type'];
195
    }
196
197
    /**
198
     * Retrieves prepared mapping to sent to the elasticsearch client.
199
     *
200
     * @param array $bundles Manager config.
201
     *
202
     * @return array|null
203
     */
204
    public function getClientMapping(array $bundles)
205
    {
206
        /** @var array $typesMapping Array of filtered mappings for the elasticsearch client*/
207
        $typesMapping = null;
208
209
        /** @var array $mappings All mapping info */
210
        $mappings = $this->getMappings($bundles);
211
212
        foreach ($mappings as $type => $mapping) {
213
            if (!empty($mapping['properties'])) {
214
                $typesMapping[$type] = array_filter(
215
                    array_merge(
216
                        ['properties' => $mapping['properties']],
217
                        $mapping['fields']
218
                    ),
219
                    function ($value) {
220
                        return (bool)$value || is_bool($value);
221
                    }
222
                );
223
            }
224
        }
225
226
        return $typesMapping;
227
    }
228
229
    /**
230
     * Prepares analysis node for Elasticsearch client.
231
     *
232
     * @param array $bundles
233
     * @param array $analysisConfig
234
     *
235
     * @return array
236
     */
237
    public function getClientAnalysis(array $bundles, $analysisConfig = [])
238
    {
239
        $cacheName = 'ongr.metadata.analysis.'.md5(serialize($bundles));
240
        $this->enableCache && $typesAnalysis = $this->cache->fetch($cacheName);
241
242
        if (isset($typesAnalysis)) {
243
            return $typesAnalysis;
244
        }
245
246
        $typesAnalysis = [
247
            'analyzer' => [],
248
            'filter' => [],
249
            'tokenizer' => [],
250
            'char_filter' => [],
251
        ];
252
253
        /** @var array $mappings All mapping info */
254
        $mappings = $this->getMappings($bundles);
255
256
        foreach ($mappings as $type => $metadata) {
257
            foreach ($metadata['analyzers'] as $analyzerName) {
258
                if (isset($analysisConfig['analyzer'][$analyzerName])) {
259
                    $analyzer = $analysisConfig['analyzer'][$analyzerName];
260
                    $typesAnalysis['analyzer'][$analyzerName] = $analyzer;
261
                    $typesAnalysis['filter'] = $this->getAnalysisNodeConfiguration(
262
                        'filter',
263
                        $analyzer,
264
                        $analysisConfig,
265
                        $typesAnalysis['filter']
266
                    );
267
                    $typesAnalysis['tokenizer'] = $this->getAnalysisNodeConfiguration(
268
                        'tokenizer',
269
                        $analyzer,
270
                        $analysisConfig,
271
                        $typesAnalysis['tokenizer']
272
                    );
273
                    $typesAnalysis['char_filter'] = $this->getAnalysisNodeConfiguration(
274
                        'char_filter',
275
                        $analyzer,
276
                        $analysisConfig,
277
                        $typesAnalysis['char_filter']
278
                    );
279
                }
280
            }
281
        }
282
283
        $this->enableCache && $this->cache->save($cacheName, $mappings);
284
285
        return $typesAnalysis;
286
    }
287
288
    /**
289
     * Prepares analysis node content for Elasticsearch client.
290
     *
291
     * @param string $type Node type: filter, tokenizer or char_filter
292
     * @param array $analyzer Analyzer from which used helpers will be extracted.
293
     * @param array $analysisConfig Pre configured analyzers container
294
     * @param array $container Current analysis container where prepared helpers will be appended.
295
     *
296
     * @return array
297
     */
298
    private function getAnalysisNodeConfiguration($type, $analyzer, $analysisConfig, $container = [])
299
    {
300
        if (isset($analyzer[$type])) {
301
            if (is_array($analyzer[$type])) {
302
                foreach ($analyzer[$type] as $filter) {
303
                    if (isset($analysisConfig[$type][$filter])) {
304
                        $container[$filter] = $analysisConfig[$type][$filter];
305
                    }
306
                }
307
            } else {
308
                if (isset($analysisConfig[$type][$analyzer[$type]])) {
309
                    $container[$analyzer[$type]] = $analysisConfig[$type][$analyzer[$type]];
310
                }
311
            }
312
        }
313
        return $container;
314
    }
315
316
    /**
317
     * Gathers annotation data from class.
318
     *
319
     * @param \ReflectionClass $reflectionClass Document reflection class to read mapping from.
320
     *
321
     * @return array
322
     * @throws DocumentParserException
323
     */
324
    private function getDocumentReflectionMapping(\ReflectionClass $reflectionClass)
325
    {
326
        return $this->parser->parse($reflectionClass);
327
    }
328
329
    /**
330
     * Returns single document mapping metadata.
331
     *
332
     * @param string $namespace Document namespace
333
     *
334
     * @return array
335
     * @throws DocumentParserException
336
     */
337
    public function getMapping($namespace)
338
    {
339
        $cacheName = 'ongr.metadata.document.'.md5($namespace);
340
341
        $namespace = $this->getClassName($namespace);
342
        $this->enableCache && $mapping = $this->cache->fetch($cacheName);
343
344
        if (isset($mapping)) {
345
            return $mapping;
346
        }
347
348
        $mapping = $this->getDocumentReflectionMapping(new \ReflectionClass($namespace));
349
350
        $this->enableCache && $this->cache->save($cacheName, $mapping);
351
352
        return $mapping;
353
    }
354
355
    /**
356
     * Returns fully qualified class name.
357
     *
358
     * @param string $className
359
     *
360
     * @return string
361
     */
362
    public function getClassName($className)
363
    {
364
        return $this->finder->getNamespace($className);
365
    }
366
}
367