Completed
Push — feature-EZP-25696 ( 52d929...5f47d3 )
by André
23:51
created

Handler   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 331
Duplicated Lines 5.44 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 2
Bugs 1 Features 0
Metric Value
dl 18
loc 331
rs 10
c 2
b 1
f 0
wmc 25
lcom 1
cbo 11

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A findContent() 9 9 3
A findSingle() 0 22 3
A findLocations() 9 9 3
A suggest() 0 4 1
A indexContent() 0 6 1
A bulkIndexContent() 0 9 2
A indexLocation() 0 6 1
A bulkIndexLocations() 0 9 2
A deleteContent() 0 12 2
A deleteLocation() 0 62 3
A purgeIndex() 0 5 1
A setCommit() 0 4 1
A flush() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/**
4
 * File containing the Content Search handler class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 *
9
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Search\Elasticsearch\Content;
12
13
use eZ\Publish\API\Repository\Values\Content\Query;
14
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
15
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
16
use eZ\Publish\SPI\Persistence\Content;
17
use eZ\Publish\SPI\Persistence\Content\Type;
18
use eZ\Publish\SPI\Search\Handler as SearchHandlerInterface;
19
use eZ\Publish\SPI\Persistence\Content\Location;
20
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
21
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
22
23
class Handler implements SearchHandlerInterface
24
{
25
    /**
26
     * @var \eZ\Publish\Core\Search\Elasticsearch\Content\Gateway
27
     */
28
    protected $gateway;
29
30
    /**
31
     * @var \eZ\Publish\Core\Search\Elasticsearch\Content\Gateway
32
     */
33
    protected $locationGateway;
34
35
    /**
36
     * @var \eZ\Publish\Core\Search\Elasticsearch\Content\MapperInterface
37
     */
38
    protected $mapper;
39
40
    /**
41
     * Search result extractor.
42
     *
43
     * @var \eZ\Publish\Core\Search\Elasticsearch\Content\Extractor
44
     */
45
    protected $extractor;
46
47
    /**
48
     * Identifier of Content document type in the search backend.
49
     *
50
     * @var string
51
     */
52
    protected $contentDocumentTypeIdentifier;
53
54
    /**
55
     * Identifier of Location document type in the search backend.
56
     *
57
     * @var string
58
     */
59
    protected $locationDocumentTypeIdentifier;
60
61
    public function __construct(
62
        Gateway $gateway,
63
        Gateway $locationGateway,
64
        MapperInterface $mapper,
65
        Extractor $extractor,
66
        $contentDocumentTypeIdentifier,
67
        $locationDocumentTypeIdentifier
68
    ) {
69
        $this->gateway = $gateway;
70
        $this->locationGateway = $locationGateway;
71
        $this->mapper = $mapper;
72
        $this->extractor = $extractor;
73
        $this->contentDocumentTypeIdentifier = $contentDocumentTypeIdentifier;
74
        $this->locationDocumentTypeIdentifier = $locationDocumentTypeIdentifier;
75
    }
76
77
    /**
78
     * Finds content objects for the given query.
79
     *
80
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Query criterion is not applicable to its target
81
     *
82
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
83
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
84
     *        Also used to define which field languages are loaded for the returned content.
85
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
86
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
87
     *
88
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
89
     */
90 View Code Duplication
    public function findContent(Query $query, array $languageFilter = array())
91
    {
92
        $query->filter = $query->filter ?: new Criterion\MatchAll();
93
        $query->query = $query->query ?: new Criterion\MatchAll();
94
95
        $data = $this->gateway->find($query, $this->contentDocumentTypeIdentifier, $languageFilter);
96
97
        return $this->extractor->extract($data);
98
    }
99
100
    /**
101
     * Performs a query for a single content object.
102
     *
103
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the object was not found by the query or due to permissions
104
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Criterion is not applicable to its target
105
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is more than than one result matching the criterions
106
     *
107
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
108
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
109
     *        Also used to define which field languages are loaded for the returned content.
110
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
111
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
112
     *
113
     * @return \eZ\Publish\SPI\Persistence\Content
114
     */
115
    public function findSingle(Criterion $filter, array $languageFilter = array())
116
    {
117
        $query = new Query();
118
        $query->filter = $filter;
119
        $query->offset = 0;
120
        $query->limit = 1;
121
        $result = $this->findContent($query, $languageFilter);
122
123
        if (!$result->totalCount) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result->totalCount of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
124
            throw new NotFoundException(
125
                'Content',
126
                'findSingle() found no content for given $filter'
127
            );
128
        } elseif ($result->totalCount > 1) {
129
            throw new InvalidArgumentException(
130
                'totalCount',
131
                'findSingle() found more then one item for given $filter'
132
            );
133
        }
134
135
        return $result->searchHits[0]->valueObject;
136
    }
137
138
    /**
139
     * Finds Locations for the given $query.
140
     *
141
     * @param \eZ\Publish\API\Repository\Values\Content\LocationQuery $query
142
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
143
     *        Also used to define which field languages are loaded for the returned content.
144
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
145
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
146
     *
147
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
148
     */
149 View Code Duplication
    public function findLocations(LocationQuery $query, array $languageFilter = array())
150
    {
151
        $query->filter = $query->filter ?: new Criterion\MatchAll();
152
        $query->query = $query->query ?: new Criterion\MatchAll();
153
154
        $data = $this->locationGateway->find($query, $this->locationDocumentTypeIdentifier);
155
156
        return $this->extractor->extract($data);
157
    }
158
159
    /**
160
     * Suggests a list of values for the given prefix.
161
     *
162
     * @param string $prefix
163
     * @param string[] $fieldPaths
164
     * @param int $limit
165
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
166
     */
167
    public function suggest($prefix, $fieldPaths = array(), $limit = 10, Criterion $filter = null)
168
    {
169
        // TODO: Implement suggest() method.
170
    }
171
172
    /**
173
     * Indexes a content object.
174
     *
175
     * @param \eZ\Publish\SPI\Persistence\Content $content
176
     */
177
    public function indexContent(Content $content)
178
    {
179
        $document = $this->mapper->mapContent($content);
180
181
        $this->gateway->index($document);
182
    }
183
184
    /**
185
     * Indexes several content objects.
186
     *
187
     * @todo: This function and setCommit() is needed for Persistence\Solr for test speed but not part
188
     *       of interface for the reason described in Solr\Content\Search\Gateway\Native::bulkIndexContent
189
     *       Short: Bulk handling should be properly designed before added to the interface.
190
     *
191
     * @param \eZ\Publish\SPI\Persistence\Content[] $contentObjects
192
     */
193
    public function bulkIndexContent(array $contentObjects)
194
    {
195
        $documents = array();
196
        foreach ($contentObjects as $content) {
197
            $documents[] = $this->mapper->mapContent($content);
198
        }
199
200
        $this->gateway->bulkIndex($documents);
201
    }
202
203
    /**
204
     * Indexes a Location in the index storage.
205
     *
206
     * @param \eZ\Publish\SPI\Persistence\Content\Location $location
207
     */
208
    public function indexLocation(Location $location)
209
    {
210
        $document = $this->mapper->mapLocation($location);
211
212
        $this->gateway->index($document);
213
    }
214
215
    /**
216
     * Indexes several Locations.
217
     *
218
     * @todo: This function and setCommit() is needed for Persistence\Solr for test speed but not part
219
     *       of interface for the reason described in Solr\Content\Search\Gateway\Native::bulkIndexContent
220
     *       Short: Bulk handling should be properly designed before added to the interface.
221
     *
222
     * @param \eZ\Publish\SPI\Persistence\Content\Location[] $locations
223
     */
224
    public function bulkIndexLocations(array $locations)
225
    {
226
        $documents = array();
227
        foreach ($locations as $location) {
228
            $documents[] = $this->mapper->mapLocation($location);
229
        }
230
231
        $this->gateway->bulkIndex($documents);
232
    }
233
234
    /**
235
     * Deletes a content object from the index.
236
     *
237
     * @param int $contentId
238
     * @param int|null $versionId
239
     */
240
    public function deleteContent($contentId, $versionId = null)
241
    {
242
        // 1. Delete the Content
243
        if ($versionId === null) {
244
            $this->gateway->deleteByQuery(json_encode(['query' => ['match' => ['_id' => $contentId]]]), $this->contentDocumentTypeIdentifier);
245
        } else {
246
            $this->gateway->delete($contentId, $this->contentDocumentTypeIdentifier);
247
        }
248
249
        // 2. Delete all Content's Locations
250
        $this->gateway->deleteByQuery(json_encode(['query' => ['match' => ['content_id_id' => $contentId]]]), $this->locationDocumentTypeIdentifier);
251
    }
252
253
    /**
254
     * Deletes a location from the index.
255
     *
256
     * @todo When we support Location-less Content, we will have to reindex instead of removing
257
     * @todo Should we not already support the above?
258
     * @todo The subtree could potentially be huge, so this implementation should scroll reindex
259
     *
260
     * @param mixed $locationId
261
     * @param mixed $contentId @todo Make use of this, or remove if not needed.
262
     */
263
    public function deleteLocation($locationId, $contentId)
264
    {
265
        // 1. Update (reindex) all Content in the subtree with additional Location(s) outside of it
266
        $ast = array(
267
            'filter' => array(
268
                'and' => array(
269
                    0 => array(
270
                        'nested' => array(
271
                            'path' => 'locations_doc',
272
                            'filter' => array(
273
                                'regexp' => array(
274
                                    'locations_doc.path_string_id' => ".*/{$locationId}/.*",
275
                                ),
276
                            ),
277
                        ),
278
                    ),
279
                    1 => array(
280
                        'nested' => array(
281
                            'path' => 'locations_doc',
282
                            'filter' => array(
283
                                'regexp' => array(
284
                                    'locations_doc.path_string_id' => array(
285
                                        // Matches anything (@) and (&) not (~) <expression>
286
                                        'value' => "@&~(.*/{$locationId}/.*)",
287
                                        'flags' => 'INTERSECTION|COMPLEMENT|ANYSTRING',
288
                                    ),
289
                                ),
290
                            ),
291
                        ),
292
                    ),
293
                ),
294
            ),
295
        );
296
297
        $response = $this->gateway->findRaw(json_encode($ast), $this->contentDocumentTypeIdentifier);
298
        $result = json_decode($response->body);
299
300
        $documents = array();
301
        foreach ($result->hits->hits as $hit) {
302
            $documents[] = $this->mapper->mapContentById($hit->_id);
303
        }
304
305
        $this->gateway->bulkIndex($documents);
306
307
        // 2. Delete all Content in the subtree with no other Location(s) outside of it
308
        $ast['filter']['and'][1] = array(
309
            'not' => $ast['filter']['and'][1],
310
        );
311
        $ast = array(
312
            'query' => array(
313
                'filtered' => $ast,
314
            ),
315
        );
316
317
        $response = $this->gateway->findRaw(json_encode($ast), $this->contentDocumentTypeIdentifier);
318
        $documentsToDelete = json_decode($response->body);
319
320
        foreach ($documentsToDelete->hits->hits as $documentToDelete) {
321
            $this->gateway->deleteByQuery(json_encode(['query' => ['match' => ['_id' => $documentToDelete->_id]]]), $this->contentDocumentTypeIdentifier);
322
            $this->gateway->deleteByQuery(json_encode(['query' => ['match' => ['content_id_id' => $documentToDelete->_id]]]), $this->locationDocumentTypeIdentifier);
323
        }
324
    }
325
326
    /**
327
     * Purges all contents from the index.
328
     *
329
     * @todo: Make this public API?
330
     */
331
    public function purgeIndex()
332
    {
333
        $this->gateway->purgeIndex($this->contentDocumentTypeIdentifier);
334
        $this->gateway->purgeIndex($this->locationDocumentTypeIdentifier);
335
    }
336
337
    /**
338
     * Set if index/delete actions should commit or if several actions is to be expected.
339
     *
340
     * This should be set to false before group of actions and true before the last one
341
     *
342
     * @param bool $commit
343
     */
344
    public function setCommit($commit)
0 ignored issues
show
Unused Code introduced by
The parameter $commit is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
345
    {
346
        //$this->gateway->setCommit( $commit );
347
    }
348
349
    public function flush()
350
    {
351
        $this->gateway->flush();
352
    }
353
}
354