Skip to content

Commit c85b2c9

Browse files
authored
Merge pull request #424 from FriendsOfTYPO3/lazy-load
[TASK] Lazy Load
2 parents c856ed2 + 0e986bb commit c85b2c9

File tree

5 files changed

+202
-123
lines changed

5 files changed

+202
-123
lines changed

Classes/DataProcessing/ContentBlockData.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717

1818
namespace TYPO3\CMS\ContentBlocks\DataProcessing;
1919

20+
use TYPO3\CMS\Core\Domain\Exception\RecordPropertyException;
2021
use TYPO3\CMS\Core\Domain\RawRecord;
2122
use TYPO3\CMS\Core\Domain\Record;
2223
use TYPO3\CMS\Core\Domain\Record\ComputedProperties;
2324
use TYPO3\CMS\Core\Domain\Record\LanguageInfo;
2425
use TYPO3\CMS\Core\Domain\Record\SystemProperties;
2526
use TYPO3\CMS\Core\Domain\Record\VersionInfo;
2627
use TYPO3\CMS\Core\Domain\RecordInterface;
28+
use TYPO3\CMS\Core\Domain\RecordPropertyClosure;
2729

2830
/**
2931
* This class represents the `data` object inside the Fluid template for Content Blocks.
@@ -56,8 +58,7 @@ final class ContentBlockData implements RecordInterface
5658
public function __construct(
5759
protected ?Record $_record = null,
5860
protected string $_name = '',
59-
/** @var array<string, RelationGrid>|array<string, RenderedGridItem[]> */
60-
protected array $_grids = [],
61+
protected ?ContentBlockGridData $_grids = null,
6162
protected array $_processed = [],
6263
) {}
6364

@@ -114,7 +115,12 @@ public function get(string $id): mixed
114115
return $this->_grids;
115116
}
116117
if (array_key_exists($id, $this->_processed)) {
117-
return $this->_processed[$id];
118+
$property = $this->_processed[$id];
119+
if ($property instanceof RecordPropertyClosure) {
120+
$property = $this->readPropertyClosure($id, $property);
121+
$this->_processed[$id] = $property;
122+
}
123+
return $property;
118124
}
119125
return $this->_record->get($id);
120126
}
@@ -159,7 +165,7 @@ public function get_Name(): string
159165
return $this->_name;
160166
}
161167

162-
public function get_Grids(): array
168+
public function get_Grids(): ContentBlockGridData
163169
{
164170
return $this->_grids;
165171
}
@@ -171,4 +177,19 @@ public function override(ContentBlockData $contentBlockData): void
171177
$this->_grids = $contentBlockData->_grids;
172178
$this->_processed = $contentBlockData->_processed;
173179
}
180+
181+
private function readPropertyClosure(string $id, RecordPropertyClosure $closure): mixed
182+
{
183+
try {
184+
$property = $closure->instantiate();
185+
} catch (\Exception $exception) {
186+
// Consumers of this method can rely on catching ContainerExceptionInterface
187+
throw new RecordPropertyException(
188+
'An exception occurred while instantiating record property "' . $id . '"',
189+
1756308141,
190+
$exception
191+
);
192+
}
193+
return $property;
194+
}
174195
}

Classes/DataProcessing/ContentBlockDataDecorator.php

Lines changed: 111 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
use TYPO3\CMS\Core\Collection\LazyRecordCollection;
2828
use TYPO3\CMS\Core\Domain\Record;
2929
use TYPO3\CMS\Core\Domain\RecordInterface;
30+
use TYPO3\CMS\Core\Domain\RecordPropertyClosure;
31+
use TYPO3\CMS\Core\Resource\Collection\LazyFileReferenceCollection;
3032

3133
/**
3234
* @internal Not part of TYPO3's public API.
@@ -46,7 +48,6 @@ public function __construct(
4648
public function setRequest(ServerRequestInterface $request): void
4749
{
4850
$this->request = $request;
49-
$this->contentObjectProcessor->setRequest($request);
5051
}
5152

5253
public function decorate(RecordInterface $resolvedRecord, ?PageLayoutContext $context = null): ContentBlockData
@@ -69,8 +70,6 @@ public function decorate(RecordInterface $resolvedRecord, ?PageLayoutContext $co
6970
$context,
7071
);
7172
$this->contentBlockDataDecoratorSession->setContentBlockData($identifier, $contentBlockData);
72-
$this->gridProcessor->process();
73-
$this->contentObjectProcessor->process();
7473
return $contentBlockData;
7574
}
7675

@@ -89,144 +88,169 @@ private function buildContentBlockDataObjectRecursive(
8988
if (SpecialFieldType::tryFrom($fieldType->getName()) !== null) {
9089
continue;
9190
}
92-
// TCA type "passthrough" is not available in the record, and it won't fall back to raw record value.
93-
if ($fieldType->getTcaType() === 'passthrough') {
94-
$resolvedField = $resolvedRelation->record->getRawRecord()->get($tcaFieldDefinition->uniqueIdentifier);
95-
} else {
96-
$resolvedField = $resolvedRelation->record->get($tcaFieldDefinition->uniqueIdentifier);
97-
}
98-
if ($this->isRelationField($resolvedField)) {
99-
$resolvedField = $this->handleRelation(
100-
$resolvedField,
101-
$depth,
102-
$context,
103-
);
104-
$grids = $this->handleGrids($grids, $context, $resolvedField, $tcaFieldDefinition);
91+
$loadedField = $this->loadField($resolvedRelation, $tcaFieldDefinition, $depth, $context);
92+
$processedContentBlockData[$tcaFieldDefinition->identifier] = $loadedField;
93+
// Exclude file relations from grids.
94+
if ($loadedField instanceof RecordPropertyClosure && $fieldType->getTcaType() !== 'file') {
95+
$grids[$tcaFieldDefinition->identifier] = $this->handleGrids($context, $loadedField, $tcaFieldDefinition);
10596
}
106-
$processedContentBlockData[$tcaFieldDefinition->identifier] = $resolvedField;
10797
}
10898
$resolvedRelation->resolved = $processedContentBlockData;
99+
$gridData = new ContentBlockGridData($grids);
109100
$contentBlockDataObject = $this->buildContentBlockDataObject(
110101
$resolvedRelation,
111102
$contentTypeDefinition->getName(),
112-
$grids,
103+
$gridData,
113104
);
114105
return $contentBlockDataObject;
115106
}
116107

108+
private function loadField(
109+
ResolvedContentBlockDataRelation $resolvedRelation,
110+
TcaFieldDefinition $tcaFieldDefinition,
111+
int $depth,
112+
?PageLayoutContext $context
113+
): mixed {
114+
$fieldType = $tcaFieldDefinition->fieldType;
115+
// TCA type "passthrough" is not available in the record, and it won't fall back to raw record value.
116+
if ($fieldType->getTcaType() === 'passthrough') {
117+
$resolvedField = $resolvedRelation->record->getRawRecord()->get($tcaFieldDefinition->uniqueIdentifier);
118+
return $resolvedField;
119+
}
120+
// Simple field type, load eagerly.
121+
if ($this->isRelationField($tcaFieldDefinition) === false) {
122+
$resolvedField = $resolvedRelation->record->get($tcaFieldDefinition->uniqueIdentifier);
123+
return $resolvedField;
124+
}
125+
// Relation field type, load lazily.
126+
$recordPropertyClosure = new RecordPropertyClosure(
127+
function () use ($resolvedRelation, $tcaFieldDefinition, $depth, $context): ContentBlockData|LazyRecordCollection|LazyFileReferenceCollection|null {
128+
$resolvedField = $resolvedRelation->record->get($tcaFieldDefinition->uniqueIdentifier);
129+
$resolvedField = $this->handleRelation(
130+
$resolvedField,
131+
$depth,
132+
$context,
133+
);
134+
return $resolvedField;
135+
}
136+
);
137+
return $recordPropertyClosure;
138+
}
139+
117140
/**
118-
* @param array<string, RelationGrid>|array<string, RenderedGridItem[]> $grids
119-
* @return array<string, RelationGrid>|array<string, RenderedGridItem[]>
141+
* @return LazyRecordCollection<RenderedGridItem>|RecordPropertyClosure
120142
*/
121143
private function handleGrids(
122-
array $grids,
123144
?PageLayoutContext $context,
124-
mixed $resolvedField,
145+
RecordPropertyClosure $recordPropertyClosure,
125146
TcaFieldDefinition $tcaFieldDefinition
126-
): array {
127-
if ($context === null && $this->request !== null) {
128-
$renderedGridItemDataObjects = $resolvedField;
129-
if (!is_iterable($renderedGridItemDataObjects)) {
130-
$renderedGridItemDataObjects = [$renderedGridItemDataObjects];
147+
): LazyRecordCollection|RecordPropertyClosure {
148+
if ($context === null) {
149+
if ($this->request === null) {
150+
throw new \InvalidArgumentException(
151+
'ContentBlockDataDecorator is missing the request object.',
152+
1756397952
153+
);
131154
}
132-
foreach ($renderedGridItemDataObjects as $contentBlockDataObject) {
133-
$renderedGridItem = new RenderedGridItem();
134-
$grids[$tcaFieldDefinition->identifier][] = $renderedGridItem;
135-
$callback = function () use ($contentBlockDataObject, $renderedGridItem): void {
155+
$this->contentObjectProcessor->setRequest($this->request);
156+
$initialization = function () use ($recordPropertyClosure): array {
157+
$renderedGridItems = [];
158+
$renderedGridItemDataObjects = $recordPropertyClosure->instantiate();
159+
if (!is_iterable($renderedGridItemDataObjects)) {
160+
$renderedGridItemDataObjects = [$renderedGridItemDataObjects];
161+
}
162+
foreach ($renderedGridItemDataObjects as $contentBlockDataObject) {
163+
$renderedGridItem = new RenderedGridItem();
164+
$renderedGridItems[] = $renderedGridItem;
136165
$this->contentObjectProcessor->processContentObject(
137166
$contentBlockDataObject,
138167
$renderedGridItem
139168
);
140-
};
141-
$this->contentObjectProcessor->addInstruction($callback);
142-
}
143-
}
144-
if ($context !== null) {
145-
$relationGrid = new RelationGrid();
146-
$grids[$tcaFieldDefinition->identifier] = $relationGrid;
147-
$callback = function () use ($grids, $tcaFieldDefinition, $resolvedField, $context): void {
148-
$relationGrid = $grids[$tcaFieldDefinition->identifier];
149-
$this->gridProcessor->processGrid(
150-
$relationGrid,
151-
$context,
152-
$tcaFieldDefinition,
153-
$resolvedField
154-
);
169+
}
170+
return $renderedGridItems;
155171
};
156-
$this->gridProcessor->addInstruction($callback);
172+
return new LazyRecordCollection('', $initialization);
157173
}
158-
return $grids;
174+
$initialization = function () use ($tcaFieldDefinition, $recordPropertyClosure, $context): ?RelationGrid {
175+
$resolvedField = $recordPropertyClosure->instantiate();
176+
if ($resolvedField === null) {
177+
return null;
178+
}
179+
$relationGrid = $this->gridProcessor->processGrid(
180+
$context,
181+
$tcaFieldDefinition,
182+
$resolvedField
183+
);
184+
return $relationGrid;
185+
};
186+
return new RecordPropertyClosure($initialization);
159187
}
160188

161189
private function handleRelation(
162-
RecordInterface|LazyRecordCollection $resolvedField,
190+
RecordInterface|LazyRecordCollection|LazyFileReferenceCollection|null $resolvedField,
163191
int $depth,
164192
?PageLayoutContext $context = null,
165-
): ContentBlockData|LazyRecordCollection {
166-
if ($resolvedField instanceof LazyRecordCollection) {
167-
$resolvedField = $this->transformMultipleRelation(
168-
$resolvedField,
169-
$depth,
170-
$context,
171-
);
193+
): ContentBlockData|LazyRecordCollection|LazyFileReferenceCollection|null {
194+
if ($resolvedField === null) {
195+
return null;
196+
}
197+
if ($resolvedField instanceof LazyFileReferenceCollection) {
172198
return $resolvedField;
173199
}
174-
$resolvedField = $this->transformSelectRelation(
200+
if ($resolvedField instanceof LazyRecordCollection) {
201+
$initialization = function () use ($resolvedField, $depth, $context): array {
202+
$resolvedField = $this->transformMultipleRelation(
203+
$resolvedField,
204+
$depth,
205+
$context,
206+
);
207+
return $resolvedField;
208+
};
209+
return new LazyRecordCollection((string)$resolvedField, $initialization);
210+
}
211+
$resolvedField = $this->transformSingleRelation(
175212
$resolvedField,
176213
$depth,
177214
$context,
178215
);
179216
return $resolvedField;
180217
}
181218

182-
private function isRelationField(mixed $resolvedField): bool
219+
private function isRelationField(TcaFieldDefinition $tcaFieldDefinition): bool
183220
{
184-
if ($resolvedField instanceof Record) {
221+
$tcaType = $tcaFieldDefinition->fieldType->getTcaType();
222+
$fieldConfig = $tcaFieldDefinition->getTca()['config'] ?? [];
223+
if (in_array($tcaType, ['category', 'inline', 'file'])) {
185224
return true;
186225
}
187-
if ($resolvedField instanceof LazyRecordCollection) {
226+
$allowed = $fieldConfig['allowed'] ?? '';
227+
if ($tcaType === 'group' && $allowed !== '') {
188228
return true;
189229
}
190-
return false;
191-
}
192-
193-
private function transformSelectRelation(
194-
LazyRecordCollection|RecordInterface $processedField,
195-
int $depth,
196-
?PageLayoutContext $context = null,
197-
): LazyRecordCollection|ContentBlockData {
198-
if ($processedField instanceof Record) {
199-
$processedField = $this->transformSingleRelation(
200-
$processedField,
201-
$depth,
202-
$context,
203-
);
204-
return $processedField;
230+
$foreignTable = $fieldConfig['foreign_table'] ?? '';
231+
if ($tcaType === 'select' && $foreignTable !== '') {
232+
return true;
205233
}
206-
$processedField = $this->transformMultipleRelation(
207-
$processedField,
208-
$depth,
209-
$context,
210-
);
211-
return $processedField;
234+
return false;
212235
}
213236

214237
/**
215-
* @return LazyRecordCollection<ContentBlockData>
238+
* @return array<ContentBlockData>
216239
*/
217240
private function transformMultipleRelation(
218241
LazyRecordCollection $processedField,
219242
int $depth,
220243
?PageLayoutContext $context = null,
221-
): LazyRecordCollection {
244+
): array {
245+
$items = [];
222246
foreach ($processedField as $key => $processedFieldItem) {
223-
$processedField[$key] = $this->transformSingleRelation(
247+
$items[$key] = $this->transformSingleRelation(
224248
$processedFieldItem,
225249
$depth,
226250
$context,
227251
);
228252
}
229-
return $processedField;
253+
return $items;
230254
}
231255

232256
private function transformSingleRelation(
@@ -268,19 +292,16 @@ private function transformSingleRelation(
268292
return $contentBlockData;
269293
}
270294

271-
/**
272-
* @param array<string, RelationGrid>|array<string, RenderedGridItem[]> $grids
273-
*/
274295
private function buildContentBlockDataObject(
275296
ResolvedContentBlockDataRelation $resolvedRelation,
276297
string $name = '',
277-
array $grids = [],
298+
?ContentBlockGridData $gridData = null,
278299
): ContentBlockData {
279300
$resolvedData = $resolvedRelation->resolved;
280301
if ($resolvedRelation->record instanceof Record === false) {
281302
throw new \RuntimeException('Resolved record is not a record instance', 1728587332);
282303
}
283-
$contentBlockData = new ContentBlockData($resolvedRelation->record, $name, $grids, $resolvedData);
304+
$contentBlockData = new ContentBlockData($resolvedRelation->record, $name, $gridData, $resolvedData);
284305
return $contentBlockData;
285306
}
286307

0 commit comments

Comments
 (0)