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