Skip to content

Working with modules in PHP

Intro

These page contains functions for working with modules in PHP.

For JavaScript ( v7+ only) check this page

Querying modules

A Page entity offers access to a PageEditorModulesCollection instance that wraps its own modules and helps querying modules. In the subsequent section, module refers to an array containing the data related to that module.

php
$collection = $page->getModulesCollection();

// the 'title' module
$collection->getRoledModule('title'); 
// an array of all 'paragraph' modules
$collection->getRoledModules('paragraph'); // returns the data array corresponding to the 'title' module

// the string contents of the 'title' module
$collection->getTextModuleContent('title');
// an array with the string contents of all paragraphs
$collection->getTextModulesContents('paragraph');

The methods above accept either a string, for top level modules, or an array containing the full role path to the module, in case of submodules:

php
$collection->getTextModuleContent(['main_image', 'description']);

Getting embedded resources (content models)

php
// The main image used in the current page. 
// Xalok uses PageEditorModuleCollection::getMainImagesRoles
// method to see if the page has a module with one of these roles.
// If no such module is found, it takes the first image in the page and uses that
// as the main image
$collection->getMainImage();

// an array with `Image` entities used in the current page
$collection->getImages();
// an array with `Video` entities used in the current page
$collection->getVideos();
// an array with `Audio` entities used in the current page
$collection->getAudios();
// an array with `File` entities used in the current page
$collection->getFiles();
// an array with `Page` (most likely articles) entities used in the current page
// if there are manual articles linked in the board, they will be returned
// if there's a listing embedded in the board, the articles of that listing will be returned
$collection->getRelated();
// an array with `PageListing` entities used in the current page 
// use it to extract listings inside boards, for example
$collection->getListings();
// an array with all entities related to the current page,
// optionally filtered by type ('image', 'video', 'audio', etc.)
$collection->getContentModels($type = null);

Note: On versions prior to v8.0 There's also $page->getRelated() method that is a relic from previous versions and returns nothing. On v8.0+ this method is a proxy for $collection->getRelated() (they return the same thing). Note: There's also $page->getRelatedPages(). This returns the same as $collection->getRelated() if there are any pages included, but, if the given $page has an automated listing, it returns the articles shown by that listing.

Looping modules

Although the modules' array seems simple to loop through (foreach ($modules['__roles'] as $role)...), there are countless issues that arose from the fact that there are edge cases that developers seemed to have missed.

Because of this, you are encouraged to use eachModule helper that irons out some of these edge cases and offer some convenience:

php
$page->getModulesCollection()->eachModule(
   function ($moduleData, $role, $primaryRole, $rolePath, $primaryRolePath) {
      // do something with the data here
   }
}

As you see, the callback where you need to do work receives, besides the module's data ($module) and role ($role) some additional arguments that one might need:

eachModule receives the following arguments:

  • $cb (required): the callback to call for each module that has data. This receives the following arguments:
    • $moduleData: module's data
    • $role: module's role (e.g. paragraph, or paragraph--1)
    • $primaryRole: the primary role of the given module (e.g. paragraph for both paragraph and parangraph--1 modules) - useful if one needs to know whether this module is of a certain type (e.g. $primaryRole === 'paragraph')
    • $rolePath: the full role path of the current module - useful if the module that we're interested in is a submodule of another one
    • $primaryRolePath: a combination of the $primaryRole and $rolePath above - the path of primary roles.
  • $elements (optional, default null): pass this as non-null if you want to loop through a different array instead of the page's modules. Example: Loop through all the submodules of the main_image module
    php
    $collection = $page->getModulesCollection;
    $collection->eachModule(
       function() {
          // do something here
       },
       $collection->getRoledModule('main_image'),
       $recursive = true,
       $rolePath = ['main_image']
    )
  • $recursive (optional, default true): whether one wants to dig through submodules (true) or only through top level modules (false)
  • $rolePath (optional, default []): see the example for the $elements argument - it also passes this argument, the role paths that get to the $cb are prefixed with this value - useful if one passes the $elements argument and needs to know the full role path, not the one relative to the $elements argument.

Building modules

Building modules intro

The body of the articles must be composed as a modules array. XalokNext offers the wf_cms.page_editor_module_builder (aliased as Wf\Bundle\CmsBaseBundle\Templating\ModulesBuilder) service.

php
$modules = [];

$modulesBuilder->add($modules, 'title', ['content' => 'Article title']);
// for text modules there's a shorter version:
$modulesBuilder->addTextModule($modules, 'title', 'Article title');

// add composite modules
$modulesBuilder->addTextModule($modules, ['main_image', 'description'], 'Image description');

// link an image to the module
$modulesBuilder->add($modules, 'main_image', $content = null, $imageEntity);

The add* methods return the role path of the added module.

php
$modulesBuilder->addContentModel($modules, ['gallery', 'slide'], $image1);
$modulesBuilder->addContentModel($modules, ['gallery', 'slide--1'], $image2);

Specify increment

When called repeatedly with the same role path, ModulesBuilder adds the module in the last part of the role path:

php
$modulesBuilder->addTextModule($modules, ['gallery', 'slide', 'image_description'], 'Description1');
$modulesBuilder->addTextModule($modules, ['gallery', 'slide', 'image_description'], 'Description2');

will result in one gallery.slide module with two image_description (image_description and image_description--1) inside it.

One can specify in which part of the role path should the second module be added by appending -- to its role:

php
// add slides in a gallery by adding the image description submodule
$modulesBuilder->addTextModule($modules, ['gallery', 'slide--', 'image_description'], 'Description1');
$modulesBuilder->addTextModule($modules, ['gallery', 'slide--', 'image_description'], 'Description2');

This will result in two gallery.slide modules, each with their own image_description.

Note: While this shortcut might prove to be useful in some cases, creating the slide modules separately is supported in the same way and it makes the code more readable:

php
$slide1 = [];
$modulesBuilder->add($slide1, 'image_description', 'Description1');

$slide2 = [];
$modulesBuilder->add($slide2, 'image_description', 'Description2');

$modulesBuilder->add($modules, ['gallery', 'slide'], $slide1);
$modulesBuilder->add($modules, ['gallery', 'slide'], $slide2);

v6+Positioning modules

The add* methods recieve a Wf\Bundle\CmsBaseBundle\Templating\ModulePosition argument:

php
$modulesBuilder->addTextModule(
   $modules, 
   'paragraph', 
   'Automatic first paragraph', 
   $contentModel = null, 
   new ModulePosition(ModulePosition::POSITION_FIRST)
);
$modulesBuilder->addTextModule(
   $modules, 
   'paragraph', 
   'Automatic last paragraph', 
   $contentModel = null, 
   new ModulePosition(ModulePosition::POSITION_LAST)
);
$modulesBuilder->addTextModule(
   $modules, 
   'paragraph', 
   'Automatic after some paragraph', 
   $contentModel = null, 
   new ModulePosition(ModulePosition::POSITION_AFTER, 'paragraph--2')
);
$modulesBuilder->addTextModule(
   $modules, 
   'paragraph', 
   'Automatic before some paragraph', 
   $contentModel = null, 
   new ModulePosition(ModulePosition::POSITION_BEFORE, 'paragraph--2')
);

Removing modules

php
$modulesBuilder->remove('paragraph'); // removes module with wfRolePath ['paragraph'];
$modulesBuilder->remove('paragraph--2'); // removes module with wfRolePath ['paragraph--2'];
$modulesBuilder->removeAll('paragraph'); // removes *all* modules with primary role 'paragraph' (wfRolePath ['paragraph'], ['paragraph--1'], ['paragraph--2'], etc.)

In PageSerializer

XalokNext:6.0 comes with AbstractModulesPageSerializer that helps querying and modifying the modules before they're sent to NodeJS for Server Side Rendering.