Completed
Push — 2.1 ( d7139b...4f94f6 )
by Rafał
09:57
created

ArticleRepository   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 320
Duplicated Lines 25.31 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 62
lcom 1
cbo 11
dl 81
loc 320
rs 3.44
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A findOneBySlug() 0 6 1
A findAllArticles() 0 4 1
A getByCriteria() 0 13 1
A countByCriteria() 0 16 2
A getArticlesByCriteria() 0 28 3
A getArticlesByCriteriaIds() 0 9 1
A getArticlesByBodyContent() 0 8 1
A getPaginatedByCriteria() 11 11 2
A getQueryForRouteArticles() 0 4 1
F applyCustomFiltering() 70 188 49

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ArticleRepository 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 ArticleRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Superdesk Web Publisher Content Bundle.
7
 *
8
 * Copyright 2016 Sourcefabric z.ú. and contributors.
9
 *
10
 * For the full copyright and license information, please see the
11
 * AUTHORS and LICENSE files distributed with this source code.
12
 *
13
 * @copyright 2016 Sourcefabric z.ú
14
 * @license http://www.superdesk.org/license
15
 */
16
17
namespace SWP\Bundle\ContentBundle\Doctrine\ORM;
18
19
use Doctrine\ORM\QueryBuilder;
20
use SWP\Bundle\ContentBundle\Doctrine\ArticleRepositoryInterface;
21
use SWP\Bundle\ContentBundle\Model\ArticleAuthorReference;
22
use SWP\Bundle\ContentBundle\Model\ArticleInterface;
23
use SWP\Bundle\ContentBundle\Model\ArticleSourceReference;
24
use SWP\Bundle\ContentBundle\Model\Metadata;
25
use SWP\Bundle\StorageBundle\Doctrine\ORM\EntityRepository;
26
use SWP\Component\Common\Criteria\Criteria;
27
use SWP\Component\Common\Pagination\PaginationData;
28
use SWP\Component\TemplatesSystem\Gimme\Meta\Meta;
29
30
/**
31
 * Class ArticleRepository.
32
 */
33
class ArticleRepository extends EntityRepository implements ArticleRepositoryInterface
34
{
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function findOneBySlug($slug)
39
    {
40
        return $this->findOneBy([
41
            'slug' => $slug,
42
        ]);
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function findAllArticles()
49
    {
50
        return $this->getQueryByCriteria(new Criteria(), [], 'a')->getQuery()->getResult();
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function getByCriteria(Criteria $criteria, array $sorting): QueryBuilder
57
    {
58
        $qb = $this->getQueryByCriteria($criteria, $sorting, 'a');
59
        $qb->andWhere('a.status = :status')
60
            ->setParameter('status', $criteria->get('status', ArticleInterface::STATUS_PUBLISHED))
61
            ->leftJoin('a.media', 'm')
62
            ->leftJoin('m.renditions', 'r')
63
            ->addSelect('m', 'r');
64
65
        $this->applyCustomFiltering($qb, $criteria);
66
67
        return $qb;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function countByCriteria(Criteria $criteria, $status = ArticleInterface::STATUS_PUBLISHED): int
74
    {
75
        $queryBuilder = $this->createQueryBuilder('a')
76
            ->select('COUNT(a.id)');
77
78
        if (null !== $status) {
79
            $queryBuilder
80
                ->where('a.status = :status')
81
                ->setParameter('status', $criteria->get('status', $status));
82
        }
83
84
        $this->applyCustomFiltering($queryBuilder, $criteria);
85
        $this->applyCriteria($queryBuilder, $criteria, 'a');
86
87
        return (int) $queryBuilder->getQuery()->getSingleScalarResult();
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function getArticlesByCriteria(Criteria $criteria, array $sorting = []): QueryBuilder
94
    {
95
        $queryBuilder = $this->getArticlesByCriteriaIds($criteria);
96
        $queryBuilder->andWhere('a.route IS NOT NULL');
97
        $this->applyCustomFiltering($queryBuilder, $criteria);
98
        $this->applyCriteria($queryBuilder, $criteria, 'a');
99
        $this->applySorting($queryBuilder, $sorting, 'a', $criteria);
100
        $articlesQueryBuilder = clone $queryBuilder;
101
        $this->applyLimiting($queryBuilder, $criteria);
102
        $selectedArticles = $queryBuilder->getQuery()->getScalarResult();
103
        if (!is_array($selectedArticles)) {
104
            return [];
105
        }
106
107
        $ids = [];
108
109
        foreach ($selectedArticles as $partialArticle) {
110
            $ids[] = $partialArticle['a_id'];
111
        }
112
        $articlesQueryBuilder->addSelect('a')
113
            ->leftJoin('a.media', 'm')
114
            ->leftJoin('m.renditions', 'r')
115
            ->addSelect('m', 'r')
116
            ->andWhere('a.id IN (:ids)')
117
            ->setParameter('ids', $ids);
118
119
        return $articlesQueryBuilder;
120
    }
121
122
    public function getArticlesByCriteriaIds(Criteria $criteria): QueryBuilder
123
    {
124
        $queryBuilder = $this->createQueryBuilder('a')
125
            ->select('partial a.{id}')
126
            ->where('a.status = :status')
127
            ->setParameter('status', $criteria->get('status', ArticleInterface::STATUS_PUBLISHED));
128
129
        return $queryBuilder;
130
    }
131
132
    public function getArticlesByBodyContent(string $content): array
133
    {
134
        $queryBuilder = $this->createQueryBuilder('a');
135
        $like = $queryBuilder->expr()->like('a.body', $queryBuilder->expr()->literal('%'.$content.'%'));
136
        $queryBuilder->andWhere($like);
137
138
        return $queryBuilder->getQuery()->getResult();
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144 View Code Duplication
    public function getPaginatedByCriteria(Criteria $criteria, array $sorting = [], PaginationData $paginationData = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
    {
146
        $queryBuilder = $this->getQueryByCriteria($criteria, $sorting, 'a');
147
        $this->applyCustomFiltering($queryBuilder, $criteria);
148
149
        if (null === $paginationData) {
150
            $paginationData = new PaginationData();
151
        }
152
153
        return $this->getPaginator($queryBuilder, $paginationData);
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function getQueryForRouteArticles(string $identifier, array $order = [])
160
    {
161
        throw new \Exception('Not implemented');
162
    }
163
164
    private function applyCustomFiltering(QueryBuilder $queryBuilder, Criteria $criteria)
165
    {
166
        $orX = $queryBuilder->expr()->orX();
167
        if ($criteria->has('extra')) {
168
            foreach ((array) $criteria->get('extra') as $key => $value) {
169
                $valueExpression = $queryBuilder->expr()->literal('%'.\rtrim(\ltrim(\serialize([$key => $value]), 'a:1:{'), ';}').'%');
170
                $orX->add($queryBuilder->expr()->like('a.extra', $valueExpression));
171
            }
172
173
            $queryBuilder->andWhere($orX);
174
            $criteria->remove('extra');
175
        }
176
177
        if ($criteria->has('metadata')) {
178
            $queryBuilder
179
                ->leftJoin('a.data', 'd')
180
                ->leftJoin('d.services', 's')
181
                ->leftJoin('d.subjects', 'sb');
182
183
            foreach ((array) $criteria->get('metadata') as $key => $value) {
184
                switch ($key) {
185
                    case Metadata::SERVICE_KEY:
186
                        foreach ($value as $service) {
187
                            $orX->add($queryBuilder->expr()->eq('s.code', $queryBuilder->expr()->literal($service['code'])));
188
                        }
189
190
                        break;
191
                    case Metadata::SUBJECT_KEY:
192
                        $andX = $queryBuilder->expr()->andX();
193
                        foreach ($value as $subject) {
194
                            $andX->add($queryBuilder->expr()->eq('sb.code', $queryBuilder->expr()->literal($subject['code'])));
195
                            $andX->add($queryBuilder->expr()->eq('sb.scheme', $queryBuilder->expr()->literal($subject['scheme'])));
196
                        }
197
198
                        $orX->add($andX);
199
200
                        break;
201
                    default:
202
                        $orX->add($queryBuilder->expr()->eq("d.$key", $queryBuilder->expr()->literal($value)));
203
                }
204
            }
205
206
            $queryBuilder->andWhere($orX);
207
            $criteria->remove('metadata');
208
        }
209
210
        if ($criteria->has('keywords')) {
211
            $queryBuilder->leftJoin('a.keywords', 'k');
212
            $orX = $queryBuilder->expr()->orX();
213
            foreach ($criteria->get('keywords') as $key => $value) {
214
                $queryBuilder->setParameter($key, $value);
215
                $orX->add($queryBuilder->expr()->eq('k.name', '?'.$key));
216
                $orX->add($queryBuilder->expr()->eq('k.slug', '?'.$key));
217
            }
218
219
            $queryBuilder->andWhere($orX);
220
            $criteria->remove('keywords');
221
        }
222
223 View Code Duplication
        if ($criteria->has('publishedBefore') && null !== $criteria->get('publishedBefore')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
224
            $publishedBefore = $criteria->get('publishedBefore');
225
            $queryBuilder->andWhere('a.publishedAt < :before')
226
                ->setParameter('before', $publishedBefore instanceof \DateTimeInterface ? $publishedBefore : new \DateTime($publishedBefore));
227
            $criteria->remove('publishedBefore');
228
        }
229
230 View Code Duplication
        if ($criteria->has('publishedAfter') && null !== $criteria->get('publishedAfter')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
            $publishedAfter = $criteria->get('publishedAfter');
232
            $queryBuilder->andWhere('a.publishedAt > :after')
233
                ->setParameter('after', $publishedAfter instanceof \DateTimeInterface ? $publishedAfter : new \DateTime($publishedAfter));
234
            $criteria->remove('publishedAfter');
235
        }
236
237
        if ($criteria->has('query') && strlen($query = trim($criteria->get('query'))) > 0) {
238
            $like = $queryBuilder->expr()->like('a.title', $queryBuilder->expr()->literal('%'.$query.'%'));
239
240
            $queryBuilder->andWhere($like);
241
            $criteria->remove('query');
242
        }
243
244 View Code Duplication
        if ($criteria->has('exclude_source') && !empty($criteria->get('exclude_source'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
            $articleSourcesQueryBuilder = $this->getEntityManager()
246
                ->createQueryBuilder()
247
                ->select('excluded_article.id')
248
                ->from(ArticleSourceReference::class, 'excluded_asr')
249
                ->join('excluded_asr.article', 'excluded_article')
250
                ->join('excluded_asr.articleSource', 'excluded_articleSource');
251
252
            $orX = $queryBuilder->expr()->orX();
253
            foreach ((array) $criteria->get('exclude_source') as $value) {
254
                $orX->add($articleSourcesQueryBuilder->expr()->eq('excluded_articleSource.name', $articleSourcesQueryBuilder->expr()->literal($value)));
255
            }
256
            $articleSourcesQueryBuilder->andWhere($orX);
257
            $queryBuilder->andWhere($queryBuilder->expr()->notIn('a.id', $articleSourcesQueryBuilder->getQuery()->getDQL()));
258
259
            $criteria->remove('exclude_source');
260
        }
261
262 View Code Duplication
        if ($criteria->has('source') && !empty($criteria->get('source'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
            $articleSourcesQueryBuilder = $this->getEntityManager()
264
                ->createQueryBuilder()
265
                ->select('article.id')
266
                ->from(ArticleSourceReference::class, 'asr')
267
                ->join('asr.article', 'article')
268
                ->join('asr.articleSource', 'articleSource');
269
            $orX = $queryBuilder->expr()->orX();
270
            foreach ((array) $criteria->get('source') as $value) {
271
                $orX->add($articleSourcesQueryBuilder->expr()->eq('articleSource.name', $articleSourcesQueryBuilder->expr()->literal($value)));
272
            }
273
            $articleSourcesQueryBuilder->andWhere($orX);
274
            $queryBuilder->andWhere($queryBuilder->expr()->in('a.id', $articleSourcesQueryBuilder->getQuery()->getDQL()));
275
276
            $criteria->remove('source');
277
        }
278
279
        if (
280
            ($criteria->has('authorIds') && !empty($criteria->get('authorIds'))) ||
281
            ($criteria->has('author') && !empty($criteria->get('author'))) ||
282
            ($criteria->has('exclude_author') && !empty($criteria->get('exclude_author')))
283
        ) {
284
            $queryBuilder->leftJoin('a.authors', 'au');
285
        }
286
287 View Code Duplication
        if ($criteria->has('author') && !empty($criteria->get('author'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
288
            $orX = $queryBuilder->expr()->orX();
289
            foreach ((array) $criteria->get('author') as $value) {
290
                $orX->add($queryBuilder->expr()->eq('au.name', $queryBuilder->expr()->literal($value)));
291
            }
292
293
            $queryBuilder->andWhere($orX);
294
            $criteria->remove('author');
295
        }
296
297 View Code Duplication
        if ($criteria->has('authorIds') && !empty($criteria->get('authorIds'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
            $orX = $queryBuilder->expr()->orX();
299
300
            foreach ((array) $criteria->get('authorIds') as $value) {
301
                $orX->add($queryBuilder->expr()->eq('au.id', $value));
302
            }
303
304
            $queryBuilder->andWhere($orX);
305
            $criteria->remove('authorIds');
306
        }
307
308
        if ($criteria->has('exclude_author') && !empty($criteria->get('exclude_author'))) {
309
            $excludedAuthors = $this->getEntityManager()
310
                ->createQueryBuilder()
311
                ->from(ArticleAuthorReference::class, 'article_author')
312
                ->select('aa.id')
313
                ->join('article_author.author', 'aaa')
314
                ->join('article_author.article', 'aa')
315
                ->where('aaa.name IN (:authors)');
316
317
            $queryBuilder->setParameter('authors', array_values((array) $criteria->get('exclude_author')));
318
            $queryBuilder->andWhere($queryBuilder->expr()->not($queryBuilder->expr()->in('a.id', $excludedAuthors->getQuery()->getDQL())));
319
320
            $criteria->remove('exclude_author');
321
        }
322
323
        if ($criteria->has('exclude_article') && !empty($criteria->get('exclude_article'))) {
324
            $excludedArticles = [];
325
            foreach ((array) $criteria->get('exclude_article') as $value) {
326
                if (is_numeric($value)) {
327
                    $excludedArticles[] = $value;
328
                } elseif ($value instanceof Meta and $value->getValues() instanceof ArticleInterface) {
329
                    $excludedArticles[] = $value->getValues()->getId();
330
                }
331
            }
332
333
            $queryBuilder->andWhere('a.id NOT IN (:excludedArticles)')
334
                ->setParameter('excludedArticles', $excludedArticles);
335
            $criteria->remove('exclude_article');
336
        }
337
338 View Code Duplication
        if ($criteria->has('exclude_route') && !empty($criteria->get('exclude_route'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
339
            $andX = $queryBuilder->expr()->andX();
340
            $andX->add($queryBuilder->expr()->notIn('a.route', (array) $criteria->get('exclude_route')));
341
            $queryBuilder->andWhere($andX);
342
343
            $criteria->remove('exclude_route');
344
        }
345
346
        if (is_array($criteria->get('route')) && !empty($criteria->get('route'))) {
347
            $queryBuilder->andWhere($queryBuilder->expr()->in('a.route', (array) $criteria->get('route')));
348
349
            $criteria->remove('route');
350
        }
351
    }
352
}
353