Which built-in type fits which purpose, how categories, tags, slugs, custom URLs and SEO settings behave across them, how revisions and publishing gate what the public sees, how to model a domain the platform has no type for, and where uploaded files live.
L1–L3
Unit 04 · Custom entities
Querying records from a template
Custom entitiesPage 24 of 35Updated
Read custom entity records on any page with the query helper's two custom entity methods: criteria, order and limit, and what each call costs.
Who this is for. Developers showing records outside their own listing and detail pages: testimonials on the home page, the stores near a product, the enrolments of a course. After this page you can fetch exactly the records a template needs, in one query, and know when the work belongs somewhere else.
Two methods for custom entity records
Every template has the query helper, $_query (_query in Twig). Custom entity records have two methods of their own on it; findBy() and findOneBy(), which take an entity class name, are for everything else.
The first returns a collection of records, the second one record or null. Both find the definition by its identifier first, and an identifier that matches no definition is an error. Both add the definition to the criteria themselves, and, for a sited definition, the current site, so the records of other sites never appear.
Every call is a query, and nothing is kept between calls: the same call written twice runs twice. Three habits keep a template cheap:
Hoist the call. Make it once, at the top of the template, and loop over the result. A call inside a loop runs once per iteration.
Let the query order and limit. Pass orderBy and limit rather than fetching everything and sorting or slicing it afterwards.
Do not fetch everything to count it. A common mistake is a component that calls the method once without a limit to count the records for its pagination, and again with a limit and offset for the page it shows. The first call loads every record. For a paged list of one definition, give the definition a listing path and template, which pages for you (Listing and detail templates). For a short list, make one limited call and count what it returned.
{% set featured = _query.findCustomEntityRecordsBy('store', {'tags.slug': 'featured'}, {'position': 'ASC'}, 6) %}
{% if featured.count() > 0 %}
<ul>
{% for store in featured %}
<li><a href="{{ store.getFullPath() }}">{{ store.getName()|e }}</a></li>
{% endfor %}
</ul>
{% endif %}
When the work belongs elsewhere
Criteria filter one definition's records by their fields and relations. When a block needs records of several definitions joined and filtered in ways criteria cannot express, stop composing queries in the template. An Orchestration job that runs on the event that changes the data can write a precomputed record instead, which the template then reads with one call.