Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save davidjguru/577b850d7bb76dbc11d8d0a2d17597c5 to your computer and use it in GitHub Desktop.
Save davidjguru/577b850d7bb76dbc11d8d0a2d17597c5 to your computer and use it in GitHub Desktop.
Drupal 8 || 9 - Composing directions for files from GraphQL using Data Producers in Fields Resolvers

Use Case

Well, I have a Content Type "basic page" (a classic) and inside I can use Layout Builder to put in some custom blocks with fields. For this case I need to reach values from a certain block "FactBox" and some of its fields: title, subtitle, body...you know. But I have to get values from an entity reference field too. I have a media field for uploading documents "listOfDocuments" (name of type: MediaPdf), and I need to send to the frontend layer name of the file, url, the internal uri... How can I get the values? this is the use case here.

The Query I'd like to resolve

As you can see, I'm trying to reach values from fields: uri, url, name of a media entity MediaPdf in a field ListOfDocuments included on a block called MyCustomBlock which is present in the main zone of Layout Regions inside a Content Type Page and we're testing the query against a specific node with url /basic-page. Along the way, I will take advantage and get other field values included in the custom block: title, subtitle, text, just before the ListOfDocuments field.

query MyQuery {
  route(path: "/basic-page"){
    ... on Page {
      id
      layoutRegions {
        main {
          block {
            ... on MyCustomBlock {
              title
              subtitle
              text
              listOfDocuments {
                uri
                url
                name
              }
            }
          }
        }
      }
    }
  }
}

Descriptions

For contex, there are the description of the components I'm using as I have described in .graphqls files:

The main block:

"My Custom block component"
type MyCustomBlock implements BlockContentInterface {
  "User-entered Headline."
  title: String
  "User-entered Sub Headline."
  subtitle: String
  "User-entered text, contains formatted HTML."
  text: String!
  "An array of document collection items"
  listOfDocuments: [MediaPdf]
  "UUID of the block. Useful for keying."
  uuid: String!
}

The Media field for documents:

type MediaPdf implements MediaInterface {
  uuid: String!
  uri: String
  url: String
  name: String
}

Preparing field resolvers

I need to add a new method addMediaPdfFieldResolver(ResolverRegistryInterface $registry, ResolverBuilder $builder)in a Schema description file.

Getting file's name

    $registry->addFieldResolver('MediaPdf', 'name',
      $builder->produce('entity_label')
        ->map('entity', $builder->fromParent())
    );

Composing the uri for the entity

$registry->addFieldResolver('MediaPdf', 'uri',
    $builder->compose(
    $builder->produce('entity_url')
      ->map('entity', $builder->fromParent()),
    $builder->produce('url_path')
      ->map('url', $builder->fromParent())
    ));

Returning the whole external url for the file

If you use something like:

$registry->addFieldResolver('MediaPdf', 'url',
  $builder->produce('entity_url')
    ->map('entity', $builder->fromParent())
);

You will get an error while debugging your Query:

"Expected a value of type \"String\" but received: instance of Drupal\\Core\\Url"

This happens due to the use of the toURL() method, that returns an URL Object and not a String, as GraphQL is waiting. See api.drupal.org/function/EntityBase::toUrl/9.2.x. Using $builder->fromParent()->toString() is not an option either, it won't work.
So you have to look for another resources... you can use composing from a resource from the Image data producers, for instance. Let's see:

    $registry->addFieldResolver('MediaPdf', 'url',
      $builder->compose(
      $builder->produce('property_path')
        ->map('type', $builder->fromValue('entity:media'))
        ->map('value', $builder->fromParent())
        ->map('path', $builder->fromValue('field_media_file.entity')),
      $builder->produce('image_url')
        ->map('entity', $builder->fromParent())
        ->map('field', $builder->fromValue('field_media_file'))
      )
    );

Getting results

{
  "data": {
    "route": {
      "id": "3",
      "layoutRegions": {
        "main": [
          {
            "block": {
              "title": "Title of my custom block",
              "subtitle": "Subtitle for my custom block",
              "text": "<p>Lorem fistrum diodeno jarl está la cosa muy malar.</p>",
              "listOfDocuments": [
                {
                  "uri": "/media/4/edit",
                  "url": "https://my.domain.site/sites/default/files/2021-09/gramenawer.pdf",
                  "name": "gramenawer.pdf"
                },
                {
                  "uri": "/media/3/edit",
                  "url": "https://my.domain.site/sites/default/files/2021-09/benemeritaar.pdf",
                  "name": "benemeritaar.pdf"
                }
              ]
            }
          }
        ]
      }
    }
  }
}

It works! now I'm getting the related values!

@Boby
Copy link

Boby commented Feb 20, 2023

@davidjguru how did you get the layout builder item to expose?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment