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?

  • What you're describing sounds a lot like the functionality of the built-in GraphQL server…? – August Miller Dec 4 '19 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? – August Miller Dec 4 '19 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. – Sean O'Brien Dec 4 '19 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. – August Miller Dec 5 '19 at 4:44
1

Assuming an endpoint for one entry like:

'elementType' => Entry::class,
'criteria' => [
    'id' => $entryId
],

You could:

Fetch all the custom fields for that particular entry:

function getFields($entry) {
    $fields = [];
    foreach ($entry->getFieldLayout()->getFields() as $field ) {
        $f['handle'] = $field->handle;
        $f['class'] = get_class($field);
        array_push($fields, $f);
    }
    return $fields;
}

Next using the above, you could fetch values for all your fields doing:

function getValues($entry) {

    $values = [];
    foreach (getFields($entry) as $field ) {
        switch ($field['class']) {
            case 'craft\fields\PlainText':
                $values[$field['handle']] = $entry->{$field['handle']};
            break;

            case 'craft\fields\Assets':
                $assets = [];
                foreach ($entry->{$field['handle']}->all() as $asset) {
                    array_push($assets, $asset->url);
                }
                $values[$field['handle']] = $assets;
            break;
        }
    }
    return $values;
}

This only shows you the basics, you would need to create a switch case for all available field types, of course.

Altogether:

<?php
use Craft;
use craft\elements\Entry;

function getFields($entry) {
    $fields = [];
    foreach ($entry->getFieldLayout()->getFields() as $field ) {
        $f['handle'] = $field->handle;
        $f['class'] = get_class($field);
        array_push($fields, $f);
    }
    return $fields;
}

function getValues($entry) {

    $values = [];
    foreach (getFields($entry) as $field ) {
        switch ($field['class']) {
            case 'craft\fields\PlainText':
                $values[$field['handle']] = $entry->{$field['handle']};
            break;

            case 'craft\fields\Assets':
                $assets = [];
                foreach ($entry->{$field['handle']}->all() as $asset) {
                    array_push($assets, $asset->url);
                }
                $values[$field['handle']] = $assets;
            break;
        }
    }
    return $values;
}

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,
                        'customFields' => getValues($entry)
                    ];
                },
            ];
        }
    ]
];

Would return 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":{

         }
      }
   }
}

This is by no mean the only way and can be refactored but it should give you an idea on how to achieve what you need.

| improve this answer | |
  • 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. – Sean O'Brien Dec 17 '19 at 18:09
  • What sort of trouble? – Oli Dec 18 '19 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 at 14:08

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.