Completed
Push — 1.x ( d8f348...89609b )
by Akihito
27s queued 13s
created

ExtendedPdoAdapter::getNbResults()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 14
c 3
b 0
f 0
nc 8
nop 0
dl 0
loc 24
ccs 12
cts 12
cp 1
crap 5
rs 9.4888
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\AuraSqlModule\Pagerfanta;
6
7
use Aura\Sql\ExtendedPdoInterface;
8
use Pagerfanta\Adapter\AdapterInterface;
9
use PDO;
10
11
use function assert;
12
use function count;
13
use function is_int;
14
use function preg_match;
15
use function preg_replace;
16
use function preg_split;
17
use function strpos;
18
use function strtolower;
19
use function trim;
20
21
use const PHP_EOL;
22
23
class ExtendedPdoAdapter implements AdapterInterface
24
{
25
    /** @var ExtendedPdoInterface */
26
    private $pdo;
27
28
    /** @var string */
29
    private $sql;
30 14
31
    /** @var array<mixed> */
32 14
    private $params;
33 14
34 14
    /**
35 14
     * @param array<mixed> $params
36
     */
37
    public function __construct(ExtendedPdoInterface $pdo, string $sql, array $params)
38
    {
39
        $this->pdo = $pdo;
40 9
        $this->sql = $sql;
41
        $this->params = $params;
42
    }
43 9
44 9
    /**
45
     * {@inheritdoc}
46 1
     *
47 1
     * @phpstan-return positive-int
48
     *
49 1
     * @SuppressWarnings(PHPMD.GotoStatement)
50
     */
51 8
    public function getNbResults(): int
52
    {
53 2
        // be smart and try to guess the total number of records
54 2
        $countQuery = $this->rewriteCountQuery($this->sql);
55 2
        if (! $countQuery) {
56 2
            // GROUP BY => fetch the whole result set and count the rows returned
57
            $result = $this->pdo->perform($this->sql, $this->params)->fetchAll();
58 2
            $count = ! $result ? 0 : count($result);
59
            goto ret;
60 6
        }
61
62 6
        if ($this->params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->params of type array<mixed,mixed> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
63
            $count = $this->pdo->fetchValue($countQuery, $this->params);
64
            goto ret;
65
        }
66
67
        $count = $this->pdo->fetchValue($countQuery);
68 6
        ret:
69
        /** @var string $count */
70 6
        $nbResult = ! $count ? 0 : (int) $count;
71 6
        assert(is_int($nbResult));
72
        assert($nbResult > 0);
73 6
74
        return $nbResult;
75
    }
76
77
    /**
78
     * {@inheritdoc}
79 9
     *
80
     * @param int $offset
81 9
     * @param int $length
82 9
     *
83 5
     * @return array<array<mixed>>
84 5
     */
85 5
    public function getSlice(int $offset, int $length): iterable
86
    {
87
        $sql = $this->sql . $this->getLimitClause($offset, $length);
88 5
        $result = $this->pdo->perform($sql, $this->params)->fetchAll(PDO::FETCH_ASSOC);
89
90
        return ! $result ? [] : $result;
91 4
    }
92 3
93
    /**
94
     * {@inheritdoc}
95 1
     */
96
    public function getLimitClause(int $offset, int $length): string
97
    {
98
        if ($offset && $length) {
99
            return PHP_EOL . "LIMIT {$length} OFFSET {$offset}";
100
        }
101
102
        if ($length) {
103
            return PHP_EOL . "LIMIT {$length}";
104
        }
105
106
        return '';
107
    }
108
109
    /**
110
     * Return count query
111
     *
112
     * @param string $query
113
     *
114
     * @return string
115
     *
116
     * @see https://github.com/pear/Pager/blob/master/examples/Pager_Wrapper.php
117
     * Taken from pear/pager and modified.
118
     * tested at https://github.com/pear/Pager/blob/80c0e31c8b94f913cfbdeccbe83b63822f42a2f8/tests/pager_wrapper_test.php#L19
119
     * @codeCoverageIgnore
120
     */
121
    public function rewriteCountQuery($query)
122
    {
123
        if (is_int(strpos(strtolower($query), 'union'))) {
0 ignored issues
show
introduced by
The condition is_int(strpos(strtolower($query), 'union')) is always true.
Loading history...
124
            return '';
125
        }
126
127
        if (preg_match('/^\s*SELECT\s+\bDISTINCT\b/is', $query) || preg_match('/\s+GROUP\s+BY\s+/is', $query)) {
128
            return '';
129
        }
130
131
        $openParenthesis = '(?:\()';
132
        $closeParenthesis = '(?:\))';
133
        $subQueryInSelect = $openParenthesis . '.*\bFROM\b.*' . $closeParenthesis;
134
        $pattern = '/(?:.*' . $subQueryInSelect . '.*)\bFROM\b\s+/Uims';
135
        if (preg_match($pattern, $query)) {
136
            return '';
137
        }
138
139
        $subQueryWithLimitOrder = $openParenthesis . '.*\b(LIMIT|ORDER)\b.*' . $closeParenthesis;
140
        $pattern = '/.*\bFROM\b.*(?:.*' . $subQueryWithLimitOrder . '.*).*/Uims';
141
        if (preg_match($pattern, $query)) {
142
            return '';
143
        }
144
145
        $queryCount = preg_replace('/(?:.*)\bFROM\b\s+/Uims', 'SELECT COUNT(*) FROM ', $query, 1);
146
        /** @var array<int> $split */
147
        $split = preg_split('/\s+ORDER\s+BY\s+/is', (string) $queryCount);
148
        [$queryCount] = $split;
149
        /** @var array<int> $split2 */
150
        $split2 = preg_split('/\bLIMIT\b/is', (string) $queryCount);
151
        [$queryCount2] = $split2;
152
153
        return trim((string) $queryCount2);
154
    }
155
}
156