Skip to content
Snippets Groups Projects
Commit 43a284ca authored by Benni Mack's avatar Benni Mack Committed by Oliver Bartsch
Browse files

[FEATURE] Automatically resolve relations for records

What does it do? When creating a record
object out of a database row (via the RecordFactory),
the Record object now holds enriched values
for fields where the content is "known" based
on TCA / Schema API.

This is especially relevant for records with
possible relations, for field types such as
"type=group", "type=select" with foreign_table set
and "type=inline" as an example.

The main purpose for this relation enriching
is rendering of the backend page module and
content in frontend, but it can be used
for any kind of TCA-based record.

It also works recursively and with circular
dependencies thanks to the RecordIdentityMap.

In order to avoid any problems with large amounts
of data, an approach of "Lazy+Greedy Fetching"
was chosen.

How does this approach work under the hood?

As an example, we load 10 content elements on a
page (1 DB query) so we can render them.

Step 1: Lazy Collections / RecordPropertyClosure

RecordFactory filters out only the relevant
fields and their values from a record's type.
Now, the RecordFactory also checks for fields
with their meaning and uses a different value for
a field. Example: For a type=inline value, there
was the number "5" as value (= 5 relations) available,
now we know we need the relation records (as a collection)
properly sorted resolved there as well. For this to
work, the new RecordFieldTransformer creates
LazyCollection objects or RecordPropertyClosure objects
(for a 1:1/n:1 relation) which means that the DB query is
not made (yet) but only called when the value is accessed
for the first time ("lazy loading").

Step 2: Getting the related UID/Table Pairs

The RecordIdentityMap now knows about the
10 Records from tt_content, as they have been created
completely before handing it to the output rendering.
There comes the fun part. As soon as the value (with a
lazy closure) is accessed for the first time,
the RelationResolver checks the RelationHandler
to find the table / uids that we should resolve.

In our case, we now know that our first content element
has 5 relations to a DB table e.g. "tx_mycarousel_item"
with UIDs 12,13,14,15,16. Thanks to the RelationHandler,
we also have the proper sorting of these items.

Step 3: A greedy database query to get the full DB rows

So, for the first content element, we want the 5
complete, related DB rows. The RelationResolver
now sends this query to the "GreedyDatabaseBackend"
which uses a subquery to not only fetch
the 5 DB rows, but ALL rows of this DB table
that are on the same PID with 1 DB query (using subselects).

It however only returns the 5 items, and keeps the
other items in a runtime cache.

At this point we have made 3 DB queries.

Currently, we then do the language + workspace overlays.

Step 4: The long way back

The RelationResolver now has the full DB rows and sorts
them. The RecordFieldTransformer builds Record / Collection
objects out of it, checks if an object has been created
already (via the IdentityMap) or creates new ones,
utilizing again the Lazy approach from step 1 to ensure
we only resolve the records when we need them.

Responsibilities:

- RecordFieldTransformer
  - knows what to do based on the Field Type
  - returns objects, never raw DB records
  - initializes the lazy collections / closure objects
- RelationResolver
  - uses RelationHandler to resolve uids
  - knows and applies the sorting
- GreedyDatabaseBackend
  - does overlays / enableFields

The design decisions behind this approach:
- We only build Record objects when they are requested explicitly
- We distinguish the cardinality (1:1 / n:1 vs. 1:n)
- We do overlays on a very end of the chain

Kudos to Nikita Hovratov for creating the first draft
of this approach here: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83725
along with comprehensive tests.

PS: In the mid-term, the RelationResolver could be
based on the sys_refindex and minimize queries.

Resolves: #103581
Related: #103783
Related: #104002
Releases: main
Change-Id: I73d1f017c5f98115f7ad4ddd2634b7acf66d183c
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/85046


Tested-by: default avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: default avatarNikita Hovratov <nikita.h@live.de>
Reviewed-by: default avatarOliver Bartsch <bo@cedev.de>
Tested-by: default avatarcore-ci <typo3@b13.com>
Tested-by: default avatarNikita Hovratov <nikita.h@live.de>
Tested-by: default avatarBenjamin Franzke <ben@bnf.dev>
Reviewed-by: default avatarBenjamin Franzke <ben@bnf.dev>
parent 3e22a5d6
Branches
Tags
No related merge requests found
Showing
with 1178 additions and 100 deletions
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment