Passed
Push — master ( ab917e...27e7e9 )
by Gaetano
05:46
created

QueryBasedMatcher   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 345
Duplicated Lines 0 %

Test Coverage

Coverage 50.94%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 216
dl 0
loc 345
ccs 81
cts 159
cp 0.5094
rs 3.12
c 2
b 0
f 0
wmc 66

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 2
D getQueryCriterion() 0 138 40
D getSortClauses() 0 66 18
A getSearchService() 0 3 1
A sortOrder2Hash() 0 6 2
A hash2SortOrder() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like QueryBasedMatcher often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryBasedMatcher, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Matcher;
4
5
use eZ\Publish\API\Repository\Values\Content\Query;
6
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
7
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Operator;
8
use eZ\Publish\API\Repository\Repository;
9
use Kaliop\eZMigrationBundle\API\KeyMatcherInterface;
10
use Kaliop\eZMigrationBundle\API\Exception\InvalidSortConditionsException;
11
use Kaliop\eZMigrationBundle\API\Exception\InvalidMatchConditionsException;
12
13
/**
14
 * @todo extend to allow matching by modifier, language code, content_type_group_id
15
 */
16
abstract class QueryBasedMatcher extends RepositoryMatcher
17
{
18
    const MATCH_CONTENT_ID = 'content_id';
19
    const MATCH_LOCATION_ID = 'location_id';
20
    const MATCH_CONTENT_REMOTE_ID = 'content_remote_id';
21
    const MATCH_LOCATION_REMOTE_ID = 'location_remote_id';
22
    const MATCH_ATTRIBUTE = 'attribute';
23
    const MATCH_CONTENT_TYPE_ID = 'contenttype_id';
24
    const MATCH_CONTENT_TYPE_IDENTIFIER = 'contenttype_identifier';
25
    const MATCH_CREATION_DATE = 'creation_date';
26
    const MATCH_GROUP = 'group';
27
    const MATCH_LANGUAGE_CODE = 'lang';
28
    const MATCH_MODIFICATION_DATE = 'modification_date';
29
    const MATCH_OBJECT_STATE = 'object_state';
30
    const MATCH_OWNER = 'owner';
31
    const MATCH_PARENT_LOCATION_ID = 'parent_location_id';
32
    const MATCH_PARENT_LOCATION_REMOTE_ID = 'parent_location_remote_id';
33
    const MATCH_SECTION = 'section';
34
    const MATCH_SUBTREE = 'subtree';
35
    const MATCH_VISIBILITY = 'visibility';
36
37
    const SORT_CONTENT_ID = 'content_id';
38
    const SORT_CONTENT_NAME = 'name';
39
    const SORT_DATE_MODIFIED = 'modified';
40
    const SORT_DATE_PUBLISHED = 'published';
41
    const SORT_LOCATION_DEPTH = 'depth';
42
    const SORT_LOCATION_ID = 'node_id';
43
    const SORT_LOCATION_ISMAIN = 'is_main';
44
    const SORT_LOCATION_PATH = 'path';
45
    const SORT_LOCATION_PRIORITY = 'priority';
46
    const SORT_LOCATION_VISIBILITY = 'visibility';
47
    const SORT_SECTION_IDENTIFIER = 'section_identifier';
48
    const SORT_SECTION_NAME = 'section_name';
49
50
    // useful f.e. when talking to Solr, which defaults to java integers for max nr of items for queries
51
    const INT_MAX_16BIT = 2147483647;
52
53
    static protected $operatorsMap = array(
54
        'eq' => Operator::EQ,
55
        'gt' => Operator::GT,
56
        'gte' => Operator::GTE,
57
        'lt' => Operator::LT,
58
        'lte' => Operator::LTE,
59
        'in' => Operator::IN,
60
        'between' => Operator::BETWEEN,
61
        'like' => Operator::LIKE,
62
        'contains' => Operator::CONTAINS,
63
        Operator::EQ => Operator::EQ,
64
        Operator::GT => Operator::GT,
65
        Operator::GTE => Operator::GTE,
66
        Operator::LT => Operator::LT,
67
        Operator::LTE => Operator::LTE,
68
    );
69
70
    /** @var  KeyMatcherInterface $groupMatcher */
71
    protected $groupMatcher;
72
    /** @var  KeyMatcherInterface $sectionMatcher */
73
    protected $sectionMatcher;
74
    /** @var  KeyMatcherInterface $stateMatcher */
75
    protected $stateMatcher;
76
    /** @var  KeyMatcherInterface $userMatcher */
77
    protected $userMatcher;
78
    /** @var int $queryLimit */
79
    protected $queryLimit;
80
81
    /**
82
     * @param Repository $repository
83
     * @param KeyMatcherInterface $groupMatcher
84
     * @param KeyMatcherInterface $sectionMatcher
85
     * @param KeyMatcherInterface $stateMatcher
86
     * @param KeyMatcherInterface $userMatcher
87 96
     * @param int $queryLimit passed to the repo as max. number of results to fetch. Important to avoid SOLR errors
88
     * @todo inject the services needed, not the whole repository
89
     */
90
    public function __construct(Repository $repository, KeyMatcherInterface $groupMatcher = null,
91 96
        KeyMatcherInterface $sectionMatcher = null, KeyMatcherInterface $stateMatcher = null,
92 96
        KeyMatcherInterface $userMatcher = null, $queryLimit = null)
93 96
    {
94 96
        parent::__construct($repository);
95 96
        $this->groupMatcher = $groupMatcher;
96 96
        $this->sectionMatcher = $sectionMatcher;
97
        $this->stateMatcher = $stateMatcher;
98
        $this->userMatcher = $userMatcher;
99
100
        if ($queryLimit !== null) {
101
            $this->queryLimit = (int)$queryLimit;
102
        } else {
103
            $this->queryLimit = self::INT_MAX_16BIT;
104 13
        }
105
    }
106 13
107 13
    /**
108
     * @param $key
109
     * @param $values
110
     * @return mixed should it be \eZ\Publish\API\Repository\Values\Content\Query\CriterionInterface ?
111 13
     * @throws InvalidMatchConditionsException for unsupported keys
112 3
     */
113
    protected function getQueryCriterion($key, $values)
114 13
    {
115
        if (!is_array($values)) {
116 5
            $values = array($values);
117
        }
118 11
119 1
        switch ($key) {
120
            case self::MATCH_CONTENT_ID:
121 11
                return new Query\Criterion\ContentId($values);
122 1
123
            case self::MATCH_LOCATION_ID:
124 11
                // NB: seems to cause problems with EZP 2014.3
125 1
                return new Query\Criterion\LocationId(reset($values));
126 1
127 1
            case self::MATCH_CONTENT_REMOTE_ID:
128 1
                return new Query\Criterion\RemoteId($values);
129 1
130
            case self::MATCH_LOCATION_REMOTE_ID:
131
                return new Query\Criterion\LocationRemoteId($values);
132 1
133
            case self::MATCH_ATTRIBUTE:
134 11
                $spec = reset($values);
135 10
                $attribute = key($values);
136 3
                $match = reset($spec);
137
                $operator = key($spec);
138 10
                if (!isset(self::$operatorsMap[$operator])) {
139 8
                    throw new InvalidMatchConditionsException("Can not use '$operator' as comparison operator for attributes");
140 10
                }
141
                return new Query\Criterion\Field($attribute, self::$operatorsMap[$operator], $match);
142 2
143 1
            case 'content_type_id':
144 1
            case self::MATCH_CONTENT_TYPE_ID:
145 1
                return new Query\Criterion\ContentTypeId($values);
146
147
            case 'content_type_identifier':
148 1
            case self::MATCH_CONTENT_TYPE_IDENTIFIER:
149
                return new Query\Criterion\ContentTypeIdentifier($values);
150 2
151
            case self::MATCH_CREATION_DATE:
152
                $match = reset($values);
153
                $operator = key($values);
154
                if (!isset(self::$operatorsMap[$operator])) {
155
                    throw new InvalidMatchConditionsException("Can not use '$operator' as comparison operator for dates");
156
                }
157
                return new Query\Criterion\DateMetadata(Query\Criterion\DateMetadata::CREATED, self::$operatorsMap[$operator], $match);
158 2
159 1
            case self::MATCH_GROUP:
160
                foreach($values as &$value) {
161 2
                    if (!ctype_digit($value)) {
162
                        $value = $this->groupMatcher->matchOneByKey($value)->id;
163
                    }
164
                }
165
                return new Query\Criterion\UserMetadata(Query\Criterion\UserMetadata::GROUP, Operator::IN, $values);
166
167
            case self::MATCH_LANGUAGE_CODE:
168
                return new Query\Criterion\LanguageCode($values);
169 2
170
            case self::MATCH_MODIFICATION_DATE:
171
                $match = reset($values);
172
                $operator = key($values);
173
                if (!isset(self::$operatorsMap[$operator])) {
174
                    throw new InvalidMatchConditionsException("Can not use '$operator' as comparison operator for dates");
175
                }
176
                return new Query\Criterion\DateMetadata(Query\Criterion\DateMetadata::MODIFIED, self::$operatorsMap[$operator], $match);
177 2
178 1
            case self::MATCH_OBJECT_STATE:
179 1
                foreach($values as &$value) {
180 1
                    if (!ctype_digit($value)) {
181
                        $value = $this->stateMatcher->matchOneByKey($value)->id;
182
                    }
183 1
                }
184
                return new Query\Criterion\ObjectStateId($values);
185 2
186 1
            case self::MATCH_OWNER:
187
                foreach($values as &$value) {
188 2
                    if (!ctype_digit($value)) {
189 1
                        $value = $this->userMatcher->matchOneByKey($value)->id;
190 1
                    }
191 1
                }
192
                return new Query\Criterion\UserMetadata(Query\Criterion\UserMetadata::OWNER, Operator::IN, $values);
193 1
194
            case self::MATCH_PARENT_LOCATION_ID:
195 1
                return new Query\Criterion\ParentLocationId($values);
196
197 2
            case self::MATCH_PARENT_LOCATION_REMOTE_ID:
198 2
                $locationIds = [];
199 2
                foreach ($values as $remoteParentLocationId) {
200 2
                    $location = $this->repository->getLocationService()->loadLocationByRemoteId($remoteParentLocationId);
201
                    // unique locations
202
                    $locationIds[$location->id] = $location->id;
203 2
                }
204
                return new Query\Criterion\ParentLocationId($locationIds);
205 2
206 1
            case self::MATCH_SECTION:
207
                foreach($values as &$value) {
208 2
                    if (!ctype_digit($value)) {
209
                        $value = $this->sectionMatcher->matchOneByKey($value)->id;
210 2
                    }
211 2
                }
212
                return new Query\Criterion\SectionId($values);
213
214 2
            case self::MATCH_SUBTREE:
215
                return new Query\Criterion\Subtree($values);
216
217 2
            case self::MATCH_VISIBILITY:
218 2
                /// @todo error/warning if there is more than 1 value...
219 2
                $value = reset($values);
220 2
                if ($value) {
221 2
                    return new Query\Criterion\Visibility(Query\Criterion\Visibility::VISIBLE);
222
                } else {
223 2
                    return new Query\Criterion\Visibility(Query\Criterion\Visibility::HIDDEN);
224
                }
225 2
226 1
            case self::MATCH_AND:
227 1
                $subCriteria = array();
228 1
                foreach($values as $subCriterion) {
229 1
                    $value = reset($subCriterion);
230
                    $subCriteria[] = $this->getQueryCriterion(key($subCriterion), $value);
231 1
                }
232
                return new Query\Criterion\LogicalAnd($subCriteria);
233 2
234
            case self::MATCH_OR:
235 2
                $subCriteria = array();
236 2
                foreach($values as $subCriterion) {
237 2
                    $value = reset($subCriterion);
238
                    $subCriteria[] = $this->getQueryCriterion(key($subCriterion), $value);
239
                }
240
                return new Query\Criterion\LogicalOr($subCriteria);
241
242
            case self::MATCH_NOT:
243
                /// @todo throw if more than one sub-criteria found
244
                $value = reset($values);
245
                $subCriterion = $this->getQueryCriterion(key($values), $value);
246
                return new Query\Criterion\LogicalNot($subCriterion);
247
248
            default:
249
                throw new InvalidMatchConditionsException($this->returns . " can not be matched because matching condition '$key' is not supported. Supported conditions are: " .
250
                    implode(', ', $this->allowedConditions));
251
        }
252
    }
253
254
    /**
255
     * @param array $sortDefinition
256
     * @return array
257
     * @throws InvalidSortConditionsException
258
     */
259
    protected function getSortClauses(array $sortDefinition)
260
    {
261
        $out = array();
262
263
        foreach ($sortDefinition as $sortItem) {
264
265
            if (is_string($sortItem)) {
266
                $sortItem = array('sort_field' => $sortItem);
267
            }
268
            if (!is_array($sortItem) || !isset($sortItem['sort_field'])) {
269
                throw new InvalidSortConditionsException("Missing sort_field element in sorting definition");
270
            }
271
            if (!isset($sortItem['sort_order'])) {
272
                // we have to pick a default ;-)
273
                $sortItem['sort_order'] = 'ASC';
274
            }
275
276
            $direction = $this->hash2SortOrder($sortItem['sort_order']);
277
278
            switch($sortItem['sort_field']) {
279
                case self::SORT_CONTENT_ID:
280
                    $out[] = new SortClause\ContentId($direction);
281
                    break;
282
                case self::SORT_CONTENT_NAME:
283
                    $out[] = new SortClause\ContentName($direction);
284
                    break;
285
                case self::SORT_DATE_MODIFIED:
286
                    $out[] = new SortClause\DateModified($direction);
287
                    break;
288
                case self::SORT_DATE_PUBLISHED:
289
                    $out[] = new SortClause\DatePublished($direction);
290
                    break;
291
                /// @todo
292
                //case self::SORT_FIELD:
293
                //    $out[] = new SortClause\Field($direction);
294
                //    break;
295
                case self::SORT_LOCATION_DEPTH:
296
                    $out[] = new SortClause\Location\Depth($direction);
297
                    break;
298
                case self::SORT_LOCATION_ID:
299
                    $out[] = new SortClause\Location\Id($direction);
300
                    break;
301
                case self::SORT_LOCATION_ISMAIN:
302
                    $out[] = new SortClause\Location\IsMainLocation($direction);
303
                    break;
304
                case self::SORT_LOCATION_PATH:
305
                    $out[] = new SortClause\Location\Path($direction);
306
                    break;
307
                case self::SORT_LOCATION_PRIORITY:
308
                    $out[] = new SortClause\Location\Priority($direction);
309
                    break;
310
                case self::SORT_LOCATION_VISIBILITY:
311
                    $out[] = new SortClause\Location\Visibility($direction);
312
                    break;
313
                case self::SORT_SECTION_IDENTIFIER:
314
                    $out[] = new SortClause\SectionIdentifier($direction);
315
                    break;
316
                case self::SORT_SECTION_NAME:
317
                    $out[] = new SortClause\SectionName($direction);
318
                    break;
319
                default:
320
                    throw new InvalidSortConditionsException("Sort field '{$sortItem['sort_field']}' not implemented");
321
            }
322
        }
323
324
        return $out;
325
    }
326
327
    /**
328
     * @todo investigate how to better return the 'legacy' (db based) search engine even when a Solr-based one is available
329
     * @return \eZ\Publish\API\Repository\SearchService
330
     */
331
    protected function getSearchService()
332
    {
333
        return $this->repository->getSearchService();
334
    }
335
336
    protected function hash2SortOrder($value)
337
    {
338
        $sortOrder = null;
339
340
        if ($value !== null) {
341
            if (strtoupper($value) === 'ASC') {
342
                $sortOrder = Query::SORT_ASC;
343
            } else {
344
                $sortOrder = Query::SORT_DESC;
345
            }
346
        }
347
348
        return $sortOrder;
349
    }
350
351
    /**
352
     * @param int $value
353
     * @return string
354
     */
355
    protected function sortOrder2Hash($value)
356
    {
357
        if ($value === Query::SORT_ASC) {
0 ignored issues
show
introduced by
The condition $value === eZ\Publish\AP...Content\Query::SORT_ASC is always false.
Loading history...
358
            return 'ASC';
359
        } else {
360
            return 'DESC';
361
        }
362
    }
363
}
364