1

How can we write Element API configuration files to dynamically output JSON payloads in Craft 3.3?

The goal is to create a generic function which only needs endpoint definitions to recursively loop through a data for a given section. This shortens development time because we don't have to manually define each field.

A departed member of our team did this with Craft 2.7, and we are having trouble duplicating the system for Craft 3.3. Has anyone done this for Craft 3.3? If so, do you have guidance or example code to share?

4
  • What you're describing sounds a lot like the functionality of the built-in GraphQL server…? Dec 4, 2019 at 2:05
  • Ohh, maybe I've misunderstood—you're talking about including all the fields + attributes of the Elements you're returning, without having to declare and type-cast each of them in the transform function? Dec 4, 2019 at 2:09
  • That sounds right, @AugustMiller. Using GraphQL would still require specifying the details of each field. We want to automate that process so we only need to specify the Section details. Dec 4, 2019 at 13:13
  • It may be worth just returning the entire Entry object, i.e. function (Entry $entry) { return $entry } to see what the native serializer does with the class + ContentBehavior trait—it may well be too verbose for a JSON API… If all else fails, you could write some kind of Transformer class that iterates over $entry->getAttributes() and $entry->getFieldValues() and attempts to coerce each type of field/data into the proper format. I've done this for packing data into a CSV, but not for JSON—it becomes additionally challenging when you start dealing with relational fields. Dec 5, 2019 at 4:44

1 Answer 1

1

You can determine what custom fields are relevant to an entry through its field layout:

$layout = $entry->getFieldLayout();

A FieldLayout may contain more than just custom fields (like headings and native fields), but you can filter those “field layout elements” down to just the ones we care about:

$fields = $layout->getCustomFields();

(In Craft 3, this would just be $layout->getFields()!)

Then, you’ll need to loop over this array of Fields and pull values off of the original $entry. Here’s a function that puts this all together:

use craft\elements\Entry;

function getCustomFieldData(Entry $entry) {
    $values = [];

    // Fetch the entry’s field layout:
    /** @var craft\models\FieldLayout $layout */
    $layout = $entry->getFieldLayout();

    // Get all custom fields in the layout:
    $fields = $layout->getCustomFields();

    // Loop over the fields:
    foreach ($fields as $field) {
        // Stash a reference to the field’s handle:
        $handle = $field->handle;

        // Grab the field’s value:
        $value = $entry->getFieldValue($handle);

        // Check what field type this is, so we can deal with its value appropriately:
        switch (get_class($field)) {
            // Date + Time fields...
            case craft\fields\Date::class:
                $values[$handle] = $value->format(DateTime::ATOM);

                break;

            // Asset fields...
            case craft\fields\Assets::class:
                // Get an array of Assets by executing the element query:
                $values[$handle] = array_map(function ($asset) {
                    return $asset->url();
                }, $value->all());

                break;

            // Any other type of field we haven’t specifically handled...
            default:
                // ...just gets the raw value:
                $values[$handle] = $value;
        }
    }

    return $values;
}

This function can live in your element-api.php file, a Transformer, or a module or plugin.

Your endpoint might look something like this:

<?php

use craft\elements\Entry;

// ...function, from above!

return [
    'endpoints' => [
        'api/<entryId:\d+>.json' => function($entryId) {
            return [
                'resourceKey' => 'stories',
                'elementType' => Entry::class,
                'criteria' => [
                    'id' => $entryId
                ],
                'transformer' => function(Entry $entry) {
                    return [
                        'id' => $entry->id,
                        'title' => $entry->title,
                        'url' => $entry->url,
                        // From the transformer, call our custom function with the current entry:
                        'customFields' => getCustomFieldData($entry),
                    ];
                },
            ];
        }
    ]
];

This should return a structure that looks something like this:

{
   "data":[
      {
         "id":"28",
         "title":"The entry's title",
         "url":"http://domain.com/entry-url",
         "customFields":{
            "assets":[
               "http://domain.com/assets/image.png",
               "http://domain.com/assets/image-alt.png"
            ],
            "plainText":"Some text"
         }
      }
   ],
   "meta":{
      "pagination":{
         "total":1,
         "count":1,
         "per_page":100,
         "current_page":1,
         "total_pages":1,
         "links":{

         }
      }
   }
}
3
  • Thanks Oli. This is very helpful, but we are having trouble getting Matrix fields to work. Will respond more thoroughly when we have a solution. Dec 17, 2019 at 18:09
  • What sort of trouble?
    – Oli
    Dec 18, 2019 at 10:05
  • @SeanO'Brien did you end up finding a solution for Matrix fields? I'm working on something similar and would appreciate you posting it.
    – Udo
    Jan 27, 2020 at 14:08

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.