Event handling

Contents...

Apps powered by Webasyst framework can support events. An event is a special place in an app’s source code which allows to “plug in” custom code to it without modifying an original app. Custom code can be executed either in a plugin developed for that app or in another app. Whether your app must support events is up to you as its developer.

How it works

Simple example:

  1. A user opens a website page.
  2. To generate the page HTML code, an app’s PHP code is executed.
  3. An app’s PHP code contains an event. It was added by the app developer.
  4. An event is processed by a plugin. This is because the plugin developer has decided so. As soon as the code execution reaches the event declaration in an app’s code, a plugin attached to it will be executed, too.

The event processing involves 2 types of software products—those containing events and those capable of processing events provided by other products.

Declaring events in an app

To declare an event in an app’s PHP code, add the call of system method wa('app_id')->event('event_id') to the desired place.

Example
$result = wa('myapp')->event('backend', $params);

In this example:

Subscribing to events in plugins

To enable the execution of a plugin’s code upon occurrence of an event, the plugin developer must subscribe their plugin to that event. To subscribe means to declare in a special way that certain piece of plugin’s code will be executed when a certain event occurs. To do so, add an item with 'handlers' key to configuration file plugin.php.

There are several ways to subscribe a plugin to app events:

Method 1. One plugin method per event

Example
'handlers' => array(
    'backend_menu' => 'backendMenu',
),

This record means that the main plugin class contains a public non-static mathod named backendMenu, which will be executed upon occurrence of backend_menu events in the same app for which the plugin was developed.

Method 2. Several plugin methods per event

Example
'handlers' => array(
    'backend_menu' => array('backendMenu', 'commonHandler', '...'),
),

This record means that the main plugin class contains public non-static methods named according to method names listed as an array, which will be executed upon occurrence of backend_menu event in the same app for which the plugin was developed.

Once an event occurs, the specified methods will be executed in the same order as they are listed in the configuration file, until any of them returns any result different from null. Should any of the specified methods return anything rather than null, all remaining methods will not be executed.

Method 3. One plugin method per several events: multiple records

Example
'handlers' => array(
    'backend_layout' => 'layoutEvent',
    'frontend_layout' => 'layoutEvent',
),

This record means that both backend_layout and frontend_layout events are handled by the same method in main plugin class, the method in this example is named layoutEvent.

Method 4. One plugin method per several events: mask

Example
'handlers' => array(
    'order_action.*' => 'orderAction',
),

If an app has several events wilth the similar names containing the period character (.), then a developer may subscribe their plugin to all such events at once by replacing the different part in event names with an asterisk (*). In this example an app has events named order_action.create, order_action.pay, order_action.edit, and a plugin can be subscribed to handle all of them in one method of its main class.

You can use the asterisk only for events with the period character in their names. To use a mask for other types of event names, write a regular expression.

Example 1
'handlers' => array(
    '~(backend_frontend)_layout~' => 'layoutEvent',
),

You can use ~ or / characters as regular expression delimiters.

Example 2
'handlers' => array(
    '~[a-z]+_layout~' => 'layoutEvent',
),

This example is similar to the previous one with the exception that a regular expression is more “greedy” this time, because it matches more event names. Not only backend_layout and frontend_layout will be handled by a plugin in this case, but also dialog_layout and email_layout, for instance, if there are such events in an app.

You need to be careful when subscribing your plugins to multiple events using regular expressions to avoid subscription to unnecessary events. This may otherwise result in increased server load or make an app or plugin behave unexpectedly.

Method 5. Handling other apps’ events

Previous examples show how a plugin can be subscribed to handle events in its own app. There is also an option for a plugin to handle events occurring in other apps. To make it do so, you need to use an extended subscription format, which requires adding of a sub-array with '*' key to 'handlers' array. Specify the following values in the sub-array:

Example 1
'handlers' => array(
    '*' => array(
        'event_app_id' => 'otherapp',
        'event' => 'event_name',
        'class' => 'someappMyPlugin',
        'method' => 'eventHandler',
    ),
),

In this example a different app is specified in 'event_app_id' field. If necessary, you may specify several handling methods as an array or as a regular expression.

Example 2
'handlers' => array(
    '*' => array(
        'event_app_id' => '*',
        'event' => 'signup',
        'class' => 'someappMyPlugin',
        'method' => 'signup',
    ),
),

This is a more common example because no specific app is mentioned here whose events a plugin is being subscribed to. Instead of an app ID there is an asterisk *, it means that a plugin will handle events with the specified name whenever they may occur in any of the installed apps. An example of such an event is signup. This event is declared in framework’s system classes but those classes are used by different apps, therefore, a plugin must register the event occurrence during the execution of all apps’ PHP code.

Example 3
'handlers' => array(
    '*' => array(
        'event_app_id' => '*',
        'event' => '~.+~',
        'class' => 'someappMyPlugin',
        'method' => 'eventHandler',
    ),
),

This is the most common general-purpose example, which is used to subscribe a plugin to all events of all apps. Most probably, you will never need to use it because it will require a lot of server resources. Use this format only in very special cases.

Subscribing apps to other apps’ events

There are two ways of subscribing apps to events:

Method 1. Creation of a PHP class file at a stanard path

In this case you do not need to create a configuration file. It is enough to create a handling PHP class. The class file path format is wa-apps/app_id/lib/handlers/<em>event_app_id</em>.<em>event_name</em>.handler.php. The format of the class name to be declared in that file is app_id<em>Event_app_idEvent_name</em>Handler. The class must extend system class waEventHandler. The class’s main method must be named execute().

Example

ID of app containing an event: shop.
Event declared in shop app: backend_order.
ID of app which will handle backend_order event in shop app: crm.
Path to event handling class file: wa-apps/crm/lib/handlers/shop.backend_order.handler.php.
Name of event handling class: crmShopBackend_orderHandler.
Event handling class code example:

class crmShopBackend_orderHandler extends waEventHandler
{
    public function execute(&$params, $event_name = null)
    {
        //...
    }
}

Method 2. Specifying PHP classes and methods in a configuration file

Create file wa-apps/app_id/lib/handlers/wildcard.php. It must contain an array with the information about methods and their classes which will be handling specified events in another app.

Example of wildcard.php file
return array(
    array(
        'event'        => 'event_name_1',
        'class'        => 'app_idEvent_app_idEvent_name_1Handler',
        'method'       => array('execute', 'extraMethod'),
        'event_app_id' => 'event_app_id_1',
        'file'         => 'path/to/some/class1.php',
    ),
    array(
        'event'        => 'event_name_2',
        'class'        => 'app_idEvent_app_idEvent_name_2Handler',
        'method'       => array('execute', 'extraMethod'),
        'event_app_id' => 'event_app_id_2',
        'file'         => 'path/to/some/class2.php',
    ),
    array(
        ...
    ),
);

In this file:

Try to specify only methods of classes in wildcard.php which have been created only for the purpose of event handling rather than methods of common app classes. This approach will ensure availability of all those classes and their methods as your app evolves.

Event handling method signature

An event method, both in an app and in a plugin, always receives 2 parameters:

  1. Event data. This value is passed to wa()->event('event_name', <strong>$params</strong>) method as the $params value. Different values are passed for different events. Some events are passed no data, therefore, their handlers receive a null value from the first parameter.

    Event data, when passed, are always available by reference. Therefore, you can modify them in your handling method so that an app can use further use them, if necessary.
  2. Event name. You may use it, for example, when a handling method is used for several events, to change the common handling logic according to a specific event. This parameter is optional, you may omit it in the method signature if you are not going to use its value.
Example 1
//second parameter is specified to obtain an vent name
public function eventHandler(&$params, $event_name = null)
{
    //...
}
Example 2
//only one parameter is specified so the event name is not used in the handling method
public function eventHandler(&$params)
{
    //...
}

In apps it is recommended for event handling PHP classed to extend system class waEventHandler and override its public method execute(). This will allow your app to automatically support any further exhancements to the event handling mechanism, which may be added to future framework versions.

Dynamic event handler subscription

When developing or debugging a product, you may find it useful to be able to dynamically link a custom event handler, which will otherwise not be available in a published product. You can do so by calling waEvent::addCustomHandler($handler) method as shown in the example below. The $handler parameter must contain a custom handler’s data.

$handler = array(
    'object' => new someCustomClass(),
    'method' => 'eventHandler', //или array('eventHandler1', 'eventHandler2', ...)
    'event' => 'some_event',
    'event_app_id' => 'some_app',
);
waEvent::addCustomHandler($handler);

If several methods are listed in 'method' field, then all of them will be executed, regardless of the results which may be returned by any of them, even if they should not be equal to null. In this way dynamically linked custom event handlers are different from ordinary event handlers declared in apps and plugins.

Event handlers debugging

When exceptions are thrown during the execution of event handler methods, they are suppressed by the framework and their messages are written to log file wa-log/error.log. Those log records may be useful for your debugging process.

You can log the execution time of each event handler to a file. To do so, get authorized as a backend user and addcookie value event_log_execution = 1. This will make event handling-related information written to log file wa-log/webasyst/waEventExecutionTime.log in the following format:

===Start log block===
Recorded: User name

array (
    event_name =>
        array (
            0 =>
                array (
                    'app_id' => 'crm', //app containing an event handler
                    'plugin_id' => 'yandex', //plugin subscribed to even handling
                    'regex' => '/backend_header/', //event name as a regular expression
                    'file' => 'webasyst.backend_header.handler.php', //file name, if available
                    'class' => 'crmWebasystBackend_headerHandler', //class name
                    'method' =>
                        array (
                            0 => 'execute', //handling class methods
                        ),
                    'execution_time' => 0.0188, //event handler’s execution time in seconds
                ),
        ),
)

===End log block==="

See also waEvent class description.