1

So I have a fairly complex section structure that (on an Entry Type) has a matrix field that contains entries as fields(sub-entry) within a blocktype.

I'd like to write a GraphQL query such that when text is matched on a field in the sub-entry on the matrix blocktype to return the entire section entry. I have thus far only been able to return matches on the title of the sub-entry. When I search for text on a custom field within the sub-entry nothing is returned. I do have the Use this field’s values as search keywords option checked on the matrix field of the main entry, and the custom fields on the sub-entry checked as well. I also went poking around the database in the searchindex table and I can see the field values/keywords in there for the sub-entry I created.

Here is the query I've been playing around with:

fragment tour on tours_tour_Entry {
  title
  ... on tours_tour_Entry {
    title
    tourVariety
    duration
    complexity
    thumbnail {
      url
    }
    backgroundImage {
      url
    }
    introHeading
    introSubheading
    introContentBlocks {
      ... on introContentBlocks_introBlock_BlockType {
        body
      }
    }
    factsHeading
    factsContentBlocks {
      ... on factsContentBlocks_factsContentBlock_BlockType {
        body
      }
    }
    tourPois {
      ... on tourPois_tourPoi_BlockType {
        title
        description
        poi {
          title
          ... on pois_poi_Entry {
            fov
            description
            astroObjects {
              ... on astroObjects_astroObject_Entry {
                ra
                dec
                image {
                  url
                }
                characteristics
              }
            }
          }
        }
      }
    }
  }
}

fragment poi on pois_poi_Entry {
  fov
  description
  astroObjects {
    ... on astroObjects_astroObject_Entry {
      ra
      dec
      image {
        url
      }
      characteristics
    }
  }
}

fragment astroObject on astroObjects_astroObject_Entry {
  ra
  dec
  image {
    url
  }
  characteristics
}

{
  entries(search: "*desc* OR *char*") {
    ...tour
    ...poi
    ...astroObject
  }
}

When I perform this search, the fragments for "poi" and "astroObject" return results, but not "tour". What am I doing wrong in the query or in the "tour" fragment"? Is this even possible?

Edit based on Andris's answer:

(Including all the relevant code for posterity)

Okay, so I've created a custom plugin that extends the relevant classes and I'm still not able to get entries as results - which is odd considering that I am getting IDs back from my custom query.

Plugin.php

<?php
namespace edr;

use Craft;
use edr\queries\EntryAsField;
use edr\arguments\EntryAsFieldArgumentHandler;
use craft\events\RegisterGqlQueriesEvent;
use craft\services\Gql;
use yii\base\Event;
use craft\gql\ArgumentManager;
use craft\events\RegisterGqlArgumentHandlersEvent;

class Plugin extends \craft\base\Plugin {
    public static $plugin;

    public $hasCpSettings = true;

    public function init() {
        parent::init();
        self::$plugin = $this;

        $this->registerArgumentHandler();
        $this->registerQuery();
        
    }

    protected function createSettingsModel() {
        return new \edr\models\Settings();
    }

    protected function registerArgumentHandler() {
        Event::on(
            ArgumentManager::class,
            ArgumentManager::EVENT_DEFINE_GQL_ARGUMENT_HANDLERS,
            function(RegisterGqlArgumentHandlersEvent $event) {
                $event->handlers["searchRelated"] = EntryAsFieldArgumentHandler::class;
            }
        );
    }

    protected function settingsHtml() {
        return \Craft::$app->getView()->renderTemplate(
            'edr-nested-entry-query/settings',
            [ 'settings' => $this->getSettings() ]
        );
    }

    public function registerQuery() {
        Event::on(
            Gql::class,
            Gql::EVENT_REGISTER_GQL_QUERIES,
            function(RegisterGqlQueriesEvent $event) {                
                $event->queries['nestedEntries'] = EntryAsField::getQueries();
            }
        );
    }
}

EntryAsField.php

<?php

namespace edr\queries;

use edr\arguments\EntryAsFieldArguments;
use craft\gql\arguments\elements\Entry as EntryArguments;
use craft\gql\base\Query;
use craft\gql\interfaces\elements\Entry as EntryInterface;
use craft\gql\resolvers\elements\Entry as EntryResolver;
use craft\helpers\Gql as GqlHelper;
use GraphQL\Type\Definition\Type;

class EntryAsField extends \craft\gql\base\Query
{
    public static function getQueries($checkToken = true): array
    {
        if ($checkToken && !GqlHelper::canQueryEntries()) {
            Craft::info("platypus");
            return [];
        }

        return [
                'type' => Type::listOf(EntryInterface::getType()),
                'args' => EntryAsFieldArguments::getArguments(),
                'resolve' => EntryResolver::class . '::resolve',
                'description' => 'This query is used to query for entries.',
                'complexity' => GqlHelper::relatedArgumentComplexity(),
        ];
    }
}

EntryAsFieldArguments.php

<?php

namespace edr\arguments;

use GraphQL\Type\Definition\Type;
use craft\gql\types\QueryArgument;
use craft\gql\types\input\criteria\Entry;

class EntryAsFieldArguments extends \craft\gql\base\ElementArguments
{
    public static function getArguments(): array
    {
        return array_merge(parent::getArguments(), self::getContentArguments(), [
            'searchRelated' => [
                'name' => 'searchRelated',
                'type' => Type::listOf(Entry::getType()),
                'description' => 'Matches search on entries-as-fields and returns the whole entry.'
            ],
        ]);
    }
}

EntryAsFieldArgumentHandler.php

<?php

namespace edr\arguments;

use Craft;
use craft\elements\Entry;
use craft\gql\base\RelationArgumentHandler;
use craft\elements\MatrixBlock;

class EntryAsFieldArgumentHandler extends RelationArgumentHandler
{
    protected $argumentName = 'searchRelated';

    protected function handleArgument($argumentValue)
    {
        Craft::info("bobcat");
        $argumentValue = parent::handleArgument($argumentValue);
        $ids = $this->getIds(Entry::class, $argumentValue);
        $relatedIds = MatrixBlock::find()->select('ownerId')->column();
        if(count($relatedIds) > 0) { // dedupe and stuff in 2d array
            $relatedIds = [array_unique($relatedIds)];
        }
        Craft::info("nested-entries-graphql-queries - DEBUG - Returned IDs from 1st query");
        Craft::info($ids);
        Craft::info("nested-entries-graphql-queries - DEBUG - Returned IDs from 2nd queryz");
        Craft::info($relatedIds);
        return $relatedIds;
    }
}

In the argument handler above, the first query to getIDs() returns IDs fine. The MatrixBlock query to get the ownerIDs also returns IDs fine and I return these IDs as a 2d array. I get no results from this query though - am I missing something?

Query:

{
  nestedEntries(searchRelated: [{search: "*out*"}]) {
    id
    title
  }
}
2

Currently, it can't be done out of the box, since it's currently not possible to search the related entries like this.

However, you can extend GraphQL to pull this off. Ideally, it would be possible to add arguments to existing queries, but that won't be possible until Craft 4.0, so you'll have to add an entirely new query.

So, add a custom query and register it. It should in essence be an almost facsimile of craft\gql\queries\Entry, with the addition of an additional argument. Let's pretend you named it searchRelated.

Now also create an argument handler and register it. You would set the $argumentName value to match the name you picked above and your handleArgument method would do the search and return the matching element ids in the form of [[$ids]]. The double-array is required because the method is expected to return a list of id sets for more complex matches (for example, relatedAssets: [{criteriaSet1}, {criteriaSet2}]). This is not relevant to your specific case, however, it does enforce a certain syntax that has to be adhered to.

Anyway, if all is done correctly, you should be able to use your new query to fetch the results you want.

Some relevant reading on extending GraphQL:

7
  • Thank you so much - this will be very helpful! Question: So I don't actually need a custom resolver for this? Just a custom argument handler?
    – Eric
    Aug 25 at 18:31
  • 1
    Just a custom argument handler. The ids you return (provided you extend the RelationArgumentHandler class) are going to get included in the relatedTo argument. So, run your search based on the argument value. That will get you entry ids. Since Craft doesn't have the concept of reverse relations, you'll have to do another search to find the Matrix Blocks that are related to the entries with the ids you have. After that, you should return the ids of all the Matrix Blocks and, as they get rolled in the relatedTo argument, you get a list of the entries related to the Matrix Blocks. Aug 26 at 6:31
  • That makes sense, I feel like I am close. I have the entry IDs being returned based on my search: nestedEntries(searchRelated: [{search: "out"}]) { ... } I'm not sure where to look to find the function for the subsequent query though. Can you point me in the direction of where I can find the query that will allow me to "find the Matrix Blocks that are related to the entries with the ids you have"?
    – Eric
    Aug 26 at 18:11
  • 1
    Something like $relatedIds = MatrixBlock::find()->select('ownerId')->column(); should return what you're after. (MatrixBlock should be an alias of \craft\elements\MatrixBlock). Aug 27 at 11:43
  • Thank you so much for your help thus far Andris, I added the line you mentioned and I am getting back another set of IDs, however, no results appear as a result of the query. I added the relevant code to the original question for posterity and also for a complete look at how to accomplish this. Something still isn't right and I'm not sure what it is. One thing I had to add that you didn't mention is a custom argument class, not sure if that's causing the issue although I can't imagine it would. Thoughts?
    – Eric
    Aug 27 at 18:43

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.