Completed
Pull Request — master (#500)
by
unknown
04:48
created

MetadataCollector::getDocumentMapping()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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
use ONGR\ElasticsearchBundle\Document\DocumentInterface;
16
17
/**
18
 * DocumentParser wrapper for getting bundle documents mapping.
19
 */
20
class MetadataCollector
21
{
22
    /**
23
     * @var DocumentFinder
24
     */
25
    private $finder;
26
27
    /**
28
     * @var DocumentParser
29
     */
30
    private $parser;
31
32
    /**
33
     * @var CacheProvider
34
     */
35
    private $cache = null;
36
37
    /**
38
     * @var bool
39
     */
40
    private $enableCache = false;
41
42
    /**
43
     * Bundles mappings local cache container. Could be stored as the whole bundle or as single document.
44
     * e.g. AcmeDemoBundle, AcmeDemoBundle:Product.
45
     *
46
     * @var mixed
47
     */
48
    private $mappings = [];
49
50
    /**
51
     * @param DocumentFinder $finder For finding documents.
52
     * @param DocumentParser $parser For reading document annotations.
53
     * @param CacheProvider  $cache  Cache provider to store the meta data for later use.
54
     */
55
    public function __construct($finder, $parser, $cache = null)
56
    {
57
        $this->finder = $finder;
58
        $this->parser = $parser;
59
        $this->cache = $cache;
60
61
        if ($this->cache) {
62
            $this->mappings = $this->cache->fetch('ongr.metadata.mappings');
63
        }
64
    }
65
66
    /**
67
     * Enables metadata caching.
68
     *
69
     * @param bool $enableCache
70
     */
71
    public function setEnableCache($enableCache)
72
    {
73
        $this->enableCache = $enableCache;
74
    }
75
76
    /**
77
     * Fetches bundles mapping from documents.
78
     *
79
     * @param string[] $bundles Elasticsearch manager config. You can get bundles list from 'mappings' node.
80
     * @return array
81
     */
82
    public function getMappings(array $bundles)
83
    {
84
        $output = [];
85
        foreach ($bundles as $bundle) {
86
            $mappings = $this->getBundleMapping($bundle);
87
88
            $alreadyDefinedTypes = array_intersect_key($mappings, $output);
89
            if (count($alreadyDefinedTypes)) {
90
                throw new \LogicException(
91
                    implode(',', array_keys($alreadyDefinedTypes)) .
92
                    ' type(s) already defined in other document, you can use the same ' .
93
                    'type only once in a manager definition.'
94
                );
95
            }
96
97
            $output = array_merge($output, $mappings);
98
        }
99
100
        return $output;
101
    }
102
103
    /**
104
     * Searches for documents in the bundle and tries to read them.
105
     *
106
     * @param string $name
107
     *
108
     * @return array Empty array on containing zero documents.
109
     */
110
    public function getBundleMapping($name)
111
    {
112
        if (!is_string($name)) {
113
            throw new \LogicException('getBundleMapping() in the Metadata collector expects a string argument only!');
114
        }
115
116
        if (isset($this->mappings[$name])) {
117
            return $this->mappings[$name];
118
        }
119
120
        // Checks if is mapped document or bundle.
121
        if (strpos($name, ':') !== false) {
122
            $bundleInfo = explode(':', $name);
123
            $bundle = $bundleInfo[0];
124
            $documentClass = $bundleInfo[1];
125
126
            $documents = $this->finder->getBundleDocumentPaths($bundle);
127
            $documents = array_filter(
128
                $documents,
129
                function ($document) use ($documentClass) {
130
                    if (pathinfo($document, PATHINFO_FILENAME) == $documentClass) {
131
                        return true;
132
                    }
133
                }
134
            );
135
        } else {
136
            $documents = $this->finder->getBundleDocumentPaths($name);
137
            $bundle = $name;
138
        }
139
140
        $mappings = [];
141
        $bundleNamespace = $this->finder->getBundleClass($bundle);
142
        $bundleNamespace = substr($bundleNamespace, 0, strrpos($bundleNamespace, '\\'));
0 ignored issues
show
Unused Code introduced by
$bundleNamespace is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
143
144
        if (!count($documents)) {
145
            return [];
146
        }
147
148
        // Loop through documents found in bundle.
149
        foreach ($documents as $document) {
150
            $namespace = $this->getFileNamespace($document);
151
            if (!$namespace) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $namespace of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
152
                continue;
153
            }
154
            $documentReflection = new \ReflectionClass(
155
                $namespace . '\\' . pathinfo($document, PATHINFO_FILENAME)
156
            );
157
158
            $documentMapping = $this->getDocumentReflectionMapping($documentReflection);
159
160
            if (!array_key_exists('type', $documentMapping)) {
161
                continue;
162
            }
163
164
            if (!array_key_exists($documentMapping['type'], $mappings)) {
165
                $documentMapping['bundle'] = $bundle;
166
                $mappings = array_merge($mappings, [$documentMapping['type'] => $documentMapping]);
167
            } else {
168
                throw new \LogicException(
169
                    $bundle . ' has 2 same type names defined in the documents. ' .
170
                    'Type names must be unique!'
171
                );
172
            }
173
        }
174
175
        $this->cacheBundle($name, $mappings);
176
177
        return $mappings;
178
    }
179
180
    /**
181
     * @param array $manager
182
     *
183
     * @return array
184
     */
185
    public function getManagerTypes($manager)
186
    {
187
        $mapping = $this->getMappings($manager['mappings']);
188
189
        return array_keys($mapping);
190
    }
191
192
    /**
193
     * Resolves document elasticsearch type, use format: SomeBarBundle:AcmeDocument.
194
     *
195
     * @param string $document
196
     *
197
     * @return string
198
     */
199
    public function getDocumentType($document)
200
    {
201
        $mapping = $this->getMappingByNamespace($document);
202
203
        return $mapping['type'];
204
    }
205
206
    /**
207
     * Retrieves document mapping by namespace.
208
     *
209
     * @param string $namespace Document namespace.
210
     *
211
     * @return array|null
212
     */
213
    public function getMappingByNamespace($namespace)
214
    {
215
        if (isset($this->mappings[$namespace])) {
216
            return $this->mappings[$namespace];
217
        }
218
219
        $mapping = $this->getDocumentReflectionMapping(new \ReflectionClass($this->finder->getNamespace($namespace)));
220
        $this->cacheBundle($namespace, $mapping);
221
222
        return $mapping;
223
    }
224
225
    /**
226
     * Retrieves prepared mapping to sent to the elasticsearch client.
227
     *
228
     * @param array $bundles Manager config.
229
     *
230
     * @return array
231
     */
232
    public function getClientMapping(array $bundles)
233
    {
234
        /** @var array $typesMapping Array of filtered mappings for the elasticsearch client*/
235
        $typesMapping = [];
236
237
        /** @var array $mappings All mapping info */
238
        $mappings = $this->getMappings($bundles);
239
240
        foreach ($mappings as $type => $mapping) {
241
            if (!empty($mapping['properties'])) {
242
                $typesMapping[$type] = array_filter(
243
                    array_merge(
244
                        ['properties' => $mapping['properties']],
245
                        $mapping['fields']
246
                    ),
247
                    function ($value) {
248
                        return (bool)$value || is_bool($value);
249
                    }
250
                );
251
            }
252
        }
253
254
        return $typesMapping;
255
    }
256
257
    /**
258
     * Gathers annotation data from class.
259
     *
260
     * @param \ReflectionClass $reflectionClass Document reflection class to read mapping from.
261
     *
262
     * @return array
263
     */
264
    private function getDocumentReflectionMapping(\ReflectionClass $reflectionClass)
265
    {
266
        return $this->parser->parse($reflectionClass);
267
    }
268
269
    /**
270
     * Retrieves mapping from document.
271
     *
272
     * @param DocumentInterface $document
273
     *
274
     * @return array
275
     */
276
    public function getDocumentMapping(DocumentInterface $document)
277
    {
278
        return $this->getDocumentReflectionMapping(new \ReflectionObject($document));
279
    }
280
281
    /**
282
     * Returns mapping with metadata.
283
     *
284
     * @param string $namespace Bundle or document namespace.
285
     *
286
     * @return array
287
     */
288
    public function getMapping($namespace)
289
    {
290
        if (strpos($namespace, ':') === false) {
291
            return $this->getBundleMapping($namespace);
292
        }
293
        $mapping = $this->getMappingByNamespace($namespace);
294
295
        return $mapping === null ? [] : $mapping;
296
    }
297
298
    /**
299
     * Adds metadata information to the cache storage.
300
     *
301
     * @param string $bundle
302
     * @param array  $mapping
303
     */
304
    private function cacheBundle($bundle, array $mapping)
305
    {
306
        if ($this->enableCache) {
307
            $this->mappings[$bundle] = $mapping;
308
            $this->cache->save('ongr.metadata.mappings', $this->mappings);
309
        }
310
    }
311
312
    /**
313
     * returns the namespace declared at the start of a file
314
     * @param $filepath
315
     * @return bool
316
     */
317
    private function getFileNamespace($filepath) {
318
        $exists = preg_match('/<\?php.+?namespace ([^;]+)/si', file_get_contents($filepath), $match);
319
320
        if ($exists && isset($match[1])) {
321
            return $match[1];
322
        }
323
        return false;
324
    }
325
}
326