*** THIS CHAPTER AND THE CODE IT REFERS TO IS UNDER CONSTRUCTION ***
*** THIS CHAPTER AND THE CODE IT REFERS TO IS UNDER CONSTRUCTION ***
Doctrine provides flexible event listener architecture that not only allows listening for different events but also for altering the execution of the listened methods.
Doctrine provides flexible event listener architecture that not only allows listening for different events but also for altering the execution of the listened methods.
There are several different listeners and hooks for various Doctrine components. Listeners are separate classes where as hooks are empty template methods which are defined in the base class.
There are several different listeners and hooks for various Doctrine components. Listeners are separate classes where as hooks are empty template methods which are defined in the base class.
Connection listeners are used for listening the methods of Doctrine_Connection and its modules (such as Doctrine_Transaction). All listener methods take one argument Doctrine_Event which holds information about the listened event.
Connection listeners are used for listening the methods of Doctrine_Connection and its modules (such as Doctrine_Transaction). All listener methods take one argument Doctrine_Event which holds information about the listened event.
+++ Creating a new listener
+++ Creating a new listener
There are three different ways of defining a listener. First you can create a listener by making a class that inherits Doctrine_EventListener:
There are three different ways of defining a listener. First you can create a listener by making a class that inherits Doctrine_EventListener:
<code type="php">
<code type="php">
class MyListener extends Doctrine_EventListener
class MyListener extends Doctrine_EventListener
{
{
public function preExec(Doctrine_Event $event)
public function preExec(Doctrine_Event $event)
{
{
}
}
}
}
</code>
</code>
Note that by declaring a class that extends Doctrine_EventListener you don't have to define all the methods within the Doctrine_EventListener_Interface. This is due to a fact that Doctrine_EventListener already has empty skeletons for all these methods.
Note that by declaring a class that extends Doctrine_EventListener you don't have to define all the methods within the Doctrine_EventListener_Interface. This is due to a fact that Doctrine_EventListener already has empty skeletons for all these methods.
Sometimes it may not be possible to define a listener that extends Doctrine_EventListener (you might have a listener that inherits some other base class). In this case you can make it implement Doctrine_EventListener_Interface.
Sometimes it may not be possible to define a listener that extends Doctrine_EventListener (you might have a listener that inherits some other base class). In this case you can make it implement Doctrine_EventListener_Interface.
<code type="php">
<code type="php">
class MyListener implements Doctrine_EventListener_Interface
class MyListener implements Doctrine_EventListener_Interface
{
{
// notice: all listener methods must be defined here
// notice: all listener methods must be defined here
// (otherwise PHP throws fatal error)
// (otherwise PHP throws fatal error)
public function preExec(Doctrine_Event $event)
public function preExec(Doctrine_Event $event)
{ }
{ }
public function postExec(Doctrine_Event $event)
public function postExec(Doctrine_Event $event)
{ }
{ }
// ...
// ...
}
}
</code>
</code>
The third way of creating a listener is a very elegant one. You can make a class that implements Doctrine_Overloadable. This interface has only one method: __call(), which can be used for catching *all* the events.
The third way of creating a listener is a very elegant one. You can make a class that implements Doctrine_Overloadable. This interface has only one method: __call(), which can be used for catching *all* the events.
<code type="php">
<code type="php">
class MyDebugger implements Doctrine_Overloadable
class MyDebugger implements Doctrine_Overloadable
{
{
public function __call($methodName, $args)
public function __call($methodName, $args)
{
{
print $methodName . ' called !';
print $methodName . ' called !';
}
}
}
}
</code>
</code>
+++ Attaching listeners
+++ Attaching listeners
You can attach the listeners to a connection with setListener().
You can attach the listeners to a connection with setListener().
<code type="php">
<code type="php">
$conn->setListener(new MyDebugger());
$conn->setListener(new MyDebugger());
</code>
</code>
If you need to use multiple listeners you can use addListener().
If you need to use multiple listeners you can use addListener().
|| preFetch() || Doctrine_Connection::fetch() || query, data ||
|| preFetch() || Doctrine_Connection::fetch() || query, data ||
|| postFetch() || Doctrine_Connection::fetch() || query, data ||
|| postFetch() || Doctrine_Connection::fetch() || query, data ||
|| preFetchAll() || Doctrine_Connection::fetchAll() || query, data ||
|| preFetchAll() || Doctrine_Connection::fetchAll() || query, data ||
|| postFetchAll() || Doctrine_Connection::fetchAll() || query, data ||
|| postFetchAll() || Doctrine_Connection::fetchAll() || query, data ||
* preExecute() and postExecute() only get invoked when Doctrine_Connection::execute() is called without prepared statement parameters. Otherwise Doctrine_Connection::execute() invokes prePrepare, postPrepare, preStmtExecute and postStmtExecute.
* preExecute() and postExecute() only get invoked when Doctrine_Connection::execute() is called without prepared statement parameters. Otherwise Doctrine_Connection::execute() invokes prePrepare, postPrepare, preStmtExecute and postStmtExecute.
++ Query listeners
++ Query listeners
+++ preHydrate, postHydrate
+++ preHydrate, postHydrate
+++ preBuildQuery, postBuildQuery
+++ preBuildQuery, postBuildQuery
++ Record listeners
++ Record listeners
Doctrine_Record provides listeners very similar to Doctrine_Connection. You can set the listeners at global, connection and record(=table) level.
Doctrine_Record provides listeners very similar to Doctrine_Connection. You can set the listeners at global, connection and record(=table) level.
Here is a list of all availible listener methods:
Here is a list of all available listener methods:
||~ Methods ||~ Listens ||
||~ Methods ||~ Listens ||
|| preSave() || Doctrine_Record::save() ||
|| preSave() || Doctrine_Record::save() ||
|| postSave() || Doctrine_Record::save() ||
|| postSave() || Doctrine_Record::save() ||
|| preUpdate() || Doctrine_Record::save() when the record state is DIRTY ||
|| preUpdate() || Doctrine_Record::save() when the record state is DIRTY ||
|| postUpdate() || Doctrine_Record::save() when the record state is DIRTY ||
|| postUpdate() || Doctrine_Record::save() when the record state is DIRTY ||
|| preInsert() || Doctrine_Record::save() when the record state is TDIRTY ||
|| preInsert() || Doctrine_Record::save() when the record state is TDIRTY ||
|| postInsert() || Doctrine_Record::save() when the record state is TDIRTY ||
|| postInsert() || Doctrine_Record::save() when the record state is TDIRTY ||
Just like with connection listeners there are three ways of defining a record listener: by extending Doctrine_Record_Listener, by implement Doctrine_Record_Listener_Interface or by implementing Doctrine_Overloadable. In the following we'll create a global level listener by implementing Doctrine_Overloadable:
Just like with connection listeners there are three ways of defining a record listener: by extending Doctrine_Record_Listener, by implement Doctrine_Record_Listener_Interface or by implementing Doctrine_Overloadable. In the following we'll create a global level listener by implementing Doctrine_Overloadable:
<code type="php">
<code type="php">
class Logger extends Doctrine_Overloadable
class Logger extends Doctrine_Overloadable
{
{
public function __call($m, $a)
public function __call($m, $a)
{
{
print 'catched event ' . $m;
print 'catched event ' . $m;
// do some logging here...
// do some logging here...
}
}
}
}
</code>
</code>
Attaching the listener to manager is easy:
Attaching the listener to manager is easy:
<code type="php">
<code type="php">
$manager->addRecordListener(new Logger());
$manager->addRecordListener(new Logger());
</code>
</code>
Note that by adding a manager level listener it affects on all connections and all tables / records within these connections. In the following we create a connection level listener:
Note that by adding a manager level listener it affects on all connections and all tables / records within these connections. In the following we create a connection level listener:
<code type="php">
<code type="php">
class Debugger extends Doctrine_Record_Listener
class Debugger extends Doctrine_Record_Listener
{
{
public function preInsert(Doctrine_Event $event)
public function preInsert(Doctrine_Event $event)
{
{
print 'inserting a record ...';
print 'inserting a record ...';
}
}
public function preUpdate(Doctrine_Event $event)
public function preUpdate(Doctrine_Event $event)
{
{
print 'updating a record...';
print 'updating a record...';
}
}
}
}
</code>
</code>
Attaching the listener to a connection is as easy as:
Attaching the listener to a connection is as easy as:
<code type="php">
<code type="php">
$conn->addRecordListener(new Debugger());
$conn->addRecordListener(new Debugger());
</code>
</code>
Many times you want the listeners to be table specific so that they only apply on the actions on that given table. Here is an example:
Many times you want the listeners to be table specific so that they only apply on the actions on that given table. Here is an example:
<code type="php">
<code type="php">
class Debugger extends Doctrine_Record_Listener
class Debugger extends Doctrine_Record_Listener
{
{
public function postDelete(Doctrine_Event $event)
public function postDelete(Doctrine_Event $event)
{
{
print 'deleted ' . $event->getInvoker()->id;
print 'deleted ' . $event->getInvoker()->id;
}
}
}
}
</code>
</code>
Attaching this listener to given table can be done as follows:
Attaching this listener to given table can be done as follows:
<code type="php">
<code type="php">
class MyRecord extends Doctrine_Record
class MyRecord extends Doctrine_Record
{
{
public function setTableDefinition()
public function setTableDefinition()
{
{
// some definitions
// some definitions
}
}
public function setUp()
public function setUp()
{
{
$this->addListener(new Debugger());
$this->addListener(new Debugger());
}
}
}
}
</code>
</code>
++ Record hooks
++ Record hooks
||~ Methods ||~ Listens ||
||~ Methods ||~ Listens ||
|| preSave($event) || Doctrine_Record::save() ||
|| preSave($event) || Doctrine_Record::save() ||
|| postSave($event) || Doctrine_Record::save() ||
|| postSave($event) || Doctrine_Record::save() ||
|| preUpdate($event) || Doctrine_Record::save() when the record state is DIRTY ||
|| preUpdate($event) || Doctrine_Record::save() when the record state is DIRTY ||
|| postUpdate($event) || Doctrine_Record::save() when the record state is DIRTY ||
|| postUpdate($event) || Doctrine_Record::save() when the record state is DIRTY ||
|| preInsert($event) || Doctrine_Record::save() when the record state is TDIRTY ||
|| preInsert($event) || Doctrine_Record::save() when the record state is TDIRTY ||
|| postInsert($event) || Doctrine_Record::save() when the record state is TDIRTY ||
|| postInsert($event) || Doctrine_Record::save() when the record state is TDIRTY ||
All different event listeners in Doctrine allow chaining. This means that more than one listener can be attached for listening the same methods. The following example attaches two listeners for given connection:
All different event listeners in Doctrine allow chaining. This means that more than one listener can be attached for listening the same methods. The following example attaches two listeners for given connection:
<code type="php">
<code type="php">
// here Debugger and Logger both inherit Doctrine_EventListener
// here Debugger and Logger both inherit Doctrine_EventListener
$conn->addListener(new Debugger());
$conn->addListener(new Debugger());
$conn->addListener(new Logger());
$conn->addListener(new Logger());
</code>
</code>
++ The Event object
++ The Event object
+++ Getting the invoker
+++ Getting the invoker
You can get the object that invoked the event by calling getInvoker():
You can get the object that invoked the event by calling getInvoker():
<code type="php">
<code type="php">
class MyListener extends Doctrine_EventListener
class MyListener extends Doctrine_EventListener
{
{
public function preExec(Doctrine_Event $event)
public function preExec(Doctrine_Event $event)
{
{
$event->getInvoker(); // Doctrine_Connection
$event->getInvoker(); // Doctrine_Connection
}
}
}
}
</code>
</code>
+++ Event codes
+++ Event codes
Doctrine_Event uses constants as event codes. Above is the list of all availible event constants:
Doctrine_Event uses constants as event codes. Above is the list of all available event constants:
The method getInvoker() returns the object that invoked the given event. For example for event Doctrine_Event::CONN_QUERY the invoker is a Doctrine_Connection object. Example:
The method getInvoker() returns the object that invoked the given event. For example for event Doctrine_Event::CONN_QUERY the invoker is a Doctrine_Connection object. Example:
<code type="php">
<code type="php">
class MyRecord extends Doctrine_Record
class MyRecord extends Doctrine_Record
{
{
public function preUpdate(Doctrine_Event $event)
public function preUpdate(Doctrine_Event $event)
{
{
$event->getInvoker(); // Object(MyRecord)
$event->getInvoker(); // Object(MyRecord)
}
}
}
}
</code>
</code>
+++ skipOperation()
+++ skipOperation()
Doctrine_Event provides many methods for altering the execution of the listened method as well as for altering the behaviour of the listener chain.
Doctrine_Event provides many methods for altering the execution of the listened method as well as for altering the behaviour of the listener chain.
For some reason you may want to skip the execution of the listened method. It can be done as follows (note that preExec could be any listener method):
For some reason you may want to skip the execution of the listened method. It can be done as follows (note that preExec could be any listener method):
<code type="php">
<code type="php">
class MyListener extends Doctrine_EventListener
class MyListener extends Doctrine_EventListener
{
{
public function preExec(Doctrine_Event $event)
public function preExec(Doctrine_Event $event)
{
{
// some business logic, then:
// some business logic, then:
$event->skipOperation();
$event->skipOperation();
}
}
}
}
</code>
</code>
+++ skipNextListener()
+++ skipNextListener()
When using a chain of listeners you might want to skip the execution of the next listener. It can be achieved as follows:
When using a chain of listeners you might want to skip the execution of the next listener. It can be achieved as follows: