Step by Step Guide: Create a Cached Config Container¶
Introduction¶
The cached configuration container in your extension could be used to store any sort of configuration in code to have it easily and quickly accessible.
Configuration could be anything, such as custom groups, custom fields, activity types, case types etc...
The cached configuration container is an on the fly generated PHP file and stored in the templates_c
folder of your CiviCRM installation. We don't need any extra database calls to retrieve the configuration it is all in code and therefore extremely fast to load.
In this guide we create a basic container for relationship types and for a custom group and a custom field.
Building the cached config container
When no cached config container exists one is created and then the following happens:
- The function
ConfigContainer::build
is called with a containerBuilder object as a parameter. In the functionbuild
we define how the ConfigContainer should be build when it is requested. - The result from the
containerBuilder
object is stored as PHP code in the templates_c folder
Creating a config container instance
An instance of the config container is created by checking if the PHP file exists in the templates_c
folder.
If it exists then it is included and the config container object is created.
Requirements
We need two files:
ConfigContainer
class, this one holds the actual configurationConfigContainerBuilder
class, this one holds the functionality to generate a ConfigContainer class.
Create the config container skeleton¶
First we need to create the skeleton of the config container.
The config container extends the symfony dependency injection Container
class, this makes it easier to store it in a PHP file.
namespace \Civi\MyExtension;
use \Symfony\Component\DependencyInjection\Container;
use \Symfony\Component\DependencyInjection\ContainerBuilder;
class ConfigContainer extends Container {
/**
* Build the container with the custom field and custom groups.
*
* @param ContainerBuilder $containerBuilder
*/
public static function build(ContainerBuilder $containerBuilder) {
// This is where the actual retrieval of configuration happens.
}
}
The first three lines consist of the namespace declaration and the use statements.
We both need the Container
and ContainerBuilder
class. Those classes are from the symfony dependency injection system.
Then we define the name of our class: ConfigContainer
extending the symfony Container
class.
Retrieve and set the configuration¶
In this step we will define the contents of the config container. We will lookup a custom group (with the name MyCustomGroup), a custom field (with the name MyCustomField) and a relationship type (with the name Is Child).
We do this in the build
function of the ConfigContainer
class.
class ConfigContainer extends Container {
/**
* Build the container with the custom field and custom groups.
*
* @param ContainerBuilder $containerBuilder
*/
public static function build(ContainerBuilder $containerBuilder) {
$customGroup = civicrm_api3('CustomGroup', 'getsingle', ['name' => 'MyCustomGroup']);
$customField = civicrm_api3('CustomField', 'getsingle', ['name' => 'MyCustomField']);
$childRelationshipType = civicrm_api3('RelationshipType', 'getsingle', ['name_a_b' => 'Is Child']);
// Now store the data in the container builder.
$containerBuilder->setParameter('customGroup', $customGroup);
$containerBuilder->setParameter('customField', $customField);
$containerBuilder->setParameter('childRelationshipType', $childRelationshipType);
}
}
Creating the Getter functions¶
Now it is time to add the getter methods to the class. We can access the data passed to the builder with the getParameter
function. For maintainability, it is better to use our own defined getter methods so that we can make sure the return value is what we expected.
We will define the following getter methods:
GetMyCustomGroupId
: to return the ID of the custom groupGetMyCustomFieldId
: to return the ID of the custom fieldGetChildRelationshipTypeId
: to return the ID of the Is Child relationship type
class ConfigContainer extends Container {
/**
* @return int
*/
public function getMyCustomGroupId() {
return $this->getParameter('CustomGroup')['id'];
}
/**
* @return int
*/
public function getMyCustomFieldId() {
return $this->getParameter('CustomField')['id'];
}
/**
* @return int
*/
public function getChildRelationshipTypeId() {
return $this->getParameter('childRelationshipType')['id'];
}
/**
* Build the container with the custom field and custom groups.
*
* @param ContainerBuilder $containerBuilder
*/
public static function build(ContainerBuilder $containerBuilder) {
// ... see previous step
}
}
Create the ConfigContainerBuilder class¶
We need to do one more thing and that is to create the config container builder class. This class takes care of creating and caching the ConfigContainer
.
The ConfigContainerBuilder
class follows the singleton pattern.
An instance is returned by the function getInstance
, if a cached class already exists it will include the cached class, if it does not exist it will create one.
The cached container is stored in the templates_c directory of CiviCRM. So whenvever the CiviCRM cache is cleared our cached config container class is also removed.
First we create the class by putting it in the same namespace as the ConfigContainer
class.
Next we define a getInstance
function where all the magic happens:
- First we check whether we already have an existing instance of
configContainer
if so we return that one. - In the next bit we define the filename in
$file
. The filename contains an ID of the environment. - If the file
$file
exists we will include it and create and return an instance ofMyCachedConfigContainer
, which is a sub class ofConfigContainer
. - In the next bit we will build and create the file
$file
. We do this by creating a newContainerBuilder
class and pass this to our earlier definedbuild
function ofConfigContainer
class. - Lastly we will write the
ContainerBuilder
to the PHP file ($file
) with thePhpDumper
class. We state that we want to call the classMyCachedConfigContainer
and that it is a subclass ofConfigContainer
.
namespace \Civi\MyExtension;
use \Symfony\Component\DependencyInjection\Container;
use \Symfony\Component\DependencyInjection\ContainerBuilder;
use \Symfony\Component\DependencyInjection\Dumper\PhpDumper;
class ConfigContainerBuilder {
/**
* @var \Symfony\Component\DependencyInjection\Container
*/
private static $configContainer;
/**
* @return \Civi\MyExtension\ConfigContainer
*/
public static function getInstance() {
if (!self::$configContainer) {
$envId = \CRM_Core_Config_Runtime::getId();
$file = CIVICRM_TEMPLATE_COMPILEDIR."/MyCachedConfigContainer.{$envId}.php";
if (!file_exists($file)) {
// Create the builder for the config container
$containerBuilder = new ContainerBuilder();
ConfigContainer::build($containerBuilder);
$containerBuilder->compile();
// Store the builder as a php file.
$dumper = new PhpDumper($containerBuilder);
file_put_contents($file, $dumper->dump([
'class' => 'MyCachedConfigContainer',
'base_class' => '\Civi\MyExtension\ConfigContainer',
]));
}
// Include the cached config container and instanciate a new instance
require_once $file;
self::$configContainer = new \MyCachedConfigContainer();
}
return self::$configContainer;
}
}
How to use the ConfigContainer
¶
Below an example of how to use the ConfigContainer
class in your code.
We will first retrieve an instance of the ConfigContainer
class from the ConfigContainerBuilder
. We can then use this class to retrieve the configuration.
$configContainer = \Civi\MyExtension\ConfigContainerBuilder::getInstance();
$customGroupId = $configContainer->getCustomGroupId();
$isChildRelationshipTypeId = $configContainer->getChildRelationshipTypeId();
An example of the CachedConfigContainer¶
You can lookup the cached config container in the templates_c directory.
It has the filename of MyCachedConfigContainer.1234cdefgh.php (whereby 1234cdefgh
is a random string).
Opening the file gives something like this below. The function getDefaultParameters
contains the build configuration.
/**
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
class MyCachedConfigContainer extends \Civi\MyExtension\ConfigContainer
{
// ...
/**
* Gets the default parameters.
*
* @return array An array of the default parameters
*/
protected function getDefaultParameters()
{
return [
'customGroup' => [
'id' => '1',
'name' => 'MyCustomGroup',
'title' => 'My Custom group',
'extends' => 'Participant',
'extends_entity_column_id' => '2',
'extends_entity_column_value' => [0 => '1'],
'style' => 'Inline',
'collapse_display' => '0',
'weight' => '1',
'is_active' => '1',
'table_name' => 'civicrm_value_my_custom_group_1',
'is_multiple' => '0',
'collapse_adv_display' => '0',
'is_reserved' => '0',
'is_public' => '1',
],
'customField' => [
'id' => '2',
'custom_group_id' => '1',
'name' => 'MyCustomField',
'label' => 'My Custom Field',
'data_type' => 'String',
'html_type' => 'Text',
'is_required' => '0',
'is_searchable' => '1',
'is_search_range' => '0',
'weight' => '10',
'is_active' => '1',
'is_view' => '0',
'text_length' => '255',
'column_name' => 'my_custom_field_2',
'serialize' => '0',
'in_selector' => '0',
],
// ...
];
}
// ...
}
Test your knowledge¶
See also¶
- Symfony Dependency Injection
- Singleton creational design pattern
- The Data Processor extension has a working implementation.