2

I would like to fetch all entries in a section according to a Lightswitch value inside of a matrix field. Initially I tried:

craft.entries.section('buildings').spaces.space.isAvailable('1').find

but this doesn't pull any results. Is this even possible in a single query? Or do I need to get all entries from the section first, and then loop over them in order to determine if the Lightswitch has the desired value?

2 Answers 2

2

Fetching entries by a Matrix block field value is actually quite complicated, and not possible to do in a single query – at least not using built in services like craft.entries.

Your idea of pulling all entries and looping through would work – but this kind of "brute force" solution might not scale that well, performance wise.

A better idea might be to first pull all the space Matrix blocks that have a true value for the isAvailable field, and then pull any entry belonging to the buildings section that has a relation at least one of those blocks.

Unfortunately, unlike the EntriesService (i.e. craft.entries), Craft doesn't expose the MatrixService to Twig, making the above hard to accomplish in a template.

For a clean & effective solution to this problem, I'd look to the excellent Low Blocks plugin.

With Low Blocks, you'll be able to pull all space blocks with a true value for isAvailable like this:

{% set spacesMatrixField = craft.fields.getFieldByHandle('spaces') %}
{% set spaceMatrixBlocks = craft.lowblocks.blocks.fieldId(spacesMatrixField.id).type('space').isAvailable('1') %}

After that, we can loop on the matrixBlocks variable in order to create an array containing the IDs of the entries "owning" these Matrix blocks:

{% set buildingEntryIds = [] %}
{% for block in spaceMatrixBlocks %}
    {% set buildingEntryIds = buildingEntryIds|merge([block.owner.id]) %}
{% endfor %}

And finally, we pull the entries using the buildingEntryIds array variable as the parameter to the id argument:

{% set entries = craft.entries.section('buildings').id(buildingEntryIds).limit(null) %}

Here's everything together, for cohesion:

{# Get Matrix blocks #}
{% set spacesMatrixField = craft.fields.getFieldByHandle('spaces') %}
{% set spaceMatrixBlocks = craft.lowblocks.blocks.fieldId(spacesMatrixField.id).type('space').isAvailable('1') %}

{# Get entry IDs #}
{% set buildingEntryIds = [] %}
{% for block in spaceMatrixBlocks %}
    {% set buildingEntryIds = buildingEntryIds|merge([block.owner.id]) %}
{% endfor %}

{# Get entries #}
{% set entries = craft.entries.section('buildings').id(buildingEntryIds).limit(null) %}

That should hopefully do the trick. If you're interested though, here's some more info on why your first attempt wouldn't work:

First, I'd like to point out that its important to realize that each chained "segment" (such as section or isAvailable) in a call to craft.entries in a template actually ends up as an attribute of an ElementCriteriaModel (an object Craft constructs to query the database). This means that

craft.entries.section('buildings').spaces.space.isAvailable('1').find

is actually the same as

craft.entries({
    section : 'buildings',
    spaces : null,
    space : null,
    isAvailable : '1'
})

It should be fairly obvious why this query won't do what you want; Craft has no idea that spaces.space.isAvailable is actually the "path" to a certain Matrix block type's field, and will consider each of those segments (spaces, space and isAvailable) as a separate attribute/argument.

Aside from the syntax issue, the real reason why you'll never be able to use something like spaces.space.isAvailable in a craft.entries query is that Matrix blocks aren't just regular, nested fields, but elements unto themselves (just like entries, categories or assets). Also, for any single entry there can be an indefinite number of instances of each block type. If you have multiple space blocks for a single entry, some with a false isAvailable value and some with true, how would Craft know if the entry should be returned or not?

2

This is a perfect use case for the Preparse plugin. Install the plugin and add a Prepase field to your Entry Type and name it spaceAvailable for example. Configure the field to fetch the first space Matrix block it can find in your spaces Matrix and make it return the value of its isAvailable lightswitch.

{% set firstSpaceBlock = entry.spaces.type('space').first() %}
{{- firstSpaceBlock ? firstSpaceBlock.isAvailable : 0 }}

This basically copies that value to the root level of your entry model (it does so on every entry save!), and you can now query for the entries you're interested in with a single criteria model.

{% set entries = craft.entries.spaceAvailable(1).limit(null) %}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.