Skip to content

Commit 06d55cd

Browse files
committed
More info on fieldValueSql()
1 parent 4909d5a commit 06d55cd

File tree

2 files changed

+128
-9
lines changed

2 files changed

+128
-9
lines changed

docs/5.x/development/element-queries.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,132 @@ Element queries are specialized [query builders](guide:db-query-builder) under t
744744
You may call <craft5:craft\db\Query::asArray()> to skip populating element models with results and return matching rows’ data as an associative array. Altering [selections](#selections) in particular can make elements behave erratically, as they may be missing critical pieces of information.
745745
:::
746746

747+
### Content and Custom Fields
748+
749+
Most custom field values are stored in a single JSON column, keyed by their unique field instance UUID. Craft handles this automatically when using a field or field instance’s built-in query methods (i.e. `.myCustomDateField('<= 2025-11-05')`) by building the appropriate “JSON extraction” expression.
750+
751+
However, Craft _does not_ intercept direct use of `.where()` and other [specialized condition](#conditions) methods, and cannot infer what is a plain column name versus a field or field instance handle. This means you are responsible for building equivalent field value expressions. Typically, this involves a field instance handle and a _field layout provider_ (like an entry type, asset volume, category group, or other component that manages a field layout):
752+
753+
::: code
754+
```twig{1,7} Twig
755+
{% set valueSql = fieldValueSql(entryType('post'), 'sourceMedia') %}
756+
757+
{% set entriesFromPhysicalMedia = craft.entries()
758+
.section('news')
759+
.andWhere([
760+
'or like',
761+
valueSql,
762+
['print', 'paper', 'press']
763+
])
764+
.all() %}
765+
```
766+
```php PHP
767+
$entryType = Craft::$app->getEntries()->getEntryTypeByHandle('post');
768+
$fieldLayout = $entryType->getFieldLayout();
769+
$fieldInstance = $fieldLayout->getFieldByHandle('sourceMedia');
770+
771+
$entriesFromPhysicalMedia = Entry::find()
772+
->section('news')
773+
->andWhere([
774+
'or like',
775+
$fieldInstance->getValueSql(),
776+
['print', 'paper', 'press'],
777+
])
778+
->all();
779+
```
780+
:::
781+
782+
Each element type has a unique means of accessing the relevant field layout:
783+
784+
Addresses
785+
: ```twig
786+
{% set fl = craft.app
787+
.addresses
788+
.getFieldLayout() %}
789+
```
790+
791+
Assets
792+
: Only one field layout exists across all addresses:
793+
794+
```twig
795+
{% set fl = craft.app
796+
.volumes
797+
.getVolumeByHandle('myAssetVolume')
798+
.getFieldLayout() %}
799+
```
800+
801+
Entries
802+
: You can go via the `entries` service…
803+
```twig
804+
{% set fl = craft.app
805+
.entries
806+
.getEntryTypeByHandle('myEntryType')
807+
.getFieldLayout() %}
808+
```
809+
…or use the [Twig helper](../reference/twig/functions.md#entrytype):
810+
```twig
811+
{% set fl = entryType('myEntryType').getFieldLayout() %}
812+
```
813+
814+
Categories
815+
: ```twig
816+
{% set fl = craft.app
817+
.categories
818+
.getGroupByHandle('myCategoryGroup')
819+
.getFieldLayout() %}
820+
```
821+
822+
Global Sets
823+
: ```twig
824+
{% set fl = craft.app
825+
.globals
826+
.getSetByHandle('myGlobalSet')
827+
.getFieldLayout() %}
828+
```
829+
830+
Tags
831+
: ```twig
832+
{% set fl = craft.app
833+
.tags
834+
.getTagGroupByHandle('myTagGroup')
835+
.getFieldLayout() %}
836+
```
837+
838+
Users
839+
: All users share a single field layout:
840+
```twig
841+
{% set fl = craft.app
842+
.fields
843+
.getLayoutByType('craft\\elements\\User') %}
844+
```
845+
846+
Pass any of these `fl` variables to `fieldValueSql()` along with the desired field instance handle to build the required expression:
847+
848+
```twig
849+
{% set valueSql = fieldValueSql(fl, 'sourceMedia') %}
850+
```
851+
852+
### Fixed Ordering
853+
854+
When using `.id()` to query for elements with a specific set of IDs, you can call `.fixedOrder()` to retain their positions. This alleviates some problems when [limiting](#selections) or [paginating](#pagination) sets of results that are in a prescribed order.
855+
856+
```twig
857+
{% set someExternalApiResult = ['SKU-C', 'SKU-R', 'SKU-A', 'SKU-F', 'SKU-T', '...'] %}
858+
859+
{# Pre-flight a query to get a SKU-to-ID map: #}
860+
{% set internalIdMatches = craft.entries()
861+
.select(['id', 'sku'])
862+
.indexBy('sku')
863+
.asArray()
864+
.all() %}
865+
866+
{% Translate SKUs to internal IDs, and load the entries in that order: #}
867+
{% set orderedMatches = craft.entries()
868+
.id(someExternalApiResult | map(sku => internalIdMatches[sku].id))
869+
.fixedOrder()
870+
.all() %}
871+
```
872+
747873
### Selections
748874

749875
Selections modify what columns and rows are returned.

docs/5.x/upgrade.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -400,18 +400,11 @@ When using custom fields in [advanced `where()` conditions](development/element-
400400
401401
::: code
402402
```twig{12} Twig
403-
{# Locate the field layout element that would save to the desired column: #}
404-
{% set entryType = craft.app.entries.getEntryTypeByHandle('post') %}
405-
{% set fieldLayout = entryType.getFieldLayout() %}
406-
{% set sourceField = fieldLayout.getFieldByHandle('sourceMedia') %}
407-
408-
{# (Craft 5.2 introduced the `entryType('post')` shortcut function!) #}
409-
410403
{% set entriesFromPhysicalMedia = craft.entries()
411404
.section('news')
412405
.andWhere([
413406
'or like',
414-
sourceField.getValueSql(),
407+
fieldValueSql(entryType('post'), 'sourceMedia'),
415408
['print', 'paper', 'press']
416409
])
417410
.all() %}
@@ -437,7 +430,7 @@ $entriesFromPhysicalMedia = Entry::find()
437430
438431
This ensures that you are querying for the correct instance of a [multi-instance field](system/fields.md#multi-instance-fields), each of which will store their content under a different UUID in their respective field layouts.
439432
440-
Craft already knows how to query against the appropriate instance of a field when using its handle as a query method, so this is only necessary for complex or compound conditions.
433+
Craft already knows how to query against the appropriate instance of a field when using its handle as a query method, so this is only necessary for complex or compound conditions that make use of low-level query builder features.
441434
442435
#### Case-Sensitivity
443436

0 commit comments

Comments
 (0)