Paginator   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 11
lcom 1
cbo 3
dl 0
loc 105
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getQueryBuilder() 0 4 1
A useCustomPaginator() 0 13 3
A getPaginator() 0 15 3
A getItems() 0 13 2
A count() 0 4 1
1
<?php
2
/**
3
 * This is just a proxy to detect if we can use the "fast" Pagination
4
 * or if we use the "safe" variant by Doctrine2.
5
 */
6
namespace ZfcDatagrid\DataSource\Doctrine2;
7
8
use Doctrine\ORM\QueryBuilder;
9
use Doctrine\ORM\Tools\Pagination\Paginator as Doctrine2Paginator;
10
use Zend\Paginator\Adapter\AdapterInterface;
11
use ZfcDatagrid\DataSource\Doctrine2\PaginatorFast as ZfcDatagridPaginator;
12
13
class Paginator implements AdapterInterface
14
{
15
    /**
16
     * @var QueryBuilder
17
     */
18
    protected $qb = null;
19
20
    /**
21
     * Total item count.
22
     *
23
     * @var int
24
     */
25
    protected $rowCount = null;
26
27
    /**
28
     * @var \Doctrine\ORM\Tools\Pagination\Paginator
29
     */
30
    private $paginator;
31
32
    /**
33
     * @param QueryBuilder $qb
34
     */
35
    public function __construct(QueryBuilder $qb)
36
    {
37
        $this->qb = $qb;
38
    }
39
40
    /**
41
     * @return \Doctrine\ORM\QueryBuilder
42
     */
43
    public function getQueryBuilder()
44
    {
45
        return $this->qb;
46
    }
47
48
    /**
49
     * Test which pagination solution to use.
50
     *
51
     * @return bool
52
     */
53
    private function useCustomPaginator()
54
    {
55
        $qb = $this->getQueryBuilder();
56
        $parts = $qb->getDQLParts();
57
58
        if ($parts['having'] !== null || true === $parts['distinct']) {
59
            // never tried having in such queries...
60
            return false;
61
        }
62
63
        // @todo maybe more detection needed :-/
64
        return true;
65
    }
66
67
    /**
68
     * @return Doctrine2Paginator|ZfcDatagridPaginator
69
     */
70
    private function getPaginator()
71
    {
72
        if ($this->paginator !== null) {
73
            return $this->paginator;
74
        }
75
76
        if ($this->useCustomPaginator() === true) {
77
            $this->paginator = new ZfcDatagridPaginator($this->getQueryBuilder());
0 ignored issues
show
Documentation Bug introduced by
It seems like new \ZfcDatagrid\DataSou...his->getQueryBuilder()) of type object<ZfcDatagrid\DataS...octrine2\PaginatorFast> is incompatible with the declared type object<Doctrine\ORM\Tools\Pagination\Paginator> of property $paginator.

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...
78
        } else {
79
            // Doctrine2Paginator as fallback...they are using 3 queries
80
            $this->paginator = new Doctrine2Paginator($this->getQueryBuilder());
81
        }
82
83
        return $this->paginator;
84
    }
85
86
    /**
87
     * Returns an array of items for a page.
88
     *
89
     * @param int $offset
90
     * @param int $itemCountPerPage
91
     *
92
     * @return array
93
     */
94
    public function getItems($offset, $itemCountPerPage)
95
    {
96
        $paginator = $this->getPaginator();
97
        if ($paginator instanceof Doctrine2Paginator) {
98
            $this->getQueryBuilder()
99
                ->setFirstResult($offset)
100
                ->setMaxResults($itemCountPerPage);
101
102
            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, Issue523, RecursiveArrayIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder...or\MockFileListIterator, Zend\Http\Header\Cookie, Zend\I18n\Translator\TextDomain, Zend\Session\AbstractContainer, Zend\Session\Container, Zend\Session\Storage\ArrayStorage, Zend\Session\Storage\SessionStorage, Zend\Stdlib\ArrayObject, Zend\Stdlib\ArrayStack, Zend\Stdlib\Parameters, Zend\View\Helper\Placeholder\Container, Zend\View\Helper\Placeho...ainer\AbstractContainer, Zend\View\Variables.

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...
103
        } else {
104
            return $paginator->getItems($offset, $itemCountPerPage);
105
        }
106
    }
107
108
    /**
109
     * Returns the total number of rows in the result set.
110
     *
111
     * @return int
112
     */
113
    public function count()
114
    {
115
        return $this->getPaginator()->count();
116
    }
117
}
118