Firing and handling events

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Firing and handling events

Post 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.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Firing and handling events

Post by VladSun »

Bump.
Anyone with an opinion? :(
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Firing and handling events

Post 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.
(#10850)
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Firing and handling events

Post 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 :)
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Re: Firing and handling events

Post by John Cartwright »

Are you considering making your framework opensource? I'd be very interesting in taking a peek.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Firing and handling events

Post 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 :) ...
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Re: Firing and handling events

Post by John Cartwright »

Sweet. I'll be sure to keep an eye out for any announcements.

//derailment ended
sike
Forum Commoner
Posts: 84
Joined: Wed Aug 02, 2006 8:33 am

Re: Firing and handling events

Post 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
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Firing and handling events

Post 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.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Firing and handling events

Post 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.
Last edited by VladSun on Tue Oct 05, 2010 4:39 pm, edited 1 time in total.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Firing and handling events

Post 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)
                        {
(#10850)
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Firing and handling events

Post 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).
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Firing and handling events

Post 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?
(#10850)
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Firing and handling events

Post 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.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Firing and handling events

Post 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 ...
Post Reply