Test Failed
Push — master ( c2139f...3d9e00 )
by Paweł
56:29
created

ArticleRepository::findArticlesByCriteria()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1.048

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 7
cts 11
cp 0.6364
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 12
nc 1
nop 2
crap 1.048
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 Doctrine\ORM\Tools\Pagination\Paginator;
21
use SWP\Component\Common\Criteria\Criteria;
22
use SWP\Bundle\ContentBundle\Doctrine\ArticleRepositoryInterface;
23
use SWP\Bundle\ContentBundle\Model\ArticleInterface;
24
use SWP\Bundle\StorageBundle\Doctrine\ORM\EntityRepository;
25
26
class ArticleRepository extends EntityRepository implements ArticleRepositoryInterface
27
{
28
    /**
29
     * {@inheritdoc}
30 8
     */
31
    public function findOneBySlug($slug)
32 8
    {
33 8
        return $this->findOneBy([
34
            'slug' => $slug,
35
        ]);
36
    }
37
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function findAllArticles()
42
    {
43
        return $this->getQueryByCriteria(new Criteria(), [], 'a')->getQuery()->getResult();
44
    }
45
46
    /**
47
     * {@inheritdoc}
48 12
     */
49
    public function getByCriteria(Criteria $criteria, array $sorting): QueryBuilder
50 12
    {
51 12
        $qb = $this->getQueryByCriteria($criteria, $sorting, 'a');
52 12
        $qb->andWhere('a.status = :status')
53
            ->setParameter('status', $criteria->get('status', ArticleInterface::STATUS_PUBLISHED))
54 12
            ->leftJoin('a.media', 'm')
55
            ->addSelect('m');
56
57
        $this->applyCustomFiltering($qb, $criteria);
58
59
        return $qb;
60 2
    }
61
62 2
    /**
63 2
     * {@inheritdoc}
64 2
     */
65 2
    public function countByCriteria(Criteria $criteria): int
66
    {
67 2
        $queryBuilder = $this->createQueryBuilder('a')
68
            ->select('COUNT(a.id)')
69 2
            ->where('a.status = :status')
70
            ->setParameter('status', $criteria->get('status', ArticleInterface::STATUS_PUBLISHED));
71
72
        $this->applyCustomFiltering($queryBuilder, $criteria);
73
        $this->applyCriteria($queryBuilder, $criteria, 'a');
74
75 5
        return (int) $queryBuilder->getQuery()->getSingleScalarResult();
76
    }
77 5
78 5
    /**
79 5
     * {@inheritdoc}
80
     */
81 5
    public function findArticlesByCriteria(Criteria $criteria, array $sorting = []): array
82 2
    {
83 2
        $queryBuilder = $this->createQueryBuilder('a')
84 2
            ->where('a.status = :status')
85
            ->setParameter('status', $criteria->get('status', ArticleInterface::STATUS_PUBLISHED))
86
            ->leftJoin('a.media', 'm')
87 2
            ->addSelect('m');
88
89
        $this->applyCustomFiltering($queryBuilder, $criteria);
90 5
        $this->applyCriteria($queryBuilder, $criteria, 'a');
91
        $this->applySorting($queryBuilder, $sorting, 'a');
92
        $this->applyLimiting($queryBuilder, $criteria);
93
94
        $paginator = new Paginator($queryBuilder->getQuery(), true);
95
96 5
        return $paginator->getIterator()->getArrayCopy();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method getArrayCopy() does only exist in the following implementations of said interface: ArrayIterator, ArrayObject, DoctrineTest\Instantiato...tAsset\ArrayObjectAsset, DoctrineTest\Instantiato...lizableArrayObjectAsset, DoctrineTest\Instantiato...ceptionArrayObjectAsset, DoctrineTest\Instantiato...sset\WakeUpNoticesAsset, Hoa\Iterator\Map, Hoa\Iterator\Recursive\Map, Issue523, Pixers\DoctrineProfilerB...attenTraceGraphIterator, QueryPathIterator, RecursiveArrayIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Form\E...r\ViolationPathIterator, Symfony\Component\Proper...ss\PropertyPathIterator, Zend\Code\Annotation\AnnotationCollection, Zend\Code\Scanner\AnnotationScanner, Zend\Stdlib\ArrayObject, Zend\Stdlib\ArrayStack, Zend\Stdlib\Parameters.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102 5
    public function getQueryForRouteArticles(string $identifier, array $order = [])
103 3
    {
104 3
        throw new \Exception('Not implemented');
105 3
    }
106
107
    private function applyCustomFiltering(QueryBuilder $queryBuilder, Criteria $criteria)
108 3
    {
109
        foreach (['metadata', 'author'] as $name) {
110
            if (!$criteria->has($name)) {
111 5
                continue;
112 5
            }
113 5
114
            $orX = $queryBuilder->expr()->orX();
115 5
            foreach ($criteria->get($name) as $value) {
116
                $orX->add($queryBuilder->expr()->like('a.metadata', $queryBuilder->expr()->literal('%'.$value.'%')));
117
            }
118
119
            $queryBuilder->andWhere($orX);
120
            $criteria->remove($name);
121
        }
122
123
        if ($criteria->has('publishedBefore')) {
124
            $queryBuilder->andWhere('a.publishedAt < :before')
125
                ->setParameter('before', $criteria->get('publishedBefore'));
126
            $criteria->remove('publishedBefore');
127
        }
128
129
        if ($criteria->has('publishedAfter')) {
130
            $queryBuilder->andWhere('a.publishedAt > :after')
131
                ->setParameter('after', $criteria->get('publishedAfter'));
132
            $criteria->remove('publishedAfter');
133
        }
134
    }
135
}
136