1

I'm using Element API and trying to create a query that returns a list of entries based on if the entry is using a specific Matrix Block Type.

I've tried a few different things including a query that does a search where the field contains any value, but it's not returning any entries.

2

If you weren’t using Element API here, you could do this by adding a condition to the element query:

$query->andWhere(['exists', (new \craft\db\Query())
    ->from(['b' => \craft\db\Table::MATRIXBLOCKS])
    ->innerJoin(\craft\db\Table::MATRIXBLOCKTYPES . ' bt', '[[bt.id]] = [[b.id]]')
    ->where('[[b.ownerId]] = [[elements.id]]')
    ->andWhere(['bt.handle' => 'yourBlockTypeHandle'])
]);

But Element API doesn’t give you direct access to the query. I can think of a couple ways to pull it off, though.


Option 1: Implement a custom element query param.

You could define a new element query behavior class, which wraps that condition into a new hasBlockType param:

<?php

use craft\db\Query;
use craft\db\Table;
use craft\elements\db\ElementQuery;
use craft\events\CancelableEvent;
use yii\base\Behavior;

/**
 * @property ElementQuery $owner
 */
class ElementQueryBehavior extends Behavior
{
    public $hasBlockType;

    public function hasBlockType(string $handle)
    {
        $this->hasBlockType = $handle;
    }

    public function events()
    {
        return [
            ElementQuery::EVENT_BEFORE_PREPARE => [$this, 'prepare'],
        ];
    }

    public function prepare()
    {
        if ($this->hasBlockType) {
            $this->owner->andWhere(['exists', (new Query())
                ->from(['b' => Table::MATRIXBLOCKS])
                ->innerJoin(Table::MATRIXBLOCKTYPES . ' bt', '[[bt.id]] = [[b.id]]')
                ->where('[[b.ownerId]] = [[elements.id]]')
                ->andWhere(['bt.handle' => $this->hasBlockType])
            ]);
        }
    }
}

Then you’d need to register that behavior with new element query instances, from your module’s init() method.

\craft\base\Event::on(
    \craft\elements\db\ElementQuery::class,
    \craft\db\Query::EVENT_DEFINE_BEHAVIORS,
    function(\craft\events\DefineBehaviorsEvent $event) {
        $event->behaviors['my-behavior'] = new ElementQueryBehavior();
    }
);

Finally, from config/element-api.php, add this to your endpoint’s criteria array:

'criteria' => [
    'hasBlockType' => 'yourBlockTypeHandle',
    // ...
],
// ...

The advantage of going this route is that your new hasBlockType query param will be available everywhere, so you can reuse it from any element queries you’re writing in Twig or PHP.


Option 2: Use a custom Fractal resource class.

Element API allows you to define Fractal resource class using the class endpoint param.

Yours could extend the built-in ElementResource class, overriding its getElementQuery() method.

<?php

use craft\db\Query;
use craft\db\Table;
use craft\elementapi\resources\ElementResource;
use craft\elements\db\ElementQueryInterface;

class MyElementResource extends ElementResource
{
    public $hasBlockType;

    protected function getElementQuery(): ElementQueryInterface
    {
        $query = parent::getElementQuery();

        if ($this->hasBlockType) {
            $query->andWhere(['exists', (new Query())
                ->from(['b' => Table::MATRIXBLOCKS])
                ->innerJoin(Table::MATRIXBLOCKTYPES . ' bt', '[[bt.id]] = [[b.id]]')
                ->where('[[b.ownerId]] = [[elements.id]]')
                ->andWhere(['bt.handle' => $this->hasBlockType])
            ]);
        }

        return $query;
    }
}

Then from config/element-api.php, add this to your endpoint config (note that it would not go in criteria):

'class' => MyElementResource::class,
'hasBlockType' => 'yourBlockTypeHandle',
// ...

Option 3: Just use Preparse

If you only care about a single block type, and don’t really need this to be configurable, the simplest route would be to use the Preparse Field plugin, and actually store whether entries have the block type, as part of their entry data.

For example, if the handle of the Matrix block type you care about is text, then you’d create a Preparse field called hasTextBlock, with this template:

{{ element.myMatrixField.type('text').exists() ? '1' }}

Then resave your entries, and add this to your endpoint’s criteria array:

'criteria' => [
    'hasTextBlock' => '1',
    // ...
],
// ...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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