ExtendedPdoAdapter::getNbResults()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

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
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
10
use function assert;
11
use function count;
12
use function is_int;
13
use function preg_match;
14
use function preg_replace;
15
use function preg_split;
16
use function strpos;
17
use function strtolower;
18
use function trim;
19
20
use const PHP_EOL;
21
22
/**
23
 * @template T
24
 * @implements AdapterInterface<T>
25
 */
26
class ExtendedPdoAdapter implements AdapterInterface
27
{
28
    private ExtendedPdoInterface $pdo;
29
    private string $sql;
30
31
    /** @var array<mixed> */
32
    private array $params;
33
    private FetcherInterface $fetcher;
34
35
    /** @param array<mixed> $params */
36
    public function __construct(ExtendedPdoInterface $pdo, string $sql, array $params, ?FetcherInterface $fetcher = null)
37
    {
38
        $this->pdo = $pdo;
39
        $this->sql = $sql;
40
        $this->params = $params;
41
        $this->fetcher = $fetcher ?? new FetchAssoc($pdo);
42
    }
43
44
    /**
45
     * {@inheritDoc}
46
     *
47
     * @SuppressWarnings(PHPMD.GotoStatement)
48
     */
49
    public function getNbResults(): int
50
    {
51
        // be smart and try to guess the total number of records
52
        $countQuery = $this->rewriteCountQuery($this->sql);
53
        if (! $countQuery) {
54
            // GROUP BY => fetch the whole result set and count the rows returned
55
            $result = $this->pdo->perform($this->sql, $this->params)->fetchAll();
56
            $count = ! $result ? 0 : count($result);
57
            goto ret;
58
        }
59
60
        if ($this->params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->params of type array 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...
61
            $count = $this->pdo->fetchValue($countQuery, $this->params);
62
            goto ret;
63
        }
64
65
        $count = $this->pdo->fetchValue($countQuery);
66
        ret:
67
        /** @var string $count */
68
        $nbResult = ! $count ? 0 : (int) $count;
69
        assert(is_int($nbResult));
70
        assert($nbResult >= 0);
71
72
        return $nbResult;
73
    }
74
75
    /**
76
     * {@inheritDoc}
77
     *
78
     * @param int $offset
79
     * @param int $length
80
     *
81
     * @return array<mixed>
82
     */
83
    public function getSlice(int $offset, int $length): iterable
84
    {
85
        $sql = $this->sql . $this->getLimitClause($offset, $length);
86
        $result = ($this->fetcher)($sql, $this->params);
87
88
        return ! $result ? [] : $result;
89
    }
90
91
    /**
92
     * {@inheritDoc}
93
     */
94
    public function getLimitClause(int $offset, int $length): string
95
    {
96
        if ($offset && $length) {
97
            return PHP_EOL . "LIMIT {$length} OFFSET {$offset}";
98
        }
99
100
        if ($length) {
101
            return PHP_EOL . "LIMIT {$length}";
102
        }
103
104
        return '';
105
    }
106
107
    /**
108
     * Return count query
109
     *
110
     * @param string $query
111
     *
112
     * @return string
113
     *
114
     * @see https://github.com/pear/Pager/blob/master/examples/Pager_Wrapper.php
115
     * Taken from pear/pager and modified.
116
     * tested at https://github.com/pear/Pager/blob/80c0e31c8b94f913cfbdeccbe83b63822f42a2f8/tests/pager_wrapper_test.php#L19
117
     * @codeCoverageIgnore
118
     */
119
    public function rewriteCountQuery($query)
120
    {
121
        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...
122
            return '';
123
        }
124
125
        if (preg_match('/^\s*SELECT\s+\bDISTINCT\b/is', $query) || preg_match('/\s+GROUP\s+BY\s+/is', $query)) {
126
            return '';
127
        }
128
129
        $openParenthesis = '(?:\()';
130
        $closeParenthesis = '(?:\))';
131
        $subQueryInSelect = $openParenthesis . '.*\bFROM\b.*' . $closeParenthesis;
132
        $pattern = '/(?:.*' . $subQueryInSelect . '.*)\bFROM\b\s+/Uims';
133
        if (preg_match($pattern, $query)) {
134
            return '';
135
        }
136
137
        $subQueryWithLimitOrder = $openParenthesis . '.*\b(LIMIT|ORDER)\b.*' . $closeParenthesis;
138
        $pattern = '/.*\bFROM\b.*(?:.*' . $subQueryWithLimitOrder . '.*).*/Uims';
139
        if (preg_match($pattern, $query)) {
140
            return '';
141
        }
142
143
        $queryCount = preg_replace('/(?:.*)\bFROM\b\s+/Uims', 'SELECT COUNT(*) FROM ', $query, 1);
144
        /** @var array<int> $split */
145
        $split = preg_split('/\s+ORDER\s+BY\s+/is', (string) $queryCount);
146
        [$queryCount] = $split;
147
        /** @var array<int> $split2 */
148
        $split2 = preg_split('/\bLIMIT\b/is', (string) $queryCount);
149
        [$queryCount2] = $split2;
150
151
        return trim((string) $queryCount2);
152
    }
153
}
154