Issues (41)

src/Pagerfanta/ExtendedPdoAdapter.php (1 issue)

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