Extending Bolt / The Basics of Extensions
Note: You are currently reading the documentation for Bolt 2.2. Looking for the documentation for Bolt 5.2 instead?
- What can you do with Bolt extensions?
- Coding your extensions
- The initialize() and getName() functions
- Initialize a snippet
- Add a CSS or Javascript file
- Add a Twig function or filter
- Adding storage events
- Using your own config.yml file
- Using the global configuration
- Overriding the default 'Content' class
- Further reading
What can you do with Bolt extensions?¶
The functionality of Bolt can be extended by creating Extensions. The possibilites are almost limitless but here are a few of the basic ideas that can be accomplished:
- Add Twig tags or modifiers, for use in the templates in your themes.
- Add 'hooks' in the templates to either insert small snippets of HTML or the result of a callback-function in the templates after rendering.
- Create custom fields that can be used in
contenttypes.yml
. - Create themes that other users can copy and use as a baseline.
- Add custom upload handlers that support different filesystems.
- Add a custom thumbnail generator that does more advanced creation of thumbs
A Bolt extension has to follow a few strict rules, so it can be auto-loaded by Bolt and to make sure it won't interfere with other Bolt functionality or even other Extensions. To do this, we have to keep the following rules:
- An extension must be placed on the Extension Marketplace.
- Each extension has its own PSR-4 namespace.
- If the extension has its own configuration, it should be in a
config.yml
file in the extension folder. - The extension should come with a 'readme' file. It must be named
readme.md
, and is written in the Markdown format. - The
extension
defines two methods namedgetName()
andinitialize()
. - The 'entry points' for callbacks and Twig functions and modifiers must be functions in the defined namespace. Additional code can be procedural or implemented in classes.
To get the hang of how extensions work, it's best to have a look for other popular extensions on the Marketplace. They all have a link to the source code on the information page..
Coding your extensions¶
Because Bolt is written in PHP, it should be no surprise that the extensions must also be written in PHP. Bolt is built upon the awesome Silex micro- framework, and uses a lot of components from the Symfony framework. When coding your extensions, you should use as much of the functionality provided by Silex and the included components as possible. Don't re-invent the wheel, and things like that.
See the chapter on Bolt internals for a detailed overview of the provided Bolt functionality, Silex objects and included libraries.
Bolt strives to adhere to the PSR-2 coding style. When writing your extensions, you should try to do the same.
The initialize()
and getName()
functions¶
Every extension needs to have a function named 'initialize()' in its registered extension class. The initialize() function is called for each page request. It's executed before content is retrieved from the database or the templates are parsed. In general, the initialize() function is used to set up hooks or functionality that is used later on in the process of outputting a page.
The getName()
method tells Bolt how you want the extension to be referred to.
The initialized extension object will be mounted onto the main Bolt Application,
so, for instance, using the following method:
function getName()
{
return "widgetpicker";
}
When the extension is registered a shareable instance of the extension will be
available at $app['extensions.widgetpicker']
initialize()¶
The initialize() function is called for the initialisation of each active extension. It's used to set up functionality to be used in the templates of for Twig function. It looks like this:
function initialize()
{
$this->addSnippet('endofbody', 'facebookScript');
$this->addTwigFunction('facebooklike', 'facebookLike');
}
As you can see the function is called without a parameter, and basically
contains the entirety of the Bolt application and objects. As such, it is often
vital to attach your functionality to whatever it should be attached to. The
init function can return true
or false
to denote whether the initialisation
was successful.
In the case of initialize()
it's often used to do the following:
- Initialize a snippet, either as a 'string' or a 'callback function'
- Add a Javascript and/or CSS file to the output of the frontend pages
- Make sure jQuery is included, if needed by your extension
- Initialize a Twig function or filter for use in the theme templates.
Initialize a snippet¶
A 'snippet' in the context of Bolt extensions is nothing more than a short
fragment of code, that gets inserted in a certain place in the theme templates
on the frontend of the website. This fragment can either be a string to be
inserted directly, or it can be the return value of a callback string. The
addSnippet()
function takes three parameters, with the third being
optional. The first is the position of the insertion point. The second one is
either a string to insert or the name of a callback function. The third,
optional parameter will be passed on to the callback function.
Snippets can be inserted in several places in the outputted HTML:
startofhead
- after the<head>
-tag.aftermeta
- after the last<meta [..] >
-tag.aftercss
- after the last<link [..] >
-tag.beforejs
- before the first<script [..] >
-tag.afterjs
- after the last<script [..] >
-tag.endofhead
- before the</head>
-tag.startofbody
- after the<body>
-tag.endofbody
- before the</body>
-tag.endofhtml
- before the</html>
-tag.afterhtml
- after the</html>
-tag.
To insert a string snippet, use:
function initialize()
{
$this->addSnippet('endofbody', '<!-- a snippet of HTML -->');
}
To insert a snippet using a callback function, use the following. You can pass one extra optional variable, which can be a simple scalar or an array.
function initialize()
{
$this->addSnippet('endofbody', 'snippetbar', $foo);
}
function snippetbar($foo)
{
return "<b>Var is $foo.</b>";
}
However, don't use this to pass a 'live' version of $app
. In the callback
function this will already be available as $this->app
.
The most important locations have aliases that are mainly for ease of remembering:
startofhead
is aliased to:beforeheadmeta
,beforeheadjs
andbeforeheadcss
endofhead
is aliased to:afterheadmeta
,afterheadjs
andafterheadcss
startofbody
is aliased to:beforebodyjs
andbeforebodycss
endofbody
is aliased to:afterbodyjs
andafterbodycss
Add a CSS or Javascript file¶
You can use the initialize()
function to add CSS or Javascript files to the
outputted HTML in the frontend. To do so, use the addJavascript()
and
addCSS()
functions:
function initialize()
{
// Add CSS file
$this->addCSS($this->app['resources']->getPath('extensions/Namespace/assets/namespace.css'));
// Add javascript file
$this->addJavascript(
$this->app['resources']->getPath('extensions/Namespace/assets/namespace.js'),
array('late' => true, 'priority' => 1000)
);
}
Both of these functions take three parameters:
- An absolute path to the desired .js or .css file. Use
$this->app['resources']->getPath()
function to always get the correct path, regardless of how Bolt is installed. See the Paths section in Internals for more details. - An (optional) associative array:
late
— Eithertrue
(end of the HTML<body>
) orfalse
(inside HTML<head>
)priority
— Loading priority, any lower value will get inserted before, while any higher value will get inserted later.attrib
— A string containing either/or 'defer', and 'async'
Be careful though. If you insert dependant code before the relevant JavaScript itself, this will cause breakage.
There's a special function for adding jQuery to the outputted HTML. A lot of extensions might or might not require jQuery to function, and the developer of the HTML might have already included it.
If several extensions added additional jQuery includes, your HTML would quickly become a mess at best. Most likely it will break, because having more than one instance of jQuery might cause conflicts in your page. If your extension requires jQuery, use the following:
function initialize()
{
$this->addJquery();
}
This will make sure jQuery is added to the outputted HTML, but only if it's not
included by the theme developer already. It also will not be included more than
once, even if several extensions have addJquery()
in the
initialize()
-function.
It will correctly detect jQuery if it's already present in the templates, whether the templates are using the minified version or not, and whether it's a local version or one that's hosted on a remote CDN.
The version of jQuery included will be the one that ships with Bolt. This will most likely be the most recent one, at the time of the release of the Bolt version that you are running.
If you want to retrieve a list of all assets added by extensions. You can use
getAssets()
in your extension. Like so,
$assets = $this->getAssets();
$assets['js']; // all js files
$assets['css']; // all css files
Or if you want to retrieve them in a twig template, use app.extensions.assets
.
{{ dump(app.extensions.assets) }}
If you need to remove all other extensions assets you can call clearAssets()
.
This should only be done on an extension specific route and as a last recourse
as it denies other extensions access to addAsset. An example where this might
be needed is if another extension causes javascript errors with your javascript
on a backend view.
$this->clearAssets();
Add a Twig function or filter¶
Extensions can add Twig functions or filters to extend the templates used in the
frontend. This example will add a function {{ foo() }}
to use in the frontend
templates:
function initialize()
{
// Initialize the Twig function
$this->addTwigFunction('foo', 'twigFoo');
}
/**
* Twig function {{ foo("var1", "var2") }} in Namespace extension.
*/
function twigFoo($var1, $var2)
{
return "Twig function Namespace.";
}
Note that the Namespace must be included with 'Namespace\twigFoo'
, even though
the function is defined within the namespace of the extension. This is because
during the rendering of the templates, it is called from the context of the
parsed Twig templates, so it won't be able to find the function if the namespace
was omitted.
You can also define a Twig filter for use in the frontend. The following example
will add a filter 'bar', that can be used in the templates like {{ "foo"|bar }}
.
function initialize()
{
// Initialize the Twig filter
$this->addTwigFilter('foo', 'twigBar');
}
/**
* Twig filter {{ "var1"|bar("var2") }} in Namespace extension.
*/
function twigBar($var1, $var2)
{
$str = "Twig filter Namespace.";
return new \Twig_Markup($str, 'UTF-8');
}
Since these are just regular Twig functions/filters, you should familiarize yourself with how Twig works. Read the chapter Extending Twig in the Twig documentation.
Using Twig functions in content records¶
As you might know, Bolt has two 'sandboxed' Twig environments, to prevent
possible security exploits in the templates. This is to prevent having a
careless developer or implementor allow a user to insert Twig tags where they
should not. If you want your extension's Twig function to be available inside a
ContentType's record field, you will need to explicitly add an isSafe()
function to your Extension.php
file, that simply returns true
:
public function isSafe()
{
return true;
}
Note: You will also need to set allowtwig
to true
, in your ContentType
definition. If either one of them isn't set, the twig tag will not work inside
the content. This way you have maximum control over where it works, and where it
doesn't. See also 'Field Definitions' in the page 'ContentTypes and Records'.
Adding storage events¶
These events are dispatched when content objects are saved or deleted. Currently there are 4 events defined:
- preSave - called before a content save
- postSave - called after a content save
- preDelete - called before a content delete
- postDelete - called after a content delete
There are no events for specific content types. However you can use the passed
\Bolt\Events\StorageEvent
to retrieve the following information:
- $event->getId() - returns the id of the content
- $event->getContentType() - returns the content type
- $event->getContent() - returns the content object (only available in preSave/postSave)
- $event->isCreate() - returns
true
,false
ornull
depending on whether the content record was created, updated or deleted.true
- Record creation eventfalse
- Record update eventnull
- Record deletion event
An example to log whenever a content has been saved.
$app['dispatcher']->addListener(\Bolt\Events\StorageEvents::POST_SAVE, array('MyClass', 'postSaveCallback'));
function postSaveCallback(\Bolt\Events\StorageEvent $event)
{
$id = $event->getId();
$contentType = $event->getContentType();
$entry = date('Y-m-d H:i:s').' '.$contentType.' with id '.$id.' has been saved'."\n";
file_put_contents('storage.log', $entry, FILE_APPEND);
}
Using your own config.yml
file¶
Extensions can use their own config files using the YAML format. It should be
named config.yml.dist
, and it should be in the folder of your extension. Bolt will copy it as {extension-name}.{vendor-name}.yml
into /app/config/extensions
when it doesn't already exist there. The extension configuration will be automatically loaded. To use it in a snippet callback, or a Twig function or modifier, use it like this:
function foo()
{
// Make sure a '$name' is set
if (isset($this->config['name'])) {
$name = $this->config['name'];
} else {
$name = "default name";
}
return "Hello, $name.";
}
Using the global configuration¶
Sometimes you might need a global configuration variable in your extension. Usually you can read a global configuration variable like this:
function foo()
{
$prefix = $this->app['config']->get('general/database/prefix', 'bolt_');
}
Overriding the default 'Content' class¶
ContentTypes can specify the class to be used for records of that ContentType. This is useful for when you have a specific ContentType, and you would like to provide extra functionality to that single ContentType.
An extension can then define that class, overriding / extending the default
behaviour of \Bolt\Content
.
For example, in your ContentType, use:
entries:
name: Entries
singular_name: Entry
fields:
title:
type: text
class: large
(snip)
class: \MyContent\Content
And in your extension, do:
class Content extends \Bolt\Content
{
function foo() {
return "bar";
}
}
Then, in your template, use:
{{ entry.foo() }} /* prints 'bar' */
Further reading¶
If you want to delve deeper into what you can and cannot do with extensions, see the chapter on Bolt internals for a detailed overview of the provided Bolt functionality, Silex objects and included libraries.
Couldn't find what you were looking for? We are happy to help you in the forum, on Slack or on Github.