Page 1 of 3

Firing and handling events

Posted: Wed Aug 11, 2010 6:47 am
by VladSun
I'm just curious how you guys handle this? What are your implementations and design?

I have a simple "works-for-me" event driven framework. Some examples:

Code: Select all

interface IObservable
{
	public function hasEvent($event);
	public function hasEventAlias($event);
	public function getEventAlias($event);
	public function getEvents();
	public function fireEvent($event, &$data = null);
	public function on($class, $event, $method);
}

class BadEventFireException		extends Exception {}
class BadEventCallbackException extends Exception {}
class BadEventAliasException	extends Exception {}

Code: Select all

class Collection_Model extends Module_Model  // Module_Model implements IObservable
{
	protected $events	= array
	(
		'initialize',

		'beforeGetDql',
		'getResult',
		'getRecord',
		'getResponseRecord',

		'beforeRawChange',
		'beforeRecordChange',
		'afterRecordChange',
		'getAfterChange',

		'beforeRawAdd'		=> array('beforeRawChange'),
		'beforeRecordAdd'	=> array('beforeRecordChange'),
		'afterRecordAdd'	=> array('afterRecordChange'),
		'getAfterAdd'		=> array('getAfterChange'),

		'beforeRawSave'		=> array('beforeRawChange'),
		'beforeRecordSave'	=> array('beforeRecordChange'),
		'afterRecordSave'	=> array('afterRecordChange'),
		'getAfterSave'		=> array('getAfterChange'),

		'beforeRawUpdate'	=> array('beforeRawChange'),
		'beforeRecordUpdate'=> array('beforeRecordChange'),
		'afterRecordUpdate'	=> array('afterRecordChange'),
		'getAfterUpdate'	=> array('getAfterChange'),

		'beforeRawRemove',
		'beforeRecordRemove',
		'afterRecordRemove',
		'getAfterRemove',
	);

           ....

	/**
	 * Remove an object from the collection
	 *
	 * @access public
	 * @return Array
	 */
	public function remove($data)
	{
		try
		{
			Doctrine_Manager::connection()->beginTransaction();
			if ($this->fireEvent('beforeRawRemove', $data) !== false)
			{
				$this->record = $this->createFromObject($data);
				if ($this->fireEvent('beforeRecordRemove', $this->record, $data) !== false)
				{
					$this->record->delete();
					$this->fireEvent('afterRecordRemove', $this->record, $data);
					
					if ($this->fireEvent('getResponseRecord', $this->record) === false)
					{
						return array('response' => null);
					}

					$data = $this->prepareEventData($this->record->toArray(true));

					if ($this->fireEvent('getAfterRemove', $data) !== false)
					{
						Doctrine_Manager::connection()->commit();
						return array
						(
							'response' => $data
						);
					}
					else
					{
						Doctrine_Manager::connection()->rollback();
						return array();
					}
				}
				else
				{
					Doctrine_Manager::connection()->rollback();
					return array();
				}
			}
			else
			{
				Doctrine_Manager::connection()->rollback();
				return array();
			}
		}
		catch (Exception $E)
		{
			Doctrine_Manager::connection()->rollback();
			Error::register($E->getMessage());
		}
	}
           ...
}

Code: Select all

class User_Collection_Model extends Collection_Model
{
	protected function hook()
	{
		parent::hook();

		$this->on('User_Collection_Model', 'beforeRecordRemove', 'onBeforeUserRemove');
	}

	public function onBeforeUserRemove($sender, &$record)
	{
		if ($record->id === $this->currentUser->getId())
		{
			Error::register('Can not remove current user.');
			return false;
		}
	}
}
In this case we have a self-to-self firing/handling event (which could be done with inheritance, but that's not the point).
You can see events are not fired per class instance, but per classes (which is fine to me for now). I think it might be problem with my implementation, but I'm very happy with it in my current design - a pluggable and modular system. This way my new plugins/modules (which are Models indeed, so they are single-instance ones) doesn't need an concrete object instance in order to observe it. It even doesn't have to be there (i.e. observing non installed modules)

So, what do you think?

PS: I could do something like:

Code: Select all

$this->for->User_Collection_Model->on->beforeRecordRemove->do->onBeforeUserRemove;
but it's not so important.

Re: Firing and handling events

Posted: Mon Oct 04, 2010 4:44 pm
by VladSun
Bump.
Anyone with an opinion? :(

Re: Firing and handling events

Posted: Mon Oct 04, 2010 6:22 pm
by Christopher
Looks ok to me. It seems like it does what it is supposed to do in a slightly convoluted but follow-able way.

Your "events" seem more like validators. Do you have a lot of these kinds of classes?


PS - in PHP5.3+ you could pass in functions a-la-javascript rather than needing this elaborate system.

Re: Firing and handling events

Posted: Tue Oct 05, 2010 3:38 am
by VladSun
Thanks for looking.
Christopher wrote:Your "events" seem more like validators. Do you have a lot of these kinds of classes?
Indeed, the example I gave looks like a validator. Another example:

Code: Select all

class PppoeUser_Collection_Model extends Collection_Model
{
	protected function hook()
	{
		parent::hook();

		$this->on('User_Collection_Model', 'afterRecordRemove', 'onAfterUserRemove');
	}

	public function onAfterUserRemove($sender, &$record)
	{
		CommandProxy::execute($this->commandAPI->get('pppoe', 'remove', $record->id));
	}
}
Christopher wrote:in PHP5.3+ you could pass in functions a-la-javascript rather than needing this elaborate system.
I need (sometimes) the $this scope/context. But it looks reasonable to put both in use :)

Re: Firing and handling events

Posted: Tue Oct 05, 2010 12:13 pm
by John Cartwright
Are you considering making your framework opensource? I'd be very interesting in taking a peek.

Re: Firing and handling events

Posted: Tue Oct 05, 2010 12:42 pm
by VladSun
John Cartwright wrote:Are you considering making your framework opensource? I'd be very interesting in taking a peek.
Yes, I am. I'll put it Coding Critique as soon as I finished my project that is using it. I must say it's a version of CI though it doesn't mess with the CI, but extends it. Also, it has a very ExtJS friendly set of features because it's intensively used with ExtJS client side.

I'm working on decoupling from CI, ExtJS and Doctrine (these are what I usually use). There are too many things to think of yet but it's progressing :) ...

Re: Firing and handling events

Posted: Tue Oct 05, 2010 1:14 pm
by John Cartwright
Sweet. I'll be sure to keep an eye out for any announcements.

//derailment ended

Re: Firing and handling events

Posted: Tue Oct 05, 2010 2:42 pm
by sike
Hey,

i like your idea of using a events system to enable a basic plugin system. what bothers me is that it seems like your system can't support multiple listeners for a event? most event systems i know of use a Event object for that purpose that is passed around to carry the current state to all listeners (e.g http://www.adobe.com/livedocs/flash/9.0 ... Event.html).

cheers
Chris

Re: Firing and handling events

Posted: Tue Oct 05, 2010 3:00 pm
by VladSun
No. You may attach infinite number of listeners - the system handles this. I have an EventRegister object that binds events to their listeners (one to many). I have even an "event-alias" mechanism that would allow us to trigger events that are alias for the current one (e.g. recordSetChanged will be triggered whenerver a recordSave, recordDelete, recordUpdate or recordInsert event is fired).

If any of the event listeners return false then fireEvent() returns false.

The main problem I see is that events are fired per object class (declaration), not per object instance though it has some advantages as I've noticed above (and that's "works for me"). I'm in the middle of rewriting my Event system so it can handle events fired per object instance together with events fired per object class.

Re: Firing and handling events

Posted: Tue Oct 05, 2010 3:44 pm
by VladSun
I must admit that there a lot of features (considered as best practice) to be implemented in my Event system - such as eventCancelable (identical the one in the link given by sike). It's still beyond of my needs though.

Re: Firing and handling events

Posted: Tue Oct 05, 2010 4:36 pm
by Christopher
Yes, there are many features that could be added -- it might just be bloat given how this kind of system would be used.

I am also interested in this. I was thinking that perhaps the system could be composable so it could be used where extension is not convenient:

Code: Select all

class Collection_Model 
{
        public __construct()
       {
                $this->event = new Vladsun_Event_Handler( array(
                'initialize',

                'beforeGetDql',
                'getResult',
                'getRecord',
                'getResponseRecord',

                'beforeRawChange',
                'beforeRecordChange',
                'afterRecordChange',
                'getAfterChange',

                'beforeRawAdd'          => array('beforeRawChange'),
                'beforeRecordAdd'       => array('beforeRecordChange'),
                'afterRecordAdd'        => array('afterRecordChange'),
                'getAfterAdd'           => array('getAfterChange'),

                'beforeRawSave'         => array('beforeRawChange'),
                'beforeRecordSave'      => array('beforeRecordChange'),
                'afterRecordSave'       => array('afterRecordChange'),
                'getAfterSave'          => array('getAfterChange'),

                'beforeRawUpdate'       => array('beforeRawChange'),
                'beforeRecordUpdate'=> array('beforeRecordChange'),
                'afterRecordUpdate'     => array('afterRecordChange'),
                'getAfterUpdate'        => array('getAfterChange'),

                'beforeRawRemove',
                'beforeRecordRemove',
                'afterRecordRemove',
                'getAfterRemove',
                )
                );
        }

        public function remove($data)
        {
                try
                {
                        Doctrine_Manager::connection()->beginTransaction();
                        if ($this->event->fire('beforeRawRemove', $data) !== false)
                        {

Re: Firing and handling events

Posted: Tue Oct 05, 2010 5:04 pm
by VladSun
Christopher wrote:Yes, there are many features that could be added -- it might just be bloat given how this kind of system would be used.
Indeed. I'll implement them if my project (which is a relatively big one) needs them.
Christopher wrote:I was thinking that perhaps the system could be composable ...
Heh, I was just trying to compile/enable the sandbox runkit PHP extension - http://php.net/manual/en/book.runkit.php , in order to implement the aggregate() function.
Stilll, no success :( (php 5.3.2). It's "association vs. aggregation":
An Association is a composition of independently constructed and externally visible parts. When we associate classes or objects, each one keeps a reference to the ones it is associated with. When we associate classes statically, one class will contain a reference to an instance of the other class.
Aggregation, on the other hand, implies encapsulation (hidding) of the parts of the composition. We can aggregate classes by using a (static) inner class (PHP does not yet support inner classes), in this case the aggregated class definition is not accessible, except through the class that contains it. The aggregation of instances (object aggregation) involves the dynamic creation of subobjects inside an object, in the process, expanding the properties and methods of that object.

Object aggregation is a natural way of representing a whole-part relationship, (for example, molecules are aggregates of atoms), or can be used to obtain an effect equivalent to multiple inheritance, without having to permanently bind a subclass to two or more parent classes and their interfaces. In fact object aggregation can be more flexible, in which we can select what methods or properties to "inherit" in the aggregated object.
http://www.php.net/manual/en/objaggrega ... iation.php
http://www.php.net/manual/en/objaggrega ... mples2.php

I'm going to open a new thread about it (when I %$#@%& manage to compile runkit ...). I think it will provide us OOP features that are still not in the core (like using the $this scope in callback functions).

Re: Firing and handling events

Posted: Tue Oct 05, 2010 5:47 pm
by Christopher
Those examples don't seem very good (i.e., PHP4 references). I don't see why it matters anyway.

Why are you using runkit?

Re: Firing and handling events

Posted: Tue Oct 05, 2010 7:29 pm
by Weirdan
Vlad, there's a fork of runkit (that is actually being developed) available on github, with support for php 5.3. pecl install http://github.com/downloads/zenovich/ru ... -1.0.2.tgz should do the trick.

Christopher:
VladSun wrote: [...] in order to implement the aggregate() function.

Re: Firing and handling events

Posted: Tue Oct 05, 2010 8:58 pm
by josh
You can pass a huge number and crash it ;-) Although in fairness I passed it for the font size.

http://chart.apis.google.com/chart?cht= ... 0000000000

I'd say they didn't validate their inputs, because you should get an error about using the API wrong or something. Not "please try again" which implies everything was fine & dandy ...