• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / Yoast-SEO-for-TYPO3 / 34594234905

11 Sep 2026 11:28AM UTC coverage: 21.161% (+0.02%) from 21.137%
34594234905

push

github

RinyVT
[TASK] Optimize queries and pagination for overview functionality

7 of 104 new or added lines in 7 files covered. (6.73%)

587 of 2774 relevant lines covered (21.16%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Classes/DataProviders/AbstractOverviewDataProvider.php
1
<?php
2

3
/**
4
 * This file is part of the "yoast_seo" extension for TYPO3 CMS.
5
 *
6
 * For the full copyright and license information, please read the
7
 * LICENSE.txt file that was distributed with this source code.
8
 */
9

10
declare(strict_types=1);
11

12
namespace YoastSeoForTypo3\YoastSeo\DataProviders;
13

14
use TYPO3\CMS\Core\Database\ConnectionPool;
15
use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
16
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
17
use TYPO3\CMS\Core\Utility\GeneralUtility;
18
use YoastSeoForTypo3\YoastSeo\Constants\TableNames;
19
use YoastSeoForTypo3\YoastSeo\Service\Overview\Dto\DataProviderRequest;
20
use YoastSeoForTypo3\YoastSeo\Utility\PageAccessUtility;
21

22
abstract class AbstractOverviewDataProvider implements OverviewDataProviderInterface
23
{
24
    protected const PAGES_FIELDS = [
25
        'uid',
26
        'doktype',
27
        'hidden',
28
        'starttime',
29
        'endtime',
30
        'fe_group',
31
        't3ver_state',
32
        'nav_hide',
33
        'is_siteroot',
34
        'module',
35
        'content_from_pid',
36
        'extendToSubpages',
37
        'sys_language_uid',
38
        'title',
39
        'seo_title',
40
        'tx_yoastseo_score_readability',
41
        'tx_yoastseo_score_seo',
42
    ];
43

44
    protected DataProviderRequest $dataProviderRequest;
45

46
    protected ?int $numberOfItems = null;
47

48
    public function initialize(DataProviderRequest $dataProviderRequest): void
49
    {
50
        $this->dataProviderRequest = $dataProviderRequest;
×
NEW
51
        $this->numberOfItems = null;
×
52
    }
53

54
    /**
55
     * Builds the fully constrained query for a chunk of page ids, selecting an
56
     * explicit column list. The query is not executed; the base class applies
57
     * counting and pagination on top of it.
58
     *
59
     * @param int[] $pageIds
60
     */
61
    abstract protected function createQueryBuilder(array $pageIds): QueryBuilder;
62

63
    /**
64
     * @return array<int, array<string, mixed>>
65
     */
66
    public function process(int $offset = 0, int $limit = 0): array
67
    {
NEW
68
        $chunks = $this->getPageIdChunks();
×
69

70
        // Fast path: a single chunk (the common case on MySQL/MariaDB/PostgreSQL)
71
        // lets the database do the windowing in one query.
NEW
72
        if (count($chunks) <= 1) {
×
NEW
73
            $queryBuilder = $this->createQueryBuilder($chunks[0] ?? [])->orderBy('uid');
×
NEW
74
            if ($limit > 0) {
×
NEW
75
                $queryBuilder->setFirstResult(max(0, $offset))->setMaxResults($limit);
×
76
            }
NEW
77
            return $queryBuilder->executeQuery()->fetchAllAssociative();
×
78
        }
79

80
        // Multi-chunk path: window across chunks (very large trees on SQLite).
NEW
81
        $items = [];
×
NEW
82
        $toSkip = max(0, $offset);
×
NEW
83
        $remaining = $limit;
×
NEW
84
        foreach ($chunks as $chunk) {
×
NEW
85
            if ($limit > 0 && $remaining <= 0) {
×
NEW
86
                break;
×
87
            }
NEW
88
            $queryBuilder = $this->createQueryBuilder($chunk)->orderBy('uid');
×
NEW
89
            if ($limit > 0) {
×
NEW
90
                $chunkCount = $this->countChunk($chunk);
×
NEW
91
                if ($toSkip >= $chunkCount) {
×
NEW
92
                    $toSkip -= $chunkCount;
×
NEW
93
                    continue;
×
94
                }
NEW
95
                $queryBuilder->setFirstResult($toSkip)->setMaxResults($remaining);
×
NEW
96
                $toSkip = 0;
×
97
            }
NEW
98
            foreach ($queryBuilder->executeQuery()->fetchAllAssociative() as $row) {
×
NEW
99
                $items[] = $row;
×
NEW
100
                $remaining--;
×
101
            }
102
        }
NEW
103
        return $items;
×
104
    }
105

106
    public function getNumberOfItems(): int
107
    {
NEW
108
        if ($this->numberOfItems === null) {
×
NEW
109
            $count = 0;
×
NEW
110
            foreach ($this->getPageIdChunks() as $chunk) {
×
NEW
111
                $count += $this->countChunk($chunk);
×
112
            }
NEW
113
            $this->numberOfItems = $count;
×
114
        }
NEW
115
        return $this->numberOfItems;
×
116
    }
117

118
    /**
119
     * @param int[] $pageIds
120
     */
121
    protected function countChunk(array $pageIds): int
122
    {
NEW
123
        return (int)$this->createQueryBuilder($pageIds)
×
NEW
124
            ->count('uid')
×
NEW
125
            ->executeQuery()
×
NEW
126
            ->fetchOne();
×
127
    }
128

129
    /**
130
     * The field a page id restriction is applied to: the translation parent in a
131
     * translated language, the page uid itself in the default language.
132
     */
133
    protected function getRestrictionField(): string
134
    {
NEW
135
        if ($this->dataProviderRequest->getLanguage() > 0) {
×
NEW
136
            return (string)$GLOBALS['TCA'][TableNames::PAGES]['ctrl']['transOrigPointerField'];
×
137
        }
NEW
138
        return 'uid';
×
139
    }
140

141
    /**
142
     * @return array<int, int[]>
143
     */
144
    protected function getPageIdChunks(): array
145
    {
NEW
146
        return array_chunk($this->getPageIds(), $this->getMaxBindParameters() - 10);
×
147
    }
148

149
    /**
150
     * @return int[]
151
     */
152
    protected function getPageIds(): array
153
    {
154
        return PageAccessUtility::getPageIds($this->dataProviderRequest->getId());
×
155
    }
156

157
    /**
158
     * @return int<999, max>
159
     */
160
    protected function getMaxBindParameters(): int
161
    {
162
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(TableNames::PAGES);
×
163
        return max(999, PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform()));
×
164
    }
165
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc