Events
Festi provides a event-driven architecture that allows different parts of the application to communicate efficiently. Events are triggered throughout the execution of a Festi application, and you can subscribe to these events to execute specific logic when they occur.
The Core
primarily acts as an Event Bus, dispatching events that can be listened to and handled by plugins, DGS (Data Grid Store), themes, and other system components.
System Events
Core::EVENT_ON_AFTER_INIT
This event is triggered after Core has been initialized and all plugins have been initialized, meaning all the init.php
files of the plugins have been called.
For example, if we need to use a constant declared in the init.php
of another plugin in our plugin:
init.php:
assert($this instanceof Core);
$this->addEventListener(Core::EVENT_ON_AFTER_INIT, function () {
$this->addEventListener(IRpc::EVENT_ON_TOKEN_LOGIN, function (FestiEvent &$event) {
Core::getInstance()->getPluginInstance('GoogleApi')->onInterceptLoginByToken($event);
});
});
IRpc
is declared in another plugin, and ifIRpc::EVENT_ON_TOKEN_LOGIN
is called outside of theCore::EVENT_ON_AFTER_INIT
event, there's a high chance you'll get a Fatal Error.
Core::EVENT_ON_CREATE_STORE
After a DGS is created, the Core::EVENT_ON_CREATE_STORE
event is triggered. It can be used to modify all DGS in the system or specific ones.
Target: * store - reference to the DGS
$stores = array();
$this->core->addEventListener(Core::EVENT_ON_CREATE_STORE, function (FestiEvent &$event) use (&$stores) {
$stores[] = &$event->getTargetValueByKey('store');
});
Core::EVENT_PLUGIN_INIT
This event is triggered during the initialization of a plugin, before the plugin's onInit
method is called.
Target: * plugin - reference to the plugin
Core::EVENT_ON_RESPONSE
This event is triggered before the response is sent to the request.
Target = Response
$this->core->addEventListener(Core::EVENT_ON_RESPONSE, function (FestiEvent &$event) {
$response = $event->getTarget();
assert($response instanceof Response);
$response->setOverride(true);
$this->core->removeEventListenersByType(Core::EVENT_ON_RESPONSE);
});
ISystemPlugin::EVENT_ON_BEFORE_REQUEST_PLUGIN_METHOD
This event is triggered before a plugin method is called by reference.
Target:
* params
- reference to the array of parameters being passed to the method
* response
- reference to the Response
instance
* plugin
- name of the plugin being called
* method
- name of the method being called
Writing an Interceptor for All Plugin Requests
We need to ensure that users of type Student
have access only to their institution for all requested methods:
class SchoolPlugin extends DisplayPlugin
{
public function onRequestInterceptor(FestiEvent &$event)
{
$params = $event->getTargetValueByKey('params');
if (App::isStudent()) {
if (!array_keyExists(1, $params)) {
throw new NotFoundException("Undefined company ID");
}
$idRequestedCompany = $params[1];
App::validateCompanyPermission($idRequestedCompany);
}
return true;
}
}
init.php:
$this->addEventListener(
ISystemPlugin::EVENT_ON_BEFORE_REQUEST_PLUGIN_METHOD,
function (FestiEvent &$event) {
Core::getInstance()->getPluginInstance('School')->onRequestInterceptor($event);
}
);
Create Custom Event and Pass Through the Core
You can create a custom event by extending FestiEvent
and pass it through the Core, allowing other parts of your project to listen for and handle the event.
Here’s an example of creating a custom event:
class NotFoundContentEvent extends FestiEvent
{
private string $_url;
private Response $_response;
private bool $isFound = false;
public function __construct(Response &$response, string $url)
{
parent::__construct($this::class);
$this->_url = $url;
$this->_response = &$response;
}
public function getUrl(): string
{
return $this->_url;
}
public function setFound(bool $isFound): void
{
$this->isFound = $isFound;
}
public function isFound(): bool
{
return $this->isFound;
}
}
````
To dispatch the custom event through Core, use the following code:
```php
$event = new NotFoundContentEvent($response, $url);
$this->core->dispatchEvent($event);
if (!$event->isFound()) {
throw new NotFoundException();
}
return true;
Once the event has been dispatched, you can subscribe to it from anywhere in your project, such as `init.php`` in any plugin, or from any other location:
assert($this instanceof Core);
$this->addEventListener(Core::EVENT_ON_AFTER_INIT, function () {
Core::getInstance()->addEventListener(NotFoundContentEvent::class, function (NotFoundContentEvent &$event) {
// Handle the custom event
});
});
Cross-Plugin Events via Core
When you need to link one plugin's event to another, subscribe to the expected event in the plugin's init.php
:
class PluginA
{
public function onUpdate(Response &$response, ?int $idUser = null)
{
...
$event = new FestiEvent(PLUGIN_A_EVENT_UPDATE, $target);
$this->core->dispatchEvent($event);
...
}
}
PluginB init.php
:
$this->addEventListener(
ISystemPlugin::EVENT_ON_BEFORE_REQUEST_PLUGIN_METHOD,
function (FestiEvent &$event) {
$plugin = Core::getInstance()->getPluginInstance('PluginB');
Core::getInstance()->addEventListener(
PLUGIN_A_EVENT_UPDATE,
function (FestiEvent &$event) use ($plugin) {
$plugin->onUpdatePluginA($event);
}
);
}
);
Theme Events
ITheme::EVENT_THEME_ON_PREPARE_LOGO
In the event’s $target
, you will find:
* link
- the URL that the logo leads to
* logo
- the logo's src attribute
* content
- if specified, $target['content']
will be rendered within the logo
init.php:
<?php
$this->addEventListener(
Core::EVENT_ON_REQUEST,
function () {
$companiesPlugin = Core::getInstance()->getPluginInstance('Companies');
Core::getInstance()->getSystemPlugin()->addEventListener(
ITheme::EVENT_THEME_ON_PREPARE_LOGO,
array(&$companiesPlugin, 'onPrepareCompanyLogo')
);
}
);
CompaniesPlugin.php:
<?php
class CompaniesPlugin extends StagePlugin
{
public function onPrepareCompanyLogo(FestiEvent &$event)
{
$target = &$event->getTarget();
// ...
if (is_null($company['logo'])) {
return;
}
// ...
$target['logo'] = $logo;
}
}