Completed
Push — 6.7 ( 2b74e9...435624 )
by André
27:33 queued 14:29
created

Handler::bulkIndex()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
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
namespace eZ\Publish\Core\Search\Legacy\Content;
10
11
use eZ\Publish\SPI\Persistence\Content;
12
use eZ\Publish\SPI\Persistence\Content\Location;
13
use eZ\Publish\SPI\Search\Handler as SearchHandlerInterface;
14
use eZ\Publish\Core\Persistence\Legacy\Content\Mapper as ContentMapper;
15
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper as LocationMapper;
16
use eZ\Publish\Core\Search\Legacy\Content\Location\Gateway as LocationGateway;
17
use eZ\Publish\Core\Search\Legacy\Content\WordIndexer\Gateway as WordIndexerGateway;
18
use eZ\Publish\API\Repository\Exceptions\NotImplementedException;
19
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
20
use eZ\Publish\API\Repository\Values\Content\Search\SearchHit;
21
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
22
use eZ\Publish\API\Repository\Values\Content\Query;
23
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
24
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
25
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
26
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
27
use eZ\Publish\Core\Search\Legacy\Content\Mapper\FullTextMapper;
28
29
/**
30
 * The Content Search handler retrieves sets of of Content objects, based on a
31
 * set of criteria.
32
 *
33
 * The basic idea of this class is to do the following:
34
 *
35
 * 1) The find methods retrieve a recursive set of filters, which define which
36
 * content objects to retrieve from the database. Those may be combined using
37
 * boolean operators.
38
 *
39
 * 2) This recursive criterion definition is visited into a query, which limits
40
 * the content retrieved from the database. We might not be able to create
41
 * sensible queries from all criterion definitions.
42
 *
43
 * 3) The query might be possible to optimize (remove empty statements),
44
 * reduce singular and and or constructs…
45
 *
46
 * 4) Additionally we might need a post-query filtering step, which filters
47
 * content objects based on criteria, which could not be converted in to
48
 * database statements.
49
 */
50
class Handler implements SearchHandlerInterface
51
{
52
    /**
53
     * Content locator gateway.
54
     *
55
     * @var \eZ\Publish\Core\Search\Legacy\Content\Gateway
56
     */
57
    protected $gateway;
58
59
    /**
60
     * Location locator gateway.
61
     *
62
     * @var \eZ\Publish\Core\Search\Legacy\Content\Location\Gateway
63
     */
64
    protected $locationGateway;
65
66
    /**
67
     * Word indexer gateway.
68
     *
69
     * @var \eZ\Publish\Core\Search\Legacy\Content\WordIndexer\Gateway
70
     */
71
    protected $indexerGateway;
72
73
    /**
74
     * Content mapper.
75
     *
76
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Mapper
77
     */
78
    protected $contentMapper;
79
80
    /**
81
     * Location locationMapper.
82
     *
83
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper
84
     */
85
    protected $locationMapper;
86
87
    /**
88
     * Language handler.
89
     *
90
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
91
     */
92
    protected $languageHandler;
93
94
    /**
95
     * FullText mapper.
96
     *
97
     * @var \eZ\Publish\Core\Search\Legacy\Content\Mapper\FullTextMapper
98
     */
99
    protected $mapper;
100
101
    /**
102
     * Creates a new content handler.
103
     *
104
     * @param \eZ\Publish\Core\Search\Legacy\Content\Gateway $gateway
105
     * @param \eZ\Publish\Core\Search\Legacy\Content\Location\Gateway $locationGateway
106
     * @param \eZ\Publish\Core\Search\Legacy\Content\WordIndexer\Gateway $indexerGateway
107
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Mapper $contentMapper
108
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper $locationMapper
109
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
110
     * @param \eZ\Publish\Core\Search\Legacy\Content\Mapper\FullTextMapper $mapper
111
     */
112
    public function __construct(
113
        Gateway $gateway,
114
        LocationGateway $locationGateway,
115
        WordIndexerGateway $indexerGateway,
116
        ContentMapper $contentMapper,
117
        LocationMapper $locationMapper,
118
        LanguageHandler $languageHandler,
119
        FullTextMapper $mapper
120
    ) {
121
        $this->gateway = $gateway;
122
        $this->locationGateway = $locationGateway;
123
        $this->indexerGateway = $indexerGateway;
124
        $this->contentMapper = $contentMapper;
125
        $this->locationMapper = $locationMapper;
126
        $this->languageHandler = $languageHandler;
127
        $this->mapper = $mapper;
128
    }
129
130
    /**
131
     * Finds content objects for the given query.
132
     *
133
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Query criterion is not applicable to its target
134
     *
135
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
136
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
137
     *        Also used to define which field languages are loaded for the returned content.
138
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
139
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
140
     *
141
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
142
     */
143 View Code Duplication
    public function findContent(Query $query, array $languageFilter = array())
144
    {
145
        if (!isset($languageFilter['languages'])) {
146
            $languageFilter['languages'] = array();
147
        }
148
149
        if (!isset($languageFilter['useAlwaysAvailable'])) {
150
            $languageFilter['useAlwaysAvailable'] = true;
151
        }
152
153
        $start = microtime(true);
154
        $query->filter = $query->filter ?: new Criterion\MatchAll();
155
        $query->query = $query->query ?: new Criterion\MatchAll();
156
157
        // The legacy search does not know about scores, so that we just
158
        // combine the query with the filter
159
        $filter = new Criterion\LogicalAnd(array($query->query, $query->filter));
160
161
        $data = $this->gateway->find(
162
            $filter,
163
            $query->offset,
164
            $query->limit,
165
            $query->sortClauses,
166
            $languageFilter,
167
            $query->performCount
168
        );
169
170
        $result = new SearchResult();
171
        $result->time = microtime(true) - $start;
0 ignored issues
show
Documentation Bug introduced by
The property $time was declared of type integer, but microtime(true) - $start is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
172
        $result->totalCount = $data['count'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $data['count'] of type array<integer,*> is incompatible with the declared type integer|null of property $totalCount.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
173
        $contentInfoList = $this->contentMapper->extractContentInfoFromRows(
174
            $data['rows'],
175
            '',
176
            'main_tree_'
177
        );
178
179
        foreach ($contentInfoList as $index => $contentInfo) {
180
            $searchHit = new SearchHit();
181
            $searchHit->valueObject = $contentInfo;
182
            $searchHit->matchedTranslation = $this->extractMatchedLanguage(
183
                $data['rows'][$index]['language_mask'],
184
                $data['rows'][$index]['initial_language_id'],
185
                $languageFilter
186
            );
187
188
            $result->searchHits[] = $searchHit;
189
        }
190
191
        return $result;
192
    }
193
194
    protected function extractMatchedLanguage($languageMask, $mainLanguageId, $languageSettings)
195
    {
196
        foreach ($languageSettings['languages'] as $languageCode) {
197
            if ($languageMask & $this->languageHandler->loadByLanguageCode($languageCode)->id) {
198
                return $languageCode;
199
            }
200
        }
201
202
        if ($languageMask & 1 || empty($languageSettings['languages'])) {
203
            return $this->languageHandler->load($mainLanguageId)->languageCode;
204
        }
205
206
        return null;
207
    }
208
209
    /**
210
     * Performs a query for a single content object.
211
     *
212
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the object was not found by the query or due to permissions
213
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Criterion is not applicable to its target
214
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is more than than one result matching the criterions
215
     *
216
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
217
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
218
     *        Also used to define which field languages are loaded for the returned content.
219
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
220
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
221
     *
222
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
223
     */
224
    public function findSingle(Criterion $filter, array $languageFilter = array())
225
    {
226
        if (!isset($languageFilter['languages'])) {
227
            $languageFilter['languages'] = array();
228
        }
229
230
        if (!isset($languageFilter['useAlwaysAvailable'])) {
231
            $languageFilter['useAlwaysAvailable'] = true;
232
        }
233
234
        $searchQuery = new Query();
235
        $searchQuery->filter = $filter;
236
        $searchQuery->query = new Criterion\MatchAll();
237
        $searchQuery->offset = 0;
238
        $searchQuery->limit = 2; // Because we optimize away the count query below
239
        $searchQuery->performCount = true;
240
        $searchQuery->sortClauses = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array<integer,object<eZ\...tent\Query\SortClause>> of property $sortClauses.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
241
        $result = $this->findContent($searchQuery, $languageFilter);
242
243
        if (empty($result->searchHits)) {
244
            throw new NotFoundException('Content', 'findSingle() found no content for given $criterion');
245
        } elseif (isset($result->searchHits[1])) {
246
            throw new InvalidArgumentException('totalCount', 'findSingle() found more then one item for given $criterion');
247
        }
248
249
        $first = reset($result->searchHits);
250
251
        return $first->valueObject;
252
    }
253
254
    /**
255
     * @see \eZ\Publish\SPI\Search\Handler::findLocations
256
     */
257 View Code Duplication
    public function findLocations(LocationQuery $query, array $languageFilter = array())
258
    {
259
        if (!isset($languageFilter['languages'])) {
260
            $languageFilter['languages'] = array();
261
        }
262
263
        if (!isset($languageFilter['useAlwaysAvailable'])) {
264
            $languageFilter['useAlwaysAvailable'] = true;
265
        }
266
267
        $start = microtime(true);
268
        $query->filter = $query->filter ?: new Criterion\MatchAll();
269
        $query->query = $query->query ?: new Criterion\MatchAll();
270
271
        // The legacy search does not know about scores, so we just
272
        // combine the query with the filter
273
        $data = $this->locationGateway->find(
274
            new Criterion\LogicalAnd(array($query->query, $query->filter)),
275
            $query->offset,
276
            $query->limit,
277
            $query->sortClauses,
278
            $languageFilter,
279
            $query->performCount
280
        );
281
282
        $result = new SearchResult();
283
        $result->time = microtime(true) - $start;
0 ignored issues
show
Documentation Bug introduced by
The property $time was declared of type integer, but microtime(true) - $start is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
284
        $result->totalCount = $data['count'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $data['count'] of type array<integer,*> is incompatible with the declared type integer|null of property $totalCount.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
285
        $locationList = $this->locationMapper->createLocationsFromRows($data['rows']);
286
287
        foreach ($locationList as $index => $location) {
288
            $searchHit = new SearchHit();
289
            $searchHit->valueObject = $location;
290
            $searchHit->matchedTranslation = $this->extractMatchedLanguage(
291
                $data['rows'][$index]['language_mask'],
292
                $data['rows'][$index]['initial_language_id'],
293
                $languageFilter
294
            );
295
296
            $result->searchHits[] = $searchHit;
297
        }
298
299
        return $result;
300
    }
301
302
    /**
303
     * Suggests a list of values for the given prefix.
304
     *
305
     * @param string $prefix
306
     * @param string[] $fieldPaths
307
     * @param int $limit
308
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
309
     * @throws NotImplementedException
310
     */
311
    public function suggest($prefix, $fieldPaths = array(), $limit = 10, Criterion $filter = null)
312
    {
313
        throw new NotImplementedException('Suggestions are not supported by legacy search engine.');
314
    }
315
316
    /**
317
     * Indexes a content object.
318
     *
319
     * @param \eZ\Publish\SPI\Persistence\Content $content
320
     */
321
    public function indexContent(Content $content)
322
    {
323
        $fullTextValue = $this->mapper->mapContent($content);
324
325
        $this->indexerGateway->index($fullTextValue);
326
    }
327
328
    /**
329
     * Bulk index list of content objects.
330
     *
331
     * @param \eZ\Publish\SPI\Persistence\Content[] $contentList
332
     * @param callable $errorCallback (Content $content, NotFoundException $e)
333
     */
334
    public function bulkIndex(array $contentList, callable $errorCallback)
335
    {
336
        $fullTextBulkData = [];
337
        foreach ($contentList as $content) {
338
            try {
339
                $fullTextBulkData[] = $this->mapper->mapContent($content);
340
            } catch (NotFoundException $e) {
341
                $errorCallback($content, $e);
342
            }
343
        }
344
345
        $this->indexerGateway->bulkIndex($fullTextBulkData);
346
    }
347
348
    /**
349
     * @param \eZ\Publish\SPI\Persistence\Content\Location $location
350
     */
351
    public function indexLocation(Location $location)
352
    {
353
        // Not needed with Legacy Storage/Search Engine
354
    }
355
356
    /**
357
     * Deletes a content object from the index.
358
     *
359
     * @param int $contentId
360
     * @param int|null $versionId
361
     */
362
    public function deleteContent($contentId, $versionId = null)
363
    {
364
        $this->indexerGateway->remove($contentId, $versionId);
365
    }
366
367
    /**
368
     * Deletes a location from the index.
369
     *
370
     * @param mixed $locationId
371
     * @param mixed $contentId
372
     */
373
    public function deleteLocation($locationId, $contentId)
374
    {
375
        // Not needed with Legacy Storage/Search Engine
376
    }
377
378
    /**
379
     * Purges all contents from the index.
380
     */
381
    public function purgeIndex()
382
    {
383
        $this->indexerGateway->purgeIndex();
384
    }
385
386
    /**
387
     * Commits the data to the index, making it available for search.
388
     *
389
     * @param bool $flush
390
     */
391
    public function commit($flush = false)
0 ignored issues
show
Unused Code introduced by
The parameter $flush 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...
392
    {
393
        // Not needed with Legacy Storage/Search Engine
394
    }
395
}
396