ContentMatcher   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 370
Duplicated Lines 0 %

Test Coverage

Coverage 29.85%

Importance

Changes 0
Metric Value
eloc 143
dl 0
loc 370
ccs 40
cts 134
cp 0.2985
rs 6.96
c 0
b 0
f 0
wmc 53

13 Methods

Rating   Name   Duplication   Size   Complexity  
A findContentsByParentLocationRemoteIds() 0 11 2
A getDefaultSortClauses() 0 23 4
A getConditionsFromKey() 0 6 3
F matchContent() 0 81 21
A findContentsByLocationIds() 0 11 2
A findContentsByParentLocationIds() 0 15 3
A matchOne() 0 13 3
A findContentsByContentTypeIds() 0 20 4
A match() 0 3 1
A findContentsByLocationRemoteIds() 0 11 2
A findContentsByContentTypeIdentifiers() 0 21 4
A findContentsByContentRemoteIds() 0 11 2
A findContentsByContentIds() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like ContentMatcher 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 ContentMatcher, 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\Exceptions\InvalidArgumentException;
6
use eZ\Publish\API\Repository\Exceptions\NotImplementedException;
7
use eZ\Publish\API\Repository\Exceptions\UnauthorizedException;
8
use eZ\Publish\API\Repository\Values\Content\Content;
9
use eZ\Publish\API\Repository\Values\Content\Query;
10
use Kaliop\eZMigrationBundle\API\Collection\ContentCollection;
11
use Kaliop\eZMigrationBundle\API\Exception\InvalidMatchConditionsException;
12
use Kaliop\eZMigrationBundle\API\Exception\InvalidSortConditionsException;
13
use Kaliop\eZMigrationBundle\API\SortingMatcherInterface;
14
use Kaliop\eZMigrationBundle\API\Exception\InvalidMatchResultsNumberException;
15
16
class ContentMatcher extends QueryBasedMatcher implements SortingMatcherInterface
17
{
18
    use FlexibleKeyMatcherTrait;
19
20
    const MATCH_RELATES_TO = 'relates_to';
21
    const MATCH_RELATED_FROM = 'related_from';
22
23
    protected $allowedConditions = array(
24
        self::MATCH_AND, self::MATCH_OR, self::MATCH_NOT,
25
        self::MATCH_CONTENT_ID, self::MATCH_LOCATION_ID, self::MATCH_CONTENT_REMOTE_ID, self::MATCH_LOCATION_REMOTE_ID,
26
        self::MATCH_ATTRIBUTE, self::MATCH_CONTENT_TYPE_ID, self::MATCH_CONTENT_TYPE_IDENTIFIER, self::MATCH_GROUP,
27
        self::MATCH_CREATION_DATE, self::MATCH_MODIFICATION_DATE, self::MATCH_OBJECT_STATE, self::MATCH_OWNER,
28
        self::MATCH_PARENT_LOCATION_ID, self::MATCH_PARENT_LOCATION_REMOTE_ID, self::MATCH_QUERY_TYPE, self::MATCH_SECTION,
29
        self::MATCH_SUBTREE, self::MATCH_VISIBILITY,
30
        // aliases
31
        'content_type',
32
        // BC
33
        'contenttype_id', 'contenttype_identifier',
34
        // content-only
35
        self::MATCH_RELATES_TO, self::MATCH_RELATED_FROM
36
    );
37
    protected $returns = 'Content';
38
39
    /**
40
     * @param array $conditions key: condition, value: int / string / int[] / string[]
41
     * @param array $sort
42
     * @param int $offset
43
     * @param int $limit
44
     * @param bool $tolerateMisses
45
     * @return ContentCollection
46
     * @throws InvalidArgumentException
47
     * @throws InvalidMatchConditionsException
48
     * @throws InvalidSortConditionsException
49
     * @throws UnauthorizedException
50
     */
51 26
    public function match(array $conditions, array $sort = array(), $offset = 0, $limit = 0, $tolerateMisses = false)
52
    {
53 26
        return $this->matchContent($conditions, $sort, $offset, $limit, $tolerateMisses);
54
    }
55
56
    /**
57
     * @param array $conditions
58
     * @param array $sort
59
     * @param int $offset
60
     * @return Content
61
     * @throws InvalidArgumentException
62
     * @throws InvalidMatchResultsNumberException
63
     * @throws InvalidMatchConditionsException
64
     * @throws InvalidSortConditionsException
65
     * @throws UnauthorizedException
66
     */
67 2
    public function matchOne(array $conditions, array $sort = array(), $offset = 0)
68
    {
69 2
        $results = $this->match($conditions, $sort, $offset, 2);
70 2
        $count = count($results);
71 2
        if ($count !== 1) {
72
            throw new InvalidMatchResultsNumberException("Found $count " . $this->returns . " when expected exactly only one to match the conditions");
73
        }
74
75 2
        if ($results instanceof \ArrayObject) {
0 ignored issues
show
introduced by
$results is always a sub-type of ArrayObject.
Loading history...
76 2
            $results = $results->getArrayCopy();
77
        }
78
79 2
        return reset($results);
80
    }
81
82
    /**
83
     * NB: does NOT throw if no contents are matching...
84
     * @param array $conditions key: condition, value: int / string / int[] / string[]
85
     * @param array $sort
86
     * @param int $offset
87
     * @param int $limit
88
     * @param bool $tolerateMisses
89
     * @return ContentCollection
90
     * @throws InvalidArgumentException
91
     * @throws InvalidMatchConditionsException
92
     * @throws InvalidSortConditionsException
93
     * @throws UnauthorizedException
94
     */
95 22
    public function matchContent(array $conditions, array $sort = array(), $offset = 0, $limit = 0, $tolerateMisses = false)
96
    {
97 22
        $this->validateConditions($conditions);
98
99 22
        foreach ($conditions as $key => $values) {
100
101
            switch ($key) {
102
103 22
                case self::MATCH_RELATES_TO:
104
                    $contentService = $this->repository->getContentService();
105
                    $contents = array();
106
                    // allow to specify the objects to relate to using different ways
107
                    $relatedContents = $this->match($values, $tolerateMisses);
0 ignored issues
show
Bug introduced by
$tolerateMisses of type boolean is incompatible with the type array expected by parameter $sort of Kaliop\eZMigrationBundle...ContentMatcher::match(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

107
                    $relatedContents = $this->match($values, /** @scrutinizer ignore-type */ $tolerateMisses);
Loading history...
108
                    foreach ($relatedContents as $relatedContent) {
109
                        foreach ($contentService->loadReverseRelations($relatedContent->contentInfo) as $relatingContent) {
110
                            $contents[$relatingContent->contentInfo->id] = $relatingContent;
111
                        }
112
                    }
113
                    break;
114
115 22
                case self::MATCH_RELATED_FROM:
116
                    $contentService = $this->repository->getContentService();
117
                    $contents = array();
118
                    // allow to specify the objects we relate to using different ways
119
                    $relatingContents = $this->match($values, $tolerateMisses);
120
                    foreach ($relatingContents as $relatingContent) {
121
                        foreach ($contentService->loadRelations($relatingContent->contentInfo) as $relatedContent) {
122
                            $contents[$relatedContent->contentInfo->id] = $relatedContent;
123
                        }
124
                    }
125
                    break;
126
127
                default:
128
129
                    // BC support
130 22
                    if ($key == 'content_type') {
131 6
                        if (is_int($values[0]) || ctype_digit($values[0])) {
132
                            $key = self::MATCH_CONTENT_TYPE_ID;
133
                        } else {
134 6
                            $key = self::MATCH_CONTENT_TYPE_IDENTIFIER;
135
                        }
136
                    }
137
138 22
                    if ($key == self::MATCH_QUERY_TYPE) {
139
                        $query = $this->getQueryByQueryType($values);
140
                    } else {
141 22
                        $query = new Query();
142 22
                        $query->filter = $this->getQueryCriterion($key, $values);
143
                    }
144
145
                    // q: when getting a query via QueryType, should we always inject offset/limit?
146 22
                    $query->limit = $limit != 0 ? $limit : $this->queryLimit;
147 22
                    $query->offset = $offset;
148 22
                    if (isset($query->performCount)) $query->performCount = false;
149 22
                    if (!empty($sort)) {
150
                        $query->sortClauses = $this->getSortClauses($sort);
151
                    } else {
152
                        switch ($key) {
153 22
                            case 'content_type_id':
154 21
                            case self::MATCH_CONTENT_TYPE_ID:
155 21
                            case 'content_type_identifier':
156 10
                            case self::MATCH_CONTENT_TYPE_IDENTIFIER:
157
                                // sort objects by depth, lower to higher, so that deleting them has less chances of failure
158
                                // NB: we only do this in eZP versions that allow depth sorting on content queries
159
                                // NB: assignment instead of comparison is correct
160 15
                                if ($sortClauses = $this->getDefaultSortClauses()) {
161
                                    $query->sortClauses = $sortClauses;
162
                                }
163
                        }
164
                    }
165
166 22
                    $results = $this->getSearchService()->findContent($query);
167
168 22
                    $contents = [];
169 22
                    foreach ($results->searchHits as $result) {
170
                        // make sure we return every object only once
171 21
                        $contents[$result->valueObject->contentInfo->id] = $result->valueObject;
172
                    }
173
            }
174
175 22
            return new ContentCollection($contents);
176
177
        }
178
    }
179
180
    /**
181
     * When matching by key, we accept content Id and remote Id only
182
     * @param int|string $key
183
     * @return array
184
     */
185 2
    protected function getConditionsFromKey($key)
186
    {
187 2
        if (is_int($key) || ctype_digit($key)) {
188 2
            return array(self::MATCH_CONTENT_ID => $key);
189
        }
190
        return array(self::MATCH_CONTENT_REMOTE_ID => $key);
191
    }
192
193
    /**
194
     * @param int[] $contentIds
195
     * @return Content[]
196
     * @deprecated
197
     */
198
    protected function findContentsByContentIds(array $contentIds)
199
    {
200
        $contents = [];
201
202
        foreach ($contentIds as $contentId) {
203
            // return unique contents
204
            $content = $this->repository->getContentService()->loadContent($contentId);
205
            $contents[$content->contentInfo->id] = $content;
206
        }
207
208
        return $contents;
209
    }
210
211
    /**
212
     * @param string[] $remoteContentIds
213
     * @return Content[]
214
     * @deprecated
215
     */
216
    protected function findContentsByContentRemoteIds(array $remoteContentIds)
217
    {
218
        $contents = [];
219
220
        foreach ($remoteContentIds as $remoteContentId) {
221
            // return unique contents
222
            $content = $this->repository->getContentService()->loadContentByRemoteId($remoteContentId);
223
            $contents[$content->contentInfo->id] = $content;
224
        }
225
226
        return $contents;
227
    }
228
229
    /**
230
     * @param int[] $locationIds
231
     * @return Content[]
232
     * @deprecated
233
     */
234
    protected function findContentsByLocationIds(array $locationIds)
235
    {
236
        $contentIds = [];
237
238
        foreach ($locationIds as $locationId) {
239
            $location = $this->repository->getLocationService()->loadLocation($locationId);
240
            // return unique ids
241
            $contentIds[$location->contentId] = $location->contentId;
242
        }
243
244
        return $this->findContentsByContentIds($contentIds);
0 ignored issues
show
Deprecated Code introduced by
The function Kaliop\eZMigrationBundle...dContentsByContentIds() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

244
        return /** @scrutinizer ignore-deprecated */ $this->findContentsByContentIds($contentIds);
Loading history...
245
    }
246
247
    /**
248
     * @param string[] $remoteLocationIds
249
     * @return Content[]
250
     * @deprecated
251
     */
252
    protected function findContentsByLocationRemoteIds($remoteLocationIds)
253
    {
254
        $contentIds = [];
255
256
        foreach ($remoteLocationIds as $remoteLocationId) {
257
            $location = $this->repository->getLocationService()->loadLocationByRemoteId($remoteLocationId);
258
            // return unique ids
259
            $contentIds[$location->contentId] = $location->contentId;
260
        }
261
262
        return $this->findContentsByContentIds($contentIds);
0 ignored issues
show
Deprecated Code introduced by
The function Kaliop\eZMigrationBundle...dContentsByContentIds() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

262
        return /** @scrutinizer ignore-deprecated */ $this->findContentsByContentIds($contentIds);
Loading history...
263
    }
264
265
    /**
266
     * @param int[] $parentLocationIds
267
     * @return Content[]
268
     * @deprecated
269
     */
270
    protected function findContentsByParentLocationIds($parentLocationIds)
271
    {
272
        $query = new Query();
273
        $query->limit = $this->queryLimit;
274
        if (isset($query->performCount)) $query->performCount = false;
275
        $query->filter = new Query\Criterion\ParentLocationId($parentLocationIds);
276
        $results = $this->getSearchService()->findContent($query);
277
278
        $contents = [];
279
        foreach ($results->searchHits as $result) {
280
            // make sure we return every object only once
281
            $contents[$result->valueObject->contentInfo->id] = $result->valueObject;
282
        }
283
284
        return $contents;
285
    }
286
287
    /**
288
     * @param string[] $remoteParentLocationIds
289
     * @return Content[]
290
     * @deprecated
291
     */
292
    protected function findContentsByParentLocationRemoteIds($remoteParentLocationIds)
293
    {
294
        $locationIds = [];
295
296
        foreach ($remoteParentLocationIds as $remoteParentLocationId) {
297
            $location = $this->repository->getLocationService()->loadLocationByRemoteId($remoteParentLocationId);
298
            // unique locations
299
            $locationIds[$location->id] = $location->id;
300
        }
301
302
        return $this->findContentsByParentLocationIds($locationIds);
0 ignored issues
show
Deprecated Code introduced by
The function Kaliop\eZMigrationBundle...tsByParentLocationIds() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

302
        return /** @scrutinizer ignore-deprecated */ $this->findContentsByParentLocationIds($locationIds);
Loading history...
303
    }
304
305
    /**
306
     * @param string[] $contentTypeIdentifiers
307
     * @return Content[]
308
     * @deprecated
309
     */
310
    protected function findContentsByContentTypeIdentifiers(array $contentTypeIdentifiers)
311
    {
312
        $query = new Query();
313
        $query->limit = $this->queryLimit;
314
        if (isset($query->performCount)) $query->performCount = false;
315
        $query->filter = new Query\Criterion\ContentTypeIdentifier($contentTypeIdentifiers);
316
        // sort objects by depth, lower to higher, so that deleting them has less chances of failure
317
        // NB: we only do this in eZP versions that allow depth sorting on content queries
318
        if ($sortClauses = $this->getDefaultSortClauses()) {
319
            $query->sortClauses = $sortClauses;
320
        }
321
322
        $results = $this->getSearchService()->findContent($query);
323
324
        $contents = [];
325
        foreach ($results->searchHits as $result) {
326
            // make sure we return every object only once
327
            $contents[$result->valueObject->contentInfo->id] = $result->valueObject;
328
        }
329
330
        return $contents;
331
    }
332
333
    /**
334
     * @param int[] $contentTypeIds
335
     * @return Content[]
336
     * @deprecated
337
     */
338
    protected function findContentsByContentTypeIds(array $contentTypeIds)
339
    {
340
        $query = new Query();
341
        $query->limit = $this->queryLimit;
342
        if (isset($query->performCount)) $query->performCount = false;
343
        $query->filter = new Query\Criterion\ContentTypeId($contentTypeIds);
344
        // sort objects by depth, lower to higher, so that deleting them has less chances of failure
345
        // NB: we only do this in eZP versions that allow depth sorting on content queries
346
        if ($sortClauses = $this->getDefaultSortClauses()) {
347
            $query->sortClauses = $sortClauses;
348
        }
349
        $results = $this->getSearchService()->findContent($query);
350
351
        $contents = [];
352
        foreach ($results->searchHits as $result) {
353
            // make sure we return every object only once
354
            $contents[$result->valueObject->contentInfo->id] = $result->valueObject;
355
        }
356
357
        return $contents;
358
    }
359
360
    /**
361
     * @return false|\eZ\Publish\API\Repository\Values\Content\Query\SortClause[]
362
     */
363 15
    protected function getDefaultSortClauses()
364
    {
365 15
        if (class_exists('eZ\Publish\API\Repository\Values\Content\Query\SortClause\LocationDepth')) {
366
            // Work around the fact that, on eZP 5.4 with recent ezplatform-solr-search-engine versions, sort class
367
            // LocationDepth does exist, but it is not supported by the Solr-based search engine.
368
            // The best workaround that we found so far: test a dummy query!
369
            $searchService = $this->getSearchService();
0 ignored issues
show
Unused Code introduced by
The assignment to $searchService is dead and can be removed.
Loading history...
370
            if (class_exists('EzSystems\EzPlatformSolrSearchEngine\Handler') /*&& $searchService instanceof */) {
371
                try {
372
                    $query = new Query();
373
                    $query->limit = 1;
374
                    $query->performCount = false;
375
                    $query->filter = new Query\Criterion\ContentTypeIdentifier('this_is_a_very_unlikely_content_type_identifier');
376
                    //$query->filter = new Query\Criterion\ContentTypeId($contentTypeIds);
377
                } catch (NotImplementedException $e) {
378
                    return false;
379
                }
380
            }
381
382
            return array(new Query\SortClause\LocationDepth(Query::SORT_DESC));
0 ignored issues
show
Deprecated Code introduced by
The class eZ\Publish\API\Repositor...ortClause\LocationDepth has been deprecated: Since 5.3, use Location search instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

382
            return array(/** @scrutinizer ignore-deprecated */ new Query\SortClause\LocationDepth(Query::SORT_DESC));
Loading history...
383
        }
384
385 15
        return false;
386
    }
387
}
388