Completed
Push — fulltext_indexing_test ( 07c79e...9fd5b8 )
by André
51:32 queued 25:03
created

Handler::findSingle()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 29
rs 8.439
cc 5
eloc 19
nc 12
nop 2
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\Legacy\Content;
12
13
use eZ\Publish\SPI\Persistence\Content;
14
use eZ\Publish\SPI\Persistence\Content\Location;
15
use eZ\Publish\SPI\Search\Handler as SearchHandlerInterface;
16
use eZ\Publish\Core\Persistence\Legacy\Content\Mapper as ContentMapper;
17
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper as LocationMapper;
18
use eZ\Publish\Core\Search\Legacy\Content\Location\Gateway as LocationGateway;
19
use eZ\Publish\Core\Search\Legacy\Content\WordIndexer\Gateway as WordIndexerGateway;
20
use eZ\Publish\API\Repository\Exceptions\NotImplementedException;
21
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
22
use eZ\Publish\API\Repository\Values\Content\Search\SearchHit;
23
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
24
use eZ\Publish\API\Repository\Values\Content\Query;
25
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
26
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
27
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
28
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
29
30
/**
31
 * The Content Search handler retrieves sets of of Content objects, based on a
32
 * set of criteria.
33
 *
34
 * The basic idea of this class is to do the following:
35
 *
36
 * 1) The find methods retrieve a recursive set of filters, which define which
37
 * content objects to retrieve from the database. Those may be combined using
38
 * boolean operators.
39
 *
40
 * 2) This recursive criterion definition is visited into a query, which limits
41
 * the content retrieved from the database. We might not be able to create
42
 * sensible queries from all criterion definitions.
43
 *
44
 * 3) The query might be possible to optimize (remove empty statements),
45
 * reduce singular and and or constructs…
46
 *
47
 * 4) Additionally we might need a post-query filtering step, which filters
48
 * content objects based on criteria, which could not be converted in to
49
 * database statements.
50
 */
51
class Handler implements SearchHandlerInterface
52
{
53
    /**
54
     * Content locator gateway.
55
     *
56
     * @var \eZ\Publish\Core\Search\Legacy\Content\Gateway
57
     */
58
    protected $gateway;
59
60
    /**
61
     * Location locator gateway.
62
     *
63
     * @var \eZ\Publish\Core\Search\Legacy\Content\Location\Gateway
64
     */
65
    protected $locationGateway;
66
67
    /**
68
     * Word indexer gateway.
69
     *
70
     * @var \eZ\Publish\Core\Search\Legacy\Content\WordIndexer\Gateway
71
     */
72
    protected $indexerGateway;
73
74
    /**
75
     * Content mapper.
76
     *
77
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Mapper
78
     */
79
    protected $contentMapper;
80
81
    /**
82
     * Location locationMapper.
83
     *
84
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper
85
     */
86
    protected $locationMapper;
87
88
    /**
89
     * Language handler.
90
     *
91
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
92
     */
93
    protected $languageHandler;
94
95
    /**
96
     * Creates a new content handler.
97
     *
98
     * @param \eZ\Publish\Core\Search\Legacy\Content\Gateway $gateway
99
     * @param \eZ\Publish\Core\Search\Legacy\Content\Location\Gateway $locationGateway
100
     * @param \eZ\Publish\Core\Search\Legacy\Content\WordIndexer\Gateway $indexerGateway
101
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Mapper $contentMapper
102
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper $locationMapper
103
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
104
     */
105
    public function __construct(
106
        Gateway $gateway,
107
        LocationGateway $locationGateway,
108
        WordIndexerGateway $indexerGateway,
109
        ContentMapper $contentMapper,
110
        LocationMapper $locationMapper,
111
        LanguageHandler $languageHandler
112
    ) {
113
        $this->gateway = $gateway;
114
        $this->locationGateway = $locationGateway;
115
        $this->indexerGateway = $indexerGateway;
116
        $this->contentMapper = $contentMapper;
117
        $this->locationMapper = $locationMapper;
118
        $this->languageHandler = $languageHandler;
119
    }
120
121
    /**
122
     * Finds content objects for the given query.
123
     *
124
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Query criterion is not applicable to its target
125
     *
126
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
127
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
128
     *        Also used to define which field languages are loaded for the returned content.
129
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
130
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
131
     *
132
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
133
     */
134 View Code Duplication
    public function findContent(Query $query, array $languageFilter = array())
135
    {
136
        if (!isset($languageFilter['languages'])) {
137
            $languageFilter['languages'] = array();
138
        }
139
140
        if (!isset($languageFilter['useAlwaysAvailable'])) {
141
            $languageFilter['useAlwaysAvailable'] = true;
142
        }
143
144
        $start = microtime(true);
145
        $query->filter = $query->filter ?: new Criterion\MatchAll();
146
        $query->query = $query->query ?: new Criterion\MatchAll();
147
148
        if (count($query->facetBuilders)) {
149
            throw new NotImplementedException('Facets are not supported by the legacy search engine.');
150
        }
151
152
        // The legacy search does not know about scores, so that we just
153
        // combine the query with the filter
154
        $filter = new Criterion\LogicalAnd(array($query->query, $query->filter));
155
156
        $data = $this->gateway->find(
157
            $filter,
158
            $query->offset,
159
            $query->limit,
160
            $query->sortClauses,
161
            $languageFilter,
162
            $query->performCount
163
        );
164
165
        $result = new SearchResult();
166
        $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...
167
        $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...
168
        $contentInfoList = $this->contentMapper->extractContentInfoFromRows(
169
            $data['rows'],
170
            '',
171
            'main_tree_'
172
        );
173
174
        foreach ($contentInfoList as $index => $contentInfo) {
175
            $searchHit = new SearchHit();
176
            $searchHit->valueObject = $contentInfo;
177
            $searchHit->matchedTranslation = $this->extractMatchedLanguage(
178
                $data['rows'][$index]['language_mask'],
179
                $data['rows'][$index]['initial_language_id'],
180
                $languageFilter
181
            );
182
183
            $result->searchHits[] = $searchHit;
184
        }
185
186
        return $result;
187
    }
188
189
    protected function extractMatchedLanguage($languageMask, $mainLanguageId, $languageSettings)
190
    {
191
        foreach ($languageSettings['languages'] as $languageCode) {
192
            if ($languageMask & $this->languageHandler->loadByLanguageCode($languageCode)->id) {
193
                return $languageCode;
194
            }
195
        }
196
197
        if ($languageMask & 1 || empty($languageSettings['languages'])) {
198
            return $this->languageHandler->load($mainLanguageId)->languageCode;
199
        }
200
201
        return null;
202
    }
203
204
    /**
205
     * Performs a query for a single content object.
206
     *
207
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the object was not found by the query or due to permissions
208
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Criterion is not applicable to its target
209
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is more than than one result matching the criterions
210
     *
211
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
212
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
213
     *        Also used to define which field languages are loaded for the returned content.
214
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
215
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
216
     *
217
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
218
     */
219
    public function findSingle(Criterion $filter, array $languageFilter = array())
220
    {
221
        if (!isset($languageFilter['languages'])) {
222
            $languageFilter['languages'] = array();
223
        }
224
225
        if (!isset($languageFilter['useAlwaysAvailable'])) {
226
            $languageFilter['useAlwaysAvailable'] = true;
227
        }
228
229
        $searchQuery = new Query();
230
        $searchQuery->filter = $filter;
231
        $searchQuery->query = new Criterion\MatchAll();
232
        $searchQuery->offset = 0;
233
        $searchQuery->limit = 2;// Because we optimize away the count query below
234
        $searchQuery->performCount = true;
235
        $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...
236
        $result = $this->findContent($searchQuery, $languageFilter);
237
238
        if (empty($result->searchHits)) {
239
            throw new NotFoundException('Content', 'findSingle() found no content for given $criterion');
240
        } elseif (isset($result->searchHits[1])) {
241
            throw new InvalidArgumentException('totalCount', 'findSingle() found more then one item for given $criterion');
242
        }
243
244
        $first = reset($result->searchHits);
245
246
        return $first->valueObject;
247
    }
248
249
    /**
250
     * @see \eZ\Publish\SPI\Search\Handler::findLocations
251
     */
252 View Code Duplication
    public function findLocations(LocationQuery $query, array $languageFilter = array())
253
    {
254
        if (!isset($languageFilter['languages'])) {
255
            $languageFilter['languages'] = array();
256
        }
257
258
        if (!isset($languageFilter['useAlwaysAvailable'])) {
259
            $languageFilter['useAlwaysAvailable'] = true;
260
        }
261
262
        $start = microtime(true);
263
        $query->filter = $query->filter ?: new Criterion\MatchAll();
264
        $query->query = $query->query ?: new Criterion\MatchAll();
265
266
        if (count($query->facetBuilders)) {
267
            throw new NotImplementedException('Facets are not supported by the legacy search engine.');
268
        }
269
270
        // The legacy search does not know about scores, so we just
271
        // combine the query with the filter
272
        $data = $this->locationGateway->find(
273
            new Criterion\LogicalAnd(array($query->query, $query->filter)),
274
            $query->offset,
275
            $query->limit,
276
            $query->sortClauses,
277
            $languageFilter,
278
            $query->performCount
279
        );
280
281
        $result = new SearchResult();
282
        $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...
283
        $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...
284
        $locationList = $this->locationMapper->createLocationsFromRows($data['rows']);
285
286
        foreach ($locationList as $index => $location) {
287
            $searchHit = new SearchHit();
288
            $searchHit->valueObject = $location;
289
            $searchHit->matchedTranslation = $this->extractMatchedLanguage(
290
                $data['rows'][$index]['language_mask'],
291
                $data['rows'][$index]['initial_language_id'],
292
                $languageFilter
293
            );
294
295
            $result->searchHits[] = $searchHit;
296
        }
297
298
        return $result;
299
    }
300
301
    /**
302
     * Suggests a list of values for the given prefix.
303
     *
304
     * @param string $prefix
305
     * @param string[] $fieldPaths
306
     * @param int $limit
307
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
308
     */
309
    public function suggest($prefix, $fieldPaths = array(), $limit = 10, Criterion $filter = null)
310
    {
311
        throw new NotImplementedException('Suggestions are not supported by legacy search engine.');
312
    }
313
314
    /**
315
     * Indexes a content object.
316
     *
317
     * @param \eZ\Publish\SPI\Persistence\Content $content
318
     */
319
    public function indexContent(Content $content)
320
    {
321
        $this->indexerGateway->index($content);
322
    }
323
324
    /**
325
     * @param \eZ\Publish\SPI\Persistence\Content\Location $location
326
     */
327
    public function indexLocation(Location $location)
328
    {
329
        // Not needed with Legacy Storage/Search Engine
330
    }
331
332
    /**
333
     * Deletes a content object from the index.
334
     *
335
     * @param int $contentId
336
     * @param int|null $versionId
337
     */
338
    public function deleteContent($contentId, $versionId = null)
339
    {
340
        $this->indexerGateway->remove($contentId, $versionId);
341
    }
342
343
    /**
344
     * Deletes a location from the index.
345
     *
346
     * @param mixed $locationId
347
     * @param mixed $contentId
348
     */
349
    public function deleteLocation($locationId, $contentId)
350
    {
351
        // Not needed with Legacy Storage/Search Engine
352
    }
353
}
354