Completed
Push — EZP-25088-search-handler-inter... ( f8bc81 )
by
unknown
26:40
created

Handler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 5
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
 * @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\API\Repository\Exceptions\NotImplementedException;
20
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
21
use eZ\Publish\API\Repository\Values\Content\Search\SearchHit;
22
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
23
use eZ\Publish\API\Repository\Values\Content\Query;
24
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
25
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
26
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
27
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
28
use eZ\Publish\SPI\Search\Indexer\ContentIndexer;
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, ContentIndexer
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
     * Content mapper.
69
     *
70
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Mapper
71
     */
72
    protected $contentMapper;
73
74
    /**
75
     * Location locationMapper.
76
     *
77
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper
78
     */
79
    protected $locationMapper;
80
81
    /**
82
     * Language handler.
83
     *
84
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
85
     */
86
    protected $languageHandler;
87
88
    /**
89
     * Creates a new content handler.
90
     *
91
     * @param \eZ\Publish\Core\Search\Legacy\Content\Gateway $gateway
92
     * @param \eZ\Publish\Core\Search\Legacy\Content\Location\Gateway $locationGateway
93
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Mapper $contentMapper
94
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper $locationMapper
95
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
96
     */
97
    public function __construct(
98
        Gateway $gateway,
99
        LocationGateway $locationGateway,
100
        ContentMapper $contentMapper,
101
        LocationMapper $locationMapper,
102
        LanguageHandler $languageHandler
103
    ) {
104
        $this->gateway = $gateway;
105
        $this->locationGateway = $locationGateway;
106
        $this->contentMapper = $contentMapper;
107
        $this->locationMapper = $locationMapper;
108
        $this->languageHandler = $languageHandler;
109
    }
110
111
    /**
112
     * Finds content objects for the given query.
113
     *
114
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Query criterion is not applicable to its target
115
     *
116
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
117
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
118
     *        Also used to define which field languages are loaded for the returned content.
119
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
120
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
121
     *
122
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
123
     */
124 View Code Duplication
    public function findContent(Query $query, array $languageFilter = array())
125
    {
126
        if (!isset($languageFilter['languages'])) {
127
            $languageFilter['languages'] = array();
128
        }
129
130
        if (!isset($languageFilter['useAlwaysAvailable'])) {
131
            $languageFilter['useAlwaysAvailable'] = true;
132
        }
133
134
        $start = microtime(true);
135
        $query->filter = $query->filter ?: new Criterion\MatchAll();
136
        $query->query = $query->query ?: new Criterion\MatchAll();
137
138
        if (count($query->facetBuilders)) {
139
            throw new NotImplementedException('Facets are not supported by the legacy search engine.');
140
        }
141
142
        // The legacy search does not know about scores, so that we just
143
        // combine the query with the filter
144
        $filter = new Criterion\LogicalAnd(array($query->query, $query->filter));
145
146
        $data = $this->gateway->find(
147
            $filter,
148
            $query->offset,
149
            $query->limit,
150
            $query->sortClauses,
151
            $languageFilter,
152
            $query->performCount
153
        );
154
155
        $result = new SearchResult();
156
        $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...
157
        $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...
158
        $contentInfoList = $this->contentMapper->extractContentInfoFromRows(
159
            $data['rows'],
160
            '',
161
            'main_tree_'
162
        );
163
164
        foreach ($contentInfoList as $index => $contentInfo) {
165
            $searchHit = new SearchHit();
166
            $searchHit->valueObject = $contentInfo;
167
            $searchHit->matchedTranslation = $this->extractMatchedLanguage(
168
                $data['rows'][$index]['language_mask'],
169
                $data['rows'][$index]['initial_language_id'],
170
                $languageFilter
171
            );
172
173
            $result->searchHits[] = $searchHit;
174
        }
175
176
        return $result;
177
    }
178
179
    protected function extractMatchedLanguage($languageMask, $mainLanguageId, $languageSettings)
180
    {
181
        foreach ($languageSettings['languages'] as $languageCode) {
182
            if ($languageMask & $this->languageHandler->loadByLanguageCode($languageCode)->id) {
183
                return $languageCode;
184
            }
185
        }
186
187
        if ($languageMask & 1 || empty($languageSettings['languages'])) {
188
            return $this->languageHandler->load($mainLanguageId)->languageCode;
189
        }
190
191
        return null;
192
    }
193
194
    /**
195
     * Performs a query for a single content object.
196
     *
197
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the object was not found by the query or due to permissions
198
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Criterion is not applicable to its target
199
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is more than than one result matching the criterions
200
     *
201
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
202
     * @param array $languageFilter - a map of language related filters specifying languages query will be performed on.
203
     *        Also used to define which field languages are loaded for the returned content.
204
     *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
205
     *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
206
     *
207
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo
208
     */
209
    public function findSingle(Criterion $filter, array $languageFilter = array())
210
    {
211
        if (!isset($languageFilter['languages'])) {
212
            $languageFilter['languages'] = array();
213
        }
214
215
        if (!isset($languageFilter['useAlwaysAvailable'])) {
216
            $languageFilter['useAlwaysAvailable'] = true;
217
        }
218
219
        $searchQuery = new Query();
220
        $searchQuery->filter = $filter;
221
        $searchQuery->query = new Criterion\MatchAll();
222
        $searchQuery->offset = 0;
223
        $searchQuery->limit = 2;// Because we optimize away the count query below
224
        $searchQuery->performCount = true;
225
        $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...
226
        $result = $this->findContent($searchQuery, $languageFilter);
227
228
        if (empty($result->searchHits)) {
229
            throw new NotFoundException('Content', 'findSingle() found no content for given $criterion');
230
        } elseif (isset($result->searchHits[1])) {
231
            throw new InvalidArgumentException('totalCount', 'findSingle() found more then one item for given $criterion');
232
        }
233
234
        $first = reset($result->searchHits);
235
236
        return $first->valueObject;
237
    }
238
239
    /**
240
     * @see \eZ\Publish\SPI\Search\Handler::findLocations
241
     */
242 View Code Duplication
    public function findLocations(LocationQuery $query, array $languageFilter = array())
243
    {
244
        if (!isset($languageFilter['languages'])) {
245
            $languageFilter['languages'] = array();
246
        }
247
248
        if (!isset($languageFilter['useAlwaysAvailable'])) {
249
            $languageFilter['useAlwaysAvailable'] = true;
250
        }
251
252
        $start = microtime(true);
253
        $query->filter = $query->filter ?: new Criterion\MatchAll();
254
        $query->query = $query->query ?: new Criterion\MatchAll();
255
256
        if (count($query->facetBuilders)) {
257
            throw new NotImplementedException('Facets are not supported by the legacy search engine.');
258
        }
259
260
        // The legacy search does not know about scores, so we just
261
        // combine the query with the filter
262
        $data = $this->locationGateway->find(
263
            new Criterion\LogicalAnd(array($query->query, $query->filter)),
264
            $query->offset,
265
            $query->limit,
266
            $query->sortClauses,
267
            $languageFilter,
268
            $query->performCount
269
        );
270
271
        $result = new SearchResult();
272
        $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...
273
        $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...
274
        $locationList = $this->locationMapper->createLocationsFromRows($data['rows']);
275
276
        foreach ($locationList as $index => $location) {
277
            $searchHit = new SearchHit();
278
            $searchHit->valueObject = $location;
279
            $searchHit->matchedTranslation = $this->extractMatchedLanguage(
280
                $data['rows'][$index]['language_mask'],
281
                $data['rows'][$index]['initial_language_id'],
282
                $languageFilter
283
            );
284
285
            $result->searchHits[] = $searchHit;
286
        }
287
288
        return $result;
289
    }
290
291
    /**
292
     * Suggests a list of values for the given prefix.
293
     *
294
     * @param string $prefix
295
     * @param string[] $fieldPaths
296
     * @param int $limit
297
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
298
     */
299
    public function suggest($prefix, $fieldPaths = array(), $limit = 10, Criterion $filter = null)
300
    {
301
        throw new NotImplementedException('Suggestions are not supported by legacy search engine.');
302
    }
303
304
    /**
305
     * Indexes a content object.
306
     *
307
     * @param \eZ\Publish\SPI\Persistence\Content $content
308
     */
309
    public function indexContent(Content $content)
310
    {
311
        // Not implemented in Legacy Storage Engine
312
    }
313
314
    /**
315
     * Deletes a content object from the index.
316
     *
317
     * @param int $contentId
318
     * @param int|null $versionId
319
     */
320
    public function deleteContent($contentId, $versionId = null)
321
    {
322
        // Not implemented in Legacy Storage Engine
323
    }
324
325
    /**
326
     * Deletes a location from the index.
327
     *
328
     * @param mixed $locationId
329
     * @param mixed $contentId
330
     */
331
    public function deleteLocation($locationId, $contentId)
332
    {
333
        // Not implemented in Legacy Storage Engine
334
    }
335
}
336