About Events
One of the most notable things about Qbix is that it makes heavy use of a common event system. In many places, rather than calling a function, Qbix or your app would fire an event. This has many benefits, including:
- The ability to override an event's handler by just dropping a file in the right place
- Potential for attaching various hooks before or after an event
- Loading code dynamically on demand, only when it is needed
In Q, lots of things are implemented using events, including the entire controller logic of your app (this is the C in MVC).
In PHP, events are fired by calling:
Q::event("Foo/bar", array('param1' => $value1))
By the way, you can often pass parameters more elegantly, using PHP's compact function:
Q::event("Foo/bar", compact('param1', 'param2'))
Handlers
When an event named Foo/bar is fired, Qbix attempts to find the corresponding event handler, by looking for a file called handlers/Foo/bar.php and expecting it to look something like this:
<?php function Foo_bar($params) { // $params is the array of parameters // passed to the event }
The search proceeds along all the paths in get_include_path(). Your app's folder (APP_DIR) is checked first, followed by any plugins that were installed (more on that later in the guide), finally followed by the Qbix platform's folder (Q_DIR). This means that plugins can override Q's default handlers (if any), and your app can override anything.
Normally, if the file of the handler is not found, or the function of the handler is not defined, Qbix throws an exception. This happens unless a "pure event" is fired, which is explained next.
Hooks
The event system in Qbix allows hooks to be attached before or after any event. This is done by explicitly merging this information into the config fields Q/handlersBeforeEvent and Q/handlersAfterEvent. For example, the Users plugin does this in its config/plugin.json file, something like the following:
{ "Q": { "handlersBeforeEvent": { "Q/init": ["Users/before/Q_init"], "Q/objects": ["Users/before/Q_objects"], "Q/redirect": ["Users/before/Q_redirect"] }, "handlersAfterEvent": { "Q/reroute": ["Users/after/Q_reroute"] } } }
After the above configuration file is merged into the config, the "Users/before/Q/init" handler, for example, will now execute every time before the "Q/init" handler (which is intended to handle the "Q/init" event). Remember that a handler is just a function defined inside a file.
Pure events
Often, you will want to fire an event for which there is no default handler. This is done just so people can hook into your code, without having to hack it. By firing events when appropriate (whether pure or not), your code becomes more re-usable.
To execute any hooks registered to run before an event, you would write:
Q::event("$app/myCoolEvent", $params, 'before');
To execute any hooks registered to run after an event, it would be:
Q::event("$app/myCoolEvent", $params, 'after');
And to execute both types of hooks, a shortcut is just to do:
Q::event("$app/myCoolEvent", $params, true);
Note that pure events can — but do not have to — have corresponding handlers.
Here is an example of how pure events can be used to outsource caching:
class Users { static function fql($query) { // run any hooks before sending our expensive request $ret = Q::event('Users/fql', compact('query'), 'before'); if (isset($ret)) { // return what is likely a cached result return $ret; } // otherwise, send the expensive request $app = Q_Config::expect('Q', 'app'); $result = Users::$facebooks[$app]->api_client->fql_query($query); // run any hooks after the result has been obtained // giving them a chance e.g. to cache the result for next time Q::event('Users/fql', compact('query', 'result'), 'after'); return $result; } ... }
In fact, the Users plugin has a method very similar to this.
Notice that Qbix determines which hooks to run simply by checking the config at run-time. There is no "magic" going on — hooks are explicitly specified in a config file (e.g. by a plugin or by your app). As a result, firing pure events hardly incurs any overhead at all when there are no hooks attached — especially when all the config files are aggregated in production. So feel free to fire as many pure events as you need in your own code.
Return values
When an event is fired, the return value of the handler is ultimately what Q::event(...) returns. However, hooks can be used to modify the value an event would return. To do this, the hook accepts a second parameter:
<?php function MyApp_before_Q_init(&$params, &$return) { // notice the ampersands above // $params are the parameters passed during Q::event(...) $params['a'] = 'b'; // modified params // Meanwhile, &$return is what will be returned to the caller. // Modifying $return will modify this return value. // In "before" hooks, it starts out as null. // In "after" hooks, it starts out as the return value of the // event's handler, if any. $return = 'foo'; // modified default return value of event }
A hook can return false to skip all subsequent hooks. If this is done in a "before" hook, then the event's handler (if any) is skipped, too.
Node.js
Here Qbix implements a similar system. During Qbix bootstrap in Node.js, all the handlers are loaded into Q.handlers, in the cascade described in the Files page, Events can be handled by calling:
Q.event("First/foo/bar", context, parameters)
This would call the function(s) at Q.handlers.First.foo.bar. Of course, you can also use the usual EventEmitter interface whenever you want.