Events
In an application there are many events for which some operation needs to be performed in response. In practice, you define listeners for an event; these listeners wait until the event calls them so that the operation defined in the listener is carried out.
For example, adding a new article is an event, after which a notification email needs to be sent; the email-sending operation is performed in a listener.
Each event can have several separate listeners.
To work with events, the framework provides the packages\base\event class.
For better consistency, it is best to place event-related code in a folder named events and listeners in a folder named listeners.
Events and their associated listeners are declared in the package.json file of the root directory under the events key.
The namespace of each event is declared in the name key, and its associated listener in the listener key.
{
"permissions": "*",
"routing": "routing.json",
"frontend": ["frontend", "panel", "userpanel"],
"autoload": {
"directories": ["controllers", "Models", "listeners", "events"]
},
"dependencies": ["userpanel", "sms", "email"],
"languages": {
"fa_IR": "langs/fa_IR.json"
},
"events": [
{
"name":"packages/userpanel/events/search",
"listener": "listeners/search@find"
},
{
"name":"packages/userpanel/events/search",
"listener": "listeners/users@list"
},
{
"name":"packages/sms/events/templates",
"listener": "listeners/sms@templates"
},
{
"name":"packages/email/events/templates",
"listener": "listeners/email@templates"
}
]
}
In the example above, two separate listeners, listeners/search@find and listeners/users@list, are defined for the packages/userpanel/events/search event.
Triggering an Event
To use an event, you must first create an object of the event class and call the trigger() method on that object.
The trigger() method is defined in the packages\base\events class and is invoked to run the event.
If you need to use the value of a variable when triggering an event, you can set it while creating the object of the event class.
For example, if you need the user's information to send a confirmation email during user registration, you can set the required data on the event class object when creating it.
namespace packages\packagename\controllers;
use themes\themename\views;
use packages\packagename\User as Model;
use packages\packagename\events\Email as Event;
use packages\base\{Controller, Response, View, Http};
class Users extends Controller {
public function insert(): Response {
$view = View::byName(views\users\Insert::class);
$this->response->setView($view);
if (Http::is_post()) {
$inputs = $this->checkInputs(array(
'name' => array(
'type' => 'string',
),
'lastname' => array(
'type' => 'string',
'optional' => true,
),
'email' => array(
'type' => 'email',
)
));
$user = new Model($inputs);
$user->save();
$event = new Event($user);
$event->trigger();
}
$this->response->setStatus(true);
return $this->response;
}
}
In the example above, the saved user is passed to the Email event object.
For more information about working with the database, refer to the Object-Oriented Database Interaction page.
Creating an Event Class
It is best to create the classes defined for events in the events folder. An event class must extend the packages\base\event class.
namespace packages\packagename\events;
use packages\base\Event;
use packages\packagename\User as Model;
class Email extends Event {
private $user;
public function __construct(Model $user) {
$this->user = $user;
}
public function getName(): string {
return $this->user->name . ' ' . $this->user->lastname ;
}
public function getEmail(): string {
return $this->user->email;
}
}
Creating a Listener Class
It is best to create listener classes in a separate folder named listeners.
When declaring events in the package.json file, a method is specified for each listener. The input of this method is an object of the event class, through which you can access the class's variables and methods.
{
"permissions": "*",
"routing": "routing.json",
"frontend": ["frontend", "panel", "userpanel"],
"autoload": {
"directories": ["controllers", "Models", "listeners", "events"]
},
"dependencies": ["userpanel", "sms", "email"],
"languages": {
"fa_IR": "langs/fa_IR.json"
},
"events": [
{
"name": "packages/packagename/events/Email",
"listener": "listeners/Email@templates"
}
]
}
namespace packages\packagename\listeners;
use packages\packagename\events\Email as Event;
class Email {
public $name;
public $userEmail;
public function templates(Event $event){
$this->name = $event->getName();
$this->userEmail = $event->getEmail();
$this->sendEmail();
}
private function sendEmail() {
$subject = "register successfully";
$body = $this->name. " Your registration completed successfully";
mail($this->userEmail, $subject, $body, self::SENDER);
}
}
Next, we present a complete example of events and their use in a theme.
We want to create settings for user registration in the application. These settings are used as the default values for a new user's information at registration time.
{
"permissions": "*",
"routing": "routing.json",
"frontend":"frontend",
"autoload": {
"directories": ["controllers", "events", "listeners", "models"]
},
"dependencies": ["base"],
"languages":{
"fa_IR": "langs/fa_IR.json",
"en_US": "langs/en_US.json"
},
"events": [
{
"name":"events/Settings",
"listener": "listeners/Settings@init"
}
]
}
namespace packages\packagename\controllers;
use themes\themename\views;
use packages\base\{Controller, Response, View, Http};
use packages\packagename\events\Settings as SettingsEvent;
class Settings extends Controller {
public function view(): Response {
$view = View::byName(views\Settings::class);
$this->response->setView($view);
$event = new SettingsEvent();
$event->trigger();
if (!$event->get()) {
throw new NotFound();
}
$view->setSettings($event->get());
$this->response->setStatus(true);
return $this->response;
}
}
In the controller, when the view method is called, the SettingsEvent event is triggered.
After the event runs, the get method defined in the event class is called, and the settings are passed to the view via the setSettings method.
namespace packages\packagename\events;
use packages\base\Event;
use packages\packagename\events\settings\Setting;
class Settings extends Event {
private $settings = [];
public function addSetting(Setting $setting) {
$this->settings[$setting->getName()] = $setting;
}
public function get(){
return $this->settings;
}
}
Two methods are defined in this event. The addSetting method is created to add a setting; its input is an object of the Setting class.
The get method returns the added settings.
namespace packages\packagename\events\settings;
class Setting {
private $name;
private $package;
private $inputs = [];
private $fields = [];
private $controller;
private $data = [];
public function __construct($name) {
$this->setName($name);
}
public function setName(string $name) {
$this->name = $name;
}
public function getName():string {
return $this->name;
}
public function addField(array $field) {
$this->fields[] = $field;
}
public function getFields():array {
return $this->fields;
}
public function setDataForm(string $name, $value) {
$this->data[$name] = $value;
}
public function getDataForm(string $name = '') {
if ($name) {
return $this->data[$name] ?? null;
}
return $this->data;
}
}
This class is created as a helper class for the Settings event. In this class, the properties of the inputs required for the settings are set. We can also specify default data for the fields.
namespace packages\packagename\listeners;
use packages\packagename\User as UserModel;
use packages\packagename\Usertype as UsertypeModel;
use packages\packagename\events\Settings as SettingsEvent;
class Settings {
public function init(SettingsEvent $settings){
$setting = new SettingsEvent\Setting('userpanel');
$this->addRegisterItems($setting);
$settings->addSetting($setting);
}
private function addRegisterItems(SettingsEvent\Setting $setting) {
$setting->addField(array(
'name' => 'userpanel_register_enabled',
'type' => 'radio',
'label' => t('settings.userpanel.register'),
'inline' => true,
'options' => array(
array(
'label' => t('active'),
'value' => 1,
),
array(
'label' => t('deactive'),
'value' => 0,
),
),
));
$setting->addField(array(
'name' => 'userpanel_register_type',
'type' => 'select',
'label' => t('settings.userpanel.register.usertype'),
'options' => $this->getUserTypesForSelect(),
));
$setting->addField(array(
'name' => 'userpanel_register_status',
'type' => 'select',
'label' => t('settings.userpanel.register.status'),
'options' => $this->getUserStatusForSelect(),
));
$setting->setDataForm('userpanel_register_enabled', false);
$setting->setDataForm('userpanel_register_type', 1);
$setting->setDataForm('userpanel_register_status', 1);
}
private function getUserTypesForSelect(): array {
$options = array();
foreach (UsertypeModel::get() as $type) {
$options[] = array(
"title" => $type->title,
"value" => $type->id,
);
}
return $options;
}
private function getUserStatusForSelect(): array {
return array(
array(
"title" => t("active"),
"value" => UserModel::ACTIVE,
),
array(
"title" => t("deactive"),
"value" => UserModel::SUSPEND,
),
array(
"title" => t("suspend"),
"value" => UserModel::DEACTIVE,
),
);
}
}
According to the configuration set in the package.json file, the init method of the listener class is called first. The input of the init method is an object of the triggering event class.
To set the field properties, we create an object of the Setting helper class.
In the addRegisterItems method, the field properties are set by calling the addField method of the Setting class.
By calling the setDataForm method, we set default values for the fields.
namespace themes\packagename\views;
use packages\base\views\Form;
use themes\clipone\{ViewTrait, views\FormTrait}; // Clipone is Userpanel package theme name
class Settings extends Form {
use ViewTrait, FormTrait;
private $settings = array();
public function __beforeLoad(){
$this->setTitle(t('userpanel.general-settings'));
$this->addBodyClass('userpanel-settings');
$this->addBodyClass('userpanel-general-settings');
$this->initFormData();
}
public function setSettings(array $settings) {
$this->settings = $settings;
}
protected function getSettings(): array {
return $this->settings;
}
private function initFormData() {
foreach ($this->getSettings() as $setting) {
foreach ($setting->getFields() as $input) {
$value = $setting->getDataForm($input['name']);
if ($value !== null) {
$this->setDataForm($value, $input['name']);
}
}
}
}
}
In the controller, after the event runs, the settings are passed to the theme by calling the setSettings method.
In the initFormData method, we get the settings with the getSettings method, access the specified default value by calling the getDataForm method, and set those default values for the fields by calling the setDataForm method.
<?php
use packages\base;
?>
<form id="general-settings-form" class="form-horizontal" action="<?php echo base\url('userpanel/settings'); ?>" method="POST">
<?php
$this->setHorizontalForm("md-4 sm-5", "md-8 sm-7");
foreach ($this->getSettings() as $tuning) {
?>
<div class="settings-row">
<?php foreach ($tuning->getFields() as $field) { ?>
<div class="settings-row-item">
<?php $this->createField($field); ?>
</div>
<?php } ?>
</div>
<?php } ?>
<div class="row">
<div class="col-sm-4 col-sm-offset-8">
<button class="btn btn-success btn-block" type="submit">
<i class="fa fa-check-square-o"></i>
<?php echo t("user.profile.save"); ?>
</button>
</div>
</div>
</form>
In the html file, we access each field's properties by calling the getFields method, and we create the fields by calling the createField method.
For more information about creating forms, refer to the Forms page.
The result of the example above is the form shown below.
