Content Sources

Add dynamic content sources to the YOOtheme Pro page builder.

Sources in YOOtheme Pro use GraphQL for quering data. In GraphQL a static and hierarchical schema with strong typing is used to define an API. GraphQL makes it possible to generate precise queries for dynamic content fields, which deliver exactly the data that is needed. YOOtheme Pro uses webonyx/graphql-php as its GraphQL PHP library.

Handling dynamic content in YOOtheme Pro is split into two steps: schema generation and data resolving. Because it's not clear which parts of the schema are needed before evaluating a query, the whole schema is required at every request. A schema is dynamic and can look different on each system depending on the data structure. To avoid repeating the schema generation on every request, it's only generated in the YOOtheme Pro customizer and is cached for front-end requests. The cache mechanism serializes the schema into the GraphQL schema language and stores it in the cache directory cache/schema.gql of YOOtheme Pro. It can be quite useful during development to inspect the cached schema.

Dynamic content sources are defined by their object type configurations and their field resolution which returns the actual data. These object types are part of the type hierarchy which forms the GraphQL schema.


Getting Started

Content sources can be added to the page builder by using a custom module. The easiest way to get started is to try out the example module which adds a custom content source or take a look at the included YOOtheme Pro content sources.

Example Module

The example module on Github demonstrates how to add custom content sources. Simply download and unzip the example module. The quickest way to try it out is using a child-theme.

Download View on Github

Included Sources

YOOtheme Pro comes with many built-in content sources. They are a useful resource to get started when creating your own custom content sources. They can be found in the respective module directory under vendor/yootheme in YOOtheme Pro.

Path Description
builder-source/src/Source/Type Source transform and builder integration
builder-source-filesystem/src/Type File system integration
builder-joomla-fields/src/Type Joomla custom fields integration
builder-joomla-source/src/Type Joomla common types for articles, categories and users
builder-joomla-zoo/src/Type ZOO extension integration
builder-wordpress-source/src/Type WordPress common types for pages, posts and taxonomies
builder-wordpress-acf/src/Type Advanced custom fields plugin integration
builder-wordpress-popular-posts/src/Type Popular posts plugin integration
builder-wordpress-toolset/src/Type Toolset plugin integration

Object Types

Object types are the most basic components of a GraphQL schema. They represent a kind of object and its fields. To define GraphQL object types a code-first approach is used. It allows to dynamically build a schema depending on the system environment. Use an array to create a simple object type configuration.

[

   // Set type fields and their configuration
   'fields' => [

   ],

    // Set metadata to define the UI in the customizer
   'metadata' => [

       // Label used in the customizer
       'label' => 'My Type',

       // Make the type usable as dynamic content source
       'type' => true,

   ]

]

The following property keys can be used in the object type configuration array.

Option Type Description
fields array Set type fields and their configuration.
metadata array Set metadata to define the UI in the customizer.
extensions array Set extensions used to add resolver functions and services.

Fields

Fields of an object type can be defined in the fields array. Just add a field name and its field definition.

[
   'fields' => [

       'my_field' => [

           'type' => 'String',

           'metadata' => [

               // Label shown in the field mapping select box in the customizer
               'label' => 'My Field',

               // Option group within the field mapping select box
               'group' => 'My Group'
           ],

           'extensions' => [

                // A static resolver function to resolve the field value
                'call' => 'MyResolver::resolveFn'
           ]

       ]
   ]
]

Every field is defined by the following properties.

Option Type Description
name string Name of the field. When not set, inferred from args array key.
type string Type of the field like object, scalar or list of types
args array Set arguments configurations.
metadata array Set metadata to define the UI in the customizer.
extensions array Set extensions used to add resolver functions and services.

Note The naming convention for field names is snake_case. Spaces or hyphens in field names will break the schema. Use the string helper YOOtheme\Str::snakeCase($string) to conveniently convert names to snake case.

Scalar Types

Like other programming languages GraphQL provides built-in scalar types like String, Int, etc. Usually the String type is used for the fields.

[
    'type' => 'String',
]

Lists of Types

List modifiers can be used to define a list of scalar or object types. The resolver function of a repeatable field has to return an array containing objects of the specified type. A field can be of any type which is defined in the schema including your own custom types.

[
    'type' => [
        'listOf' => 'MyType'
    ]
]

Field Resolution

All fields get resolved using a resolver function. Every field has a default resolver which returns the value of a property with the same name. To define a custom resolver function for a field, simply override the default resolver.

A resolver function can be defined with the extensions.call property in the field configuration. It needs to be a callable like a global or namespaced function or a static method.

[
    'extensions' => [

        'call' => 'MyResolver::resolveFn'

    ]
]

Note Closures can't be used because it's not possible to serialize them in the cached schema. Instead access the data from the configuration in the resolver function using the args option.

In order to pass static arguments to a resolver function, define the args option. The arguments will be available in the $args argument in the resolver function. All arguments have to be JSON serializable, because they are JSON encoded in the schema definition.

[
    'extensions' => [

        // Static function with arguments
        'call' => [
            'func' => 'MyResolver::resolveFn',
            'args' => [
                'arg1' => 'foo',
                'arg2' => 'bar',
            ]
        ]

    ]
]
Option Type Description
func string Name of the callable function
args array An array of arguments which will be serialized as JSON

The MyResolver::resolveFn() method has four parameters.

public static function resolveFn($obj, $args, $context, $info)
{

    // Add code to query the data here

    return 'the data';
}
Parameter Type Description
$obj mixed The result of the previous resolver
$args array An array of field arguments
$context mixed A context object which is an array of parameters passed to the builder
$info GraphQL\Type\Definition\ResolveInfo An object that contains information about the current GraphQL operation

Add Custom Sources

To extend the GraphQL schema in YOOtheme Pro with a custom source, register an event handler SourceListener::initSource to the source.init event.

include_once __DIR__ . '/src/SourceListener.php';
include_once __DIR__ . '/src/MyTypeProvider.php';
include_once __DIR__ . '/src/Type/MyType.php';
include_once __DIR__ . '/src/Type/MyQueryType.php';

return [

    'events' => [

        'source.init' => [
            SourceListener::class => ['initSource']
        ]        

    ]

];

The YOOtheme\Builder\Source service carries the schema definition and is passed as first argument to the initSource listener.

Use the YOOtheme\Builder\Source::objectType($name, $config) method to add a custom type. The first argument is the name of the type and the second its configuration. The naming convention for type names is UpperCamelCase. Use the string helper YOOtheme\Str::camelCase($string, true) to conveniently convert names to upper camel case.

A GraphQL schema has a top-level entry point which is usually called the Query type. To make MyType queryable as custom source in the Dynamic Content option, extend the Query type by invoking the YOOtheme\Builder\Source::queryType($config) method. The method merges the MyQueryType configuration with the Query type.

class SourceListener
{
    public function initSource($source)
    {
        $source->objectType('MyType', MyType::config());
        $source->queryType(MyQueryType::config());
    }
}

It's recommended to modularize a type definition with its configuration and resolver functions into a PHP class.

class MyType
{
    public static function config()
    {
        return [

            'fields' => [

                'my_field' => [
                    'type' => 'String',
                    'metadata' => [
                        'label' => 'My Field'
                    ],
                    'extensions' => [
                        'call' => __CLASS__ . '::resolve'
                    ]
                ]

            ],

            'metadata' => [
                'type' => true,
                'label' => 'My Type'
            ]

        ];
    }

    public static function resolve($obj, $args, $context, $info)
    {
        // Add code to query the data here

        return $obj->my_field;
    }
}

Custom source field

To make the object type MyType available in the Query type, add a field which returns the MyType object type.

class MyQueryType
{
    public static function config()
    {
        return [

            'fields' => [

                'custom_my_type' => [

                    'type' => 'MyType',

                    // Arguments passed to the resolver function
                    'args' => [

                        'id' => [
                            'type' => 'String'
                        ],

                    ],

                    'metadata' => [

                        // Label in the dynamic content select box
                        'label' => 'Custom MyType',

                        // Option group in the dynamic content select box
                        'group' => 'Custom',

                        // Fields to input arguments in the customizer
                        'fields' => [

                            // The array key corresponds to a key in the `args` array above
                            'id' => [

                                // Field label
                                'label' => 'Type ID',

                                // Field description
                                'description' => 'Input a type ID.',

                                // Default or custom field types can be used
                                'type' => 'text'

                            ],

                        ]

                    ],

                    'extensions' => [
                        'call' => __CLASS__ . '::resolve',
                    ],

                ],

            ]

        ];
    }

    public static function resolve($item, $args, $context, $info)
    {
        return MyTypeProvider::get($args['id']);
    }
}

Custom source

The MyTypeProvider is used to query MyType objects.

class MyTypeProvider
{    
    public static function get($id)
    {
        // Query objects
        return (object) ['my_field' => 'the data'];
    }
}

Extend Sources

The type configurations are merged recursively when Source::objectType is invoked multiple times with the same type name as argument. This allows defining new fields for any existing object type. The following code example adds a new field to an object type called Post.

$source->objectType('Post', [

    'fields' => [

        'my_new_post_field' => [

            // Add code for the field configuration here

        ]

    ]

]);

Intermediate Types

To avoid field naming conflicts for the Post type, either prefix your field names or use an intermediate subtype. The subtype will define all field names without overriding any existing Post type fields.

Note Mind that the intermediate subtype lacks a 'type' => true metadata property.

$source->objectType('MySubType', [

    'fields' => [

       'title' => [

           'type' => 'String',

           'metadata' => [
               'label' => 'My Title',
            ],

        ],

    ],

    'metadata' => [
        'label' => 'My SubType'
    ],

]);

$source->objectType('Post', [

    'fields' => [

        'my_subtype' => [

            'type' => 'MySubType',

            'extensions' => [
                'call' => 'MySubTypeResolver::resolveFn',
            ],

        ]

    ]

]);
YOOtheme Pro Documentation