event-listeners.txt 14.9 KB
Newer Older
phuson's avatar
phuson committed
1 2 3 4
++ Introduction

Doctrine provides flexible event listener architecture that not only allows listening for different events but also for altering the execution of the listened methods.

zYne's avatar
zYne committed
5 6 7
There are several different listeners and hooks for various Doctrine components. Listeners are separate classes whereas hooks are empty template methods within the listened class.

Hooks are simpler than eventlisteners but they lack the separation of different aspects. An example of using Doctrine_Record hooks:
phuson's avatar
phuson committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

<code type="php">
class Blog extends Doctrine_Record 
{
    public function setTableDefinition()
    {
        $this->hasColumn('title', 'string', 200);
        $this->hasColumn('content', 'string');
        $this->hasColumn('created', 'date');
    }
    public function preInsert($event)
    {
        $this->created = date('Y-m-d', time());
    }
}

// initialize connection etc, then:

$blog = new Blog();
$blog->title = 'New title';
$blog->content = 'Some content';
$blog->save();

$blog->created; // 2007-06-20 (format: YYYY-MM-DD)
</code>
zYne's avatar
zYne committed
33 34 35 36 37

Each listener and hook method takes one parameter Doctrine_Event object. Doctrine_Event object holds information about the event in question and can alter the execution of the listened method.

For the purposes of this documentation many method tables are provided with column named 'params' indicating names of the parameters that an event object holds on given event. For example the preCreateSavepoint event has one parameter the name of the created savepoint, which is quite intuitively named as savepoint.

phuson's avatar
phuson committed
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
++ Connection listeners    

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
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">
class MyListener extends Doctrine_EventListener
{
    public function preExec(Doctrine_Event $event)
    {

    }
}
</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.

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">
class MyListener implements Doctrine_EventListener_Interface 
{
    // notice: all listener methods must be defined here 
    // (otherwise PHP throws fatal error)
    
    public function preExec(Doctrine_Event $event)
    { }
    public function postExec(Doctrine_Event $event)
    { }
    
    // ...
}
</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.

<code type="php">
class MyDebugger implements Doctrine_Overloadable
{
    public function __call($methodName, $args) 
    {
        print $methodName . ' called !';
    }
}
</code>

+++ Attaching listeners

You can attach the listeners to a connection with setListener().

<code type="php">
$conn->setListener(new MyDebugger());
</code>

If you need to use multiple listeners you can use addListener().

<code type="php">
$conn->addListener(new MyDebugger());
$conn->addListener(new MyLogger());
</code>

+++ preConnect, postConnect

+++ Transaction listeners
||~ Methods     ||~ Listens ||~ Params ||
zYne's avatar
zYne committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|| preTransactionBegin(Doctrine_Event $event) || Doctrine_Transaction::beginTransaction() || ||
|| postTransactionBegin(Doctrine_Event $event) || Doctrine_Transaction::beginTransaction() || ||
|| preTransactionRollback(Doctrine_Event $event) || Doctrine_Transaction::rollback() || ||
|| postTransactionRollback(Doctrine_Event $event) || Doctrine_Transaction::rollback() || ||
|| preTransactionCommit(Doctrine_Event $event) || Doctrine_Transaction::commit() || ||
|| postTransactionCommit(Doctrine_Event $event) || Doctrine_Transaction::commit() || ||
|| preCreateSavepoint(Doctrine_Event $event) || Doctrine_Transaction::createSavepoint() || savepoint ||
|| postCreateSavepoint(Doctrine_Event $event) || Doctrine_Transaction::createSavepoint() || savepoint ||
|| preRollbackSavepoint(Doctrine_Event $event) || Doctrine_Transaction::rollbackSavepoint() || savepoint ||
|| postRollbackSavepoint(Doctrine_Event $event) || Doctrine_Transaction::rollbackSavepoint() || savepoint ||
|| preReleaseSavepoint(Doctrine_Event $event) || Doctrine_Transaction::releaseSavepoint() || savepoint ||
|| postReleaseSavepoint(Doctrine_Event $event) || Doctrine_Transaction::releaseSavepoint() || savepoint ||

<code type="php">
class MyTransactionListener extends Doctrine_EventListener
{
    public function preTransactionBegin(Doctrine_Event $event)
    {
        print 'beginning transaction... ';
    }

    public function preTransactionRollback(Doctrine_Event $event)
    {
        print 'rolling back transaction... ';
    }
}

</code>
phuson's avatar
phuson committed
133 134 135 136

+++ Query execution listeners

||~ Methods     ||~ Listens ||~ Params ||
zYne's avatar
zYne committed
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|| prePrepare(Doctrine_Event $event) || Doctrine_Connection::prepare() || query ||
|| postPrepare(Doctrine_Event $event) || Doctrine_Connection::prepare() || query ||
|| preExec(Doctrine_Event $event) || Doctrine_Connection::exec() || query ||
|| postExec(Doctrine_Event $event) || Doctrine_Connection::exec() || query, rows ||
|| preStmtExecute(Doctrine_Event $event) || Doctrine_Connection_Statement::execute() || query ||
|| postStmtExecute(Doctrine_Event $event) || Doctrine_Connection_Statement::execute() || query ||
|| preExecute(Doctrine_Event $event) || Doctrine_Connection::execute() * || query ||
|| postExecute(Doctrine_Event $event) || Doctrine_Connection::execute() * || query ||
|| preFetch(Doctrine_Event $event) || Doctrine_Connection::fetch() || query, data ||
|| postFetch(Doctrine_Event $event) || Doctrine_Connection::fetch() || query, data ||
|| preFetchAll(Doctrine_Event $event) || Doctrine_Connection::fetchAll() || query, data ||
|| postFetchAll(Doctrine_Event $event) || Doctrine_Connection::fetchAll() || query, data ||

* preExecute() and postExecute() only get invoked when Doctrine_Connection::execute() is being called without prepared statement parameters. Otherwise Doctrine_Connection::execute() invokes prePrepare, postPrepare, preStmtExecute and postStmtExecute.

phuson's avatar
phuson committed
152 153 154


++ Query listeners
zYne's avatar
zYne committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169

The query listeners can be used for listening the DQL query building and resultset hydration procedures. Couple of methods exist for listening the hydration procedure: preHydrate and postHydrate.

If you set the hydration listener on connection level the code within the preHydrate and postHydrate blocks will be invoked by all components within a multi-component resultset. However if you add a similar listener on table level it only gets invoked when the data of that table is being hydrated.

Consider we have a class called User with the following fields: firstname, lastname and age. In the following example we create a listener that always builds a generated field called fullname based on firstname and lastname fields.

<code type="php">
class HydrationListener extends Doctrine_Record_Listener
{
    public function preHydrate(Doctrine_Event $event)
    {
        $data = $event->data;

        $data['fullname'] = $data['firstname'] . ' ' . $data['lastname'];
pookey's avatar
pookey committed
170
        $event->data = $data;
zYne's avatar
zYne committed
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    }
}
</code>

Now all we need to do is attach this listener to the User record and fetch some users.

<code type="php">
$user = new User();
$user->addListener(new HydrationListener());

$users = Doctrine_Query::create()->from('User');

foreach ($users as $user) {
    print $user->fullname;
}
</code>

phuson's avatar
phuson committed
188 189 190 191 192 193 194
++ Record listeners

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 available listener methods:

||~ Methods     ||~ Listens ||
zYne's avatar
zYne committed
195 196 197 198 199 200 201 202 203 204
|| preSave(Doctrine_Event $event) || Doctrine_Record::save() ||
|| postSave(Doctrine_Event $event) || Doctrine_Record::save() ||
|| preUpdate(Doctrine_Event $event) || Doctrine_Record::save() when the record state is DIRTY ||
|| postUpdate(Doctrine_Event $event) || Doctrine_Record::save() when the record state is DIRTY ||
|| preInsert(Doctrine_Event $event) || Doctrine_Record::save() when the record state is TDIRTY ||
|| postInsert(Doctrine_Event $event) || Doctrine_Record::save() when the record state is TDIRTY ||
|| preDelete(Doctrine_Event $event) || Doctrine_Record::delete() ||
|| postDelete(Doctrine_Event $event) || Doctrine_Record::delete() ||
|| preValidate(Doctrine_Event $event) || Doctrine_Validator::validate() ||
|| postValidate(Doctrine_Event $event) || Doctrine_Validator::validate() ||
phuson's avatar
phuson committed
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314

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">
class Logger extends Doctrine_Overloadable
{
    public function __call($m, $a) 
    {
        print 'catched event ' . $m;
        
        // do some logging here...
    }
}
</code>

Attaching the listener to manager is easy:

<code type="php">
$manager->addRecordListener(new Logger());
</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:

<code type="php">
class Debugger extends Doctrine_Record_Listener
{
    public function preInsert(Doctrine_Event $event)
    {
        print 'inserting a record ...';
    }
    public function preUpdate(Doctrine_Event $event)
    {
        print 'updating a record...';
    }
}
</code>

Attaching the listener to a connection is as easy as:

<code type="php">
$conn->addRecordListener(new Debugger());
</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:

<code type="php">
class Debugger extends Doctrine_Record_Listener
{
    public function postDelete(Doctrine_Event $event)
    {
        print 'deleted ' . $event->getInvoker()->id;
    }
}
</code>

Attaching this listener to given table can be done as follows:

<code type="php">
class MyRecord extends Doctrine_Record
{
    public function setTableDefinition()
    {
        // some definitions
    }
    
    public function setUp()
    {
        $this->addListener(new Debugger());
    }
}
</code>



++ Record hooks
||~ Methods     ||~ Listens ||
|| preSave($event) || Doctrine_Record::save() ||
|| postSave($event) || Doctrine_Record::save() ||
|| preUpdate($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 ||
|| postInsert($event) || Doctrine_Record::save() when the record state is TDIRTY ||
|| preDelete($event) || Doctrine_Record::delete() ||
|| postDelete($event) || Doctrine_Record::delete() ||
|| preValidate($event) || Doctrine_Validator::validate() ||
|| postValidate($event) || Doctrine_Validator::validate() ||

Example 1. Using insert and update hooks
<code type="php">
class Blog extends Doctrine_Record 
{
    public function setTableDefinition()
    {
        $this->hasColumn('title', 'string', 200);
        $this->hasColumn('content', 'string');
        $this->hasColumn('created', 'date');
        $this->hasColumn('updated', 'date');
    }
    public function preInsert($event)
    {
        $this->created = date('Y-m-d', time());
    }
    public function preUpdate($event)
    {
        $this->updated = date('Y-m-d', time());
    }
}
</code>

++ Chaining listeners

zYne's avatar
zYne committed
315
Doctrine allows chaining of different eventlisteners. This means that more than one listener can be attached for listening the same events. The following example attaches two listeners for given connection:
phuson's avatar
phuson committed
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427

<code type="php">
// here Debugger and Logger both inherit Doctrine_EventListener

$conn->addListener(new Debugger());
$conn->addListener(new Logger());
</code>

++ The Event object
+++ Getting the invoker
You can get the object that invoked the event by calling getInvoker():
<code type="php">
class MyListener extends Doctrine_EventListener
{
    public function preExec(Doctrine_Event $event)
    {
        $event->getInvoker(); // Doctrine_Connection
    }
}
</code>
+++ Event codes

Doctrine_Event uses constants as event codes. Above is the list of all available event constants:

* Doctrine_Event::CONN_QUERY 
* Doctrine_Event::CONN_EXEC
* Doctrine_Event::CONN_PREPARE 
* Doctrine_Event::CONN_CONNECT 
* Doctrine_Event::STMT_EXECUTE 
* Doctrine_Event::STMT_FETCH 
* Doctrine_Event::STMT_FETCHALL

<code type="php">
class MyListener extends Doctrine_EventListener
{
    public function preExec(Doctrine_Event $event)
    {
        $event->getCode(); // Doctrine_Event::CONN_EXEC
    }
}
</code>

* Doctrine_Event::TX_BEGIN 
* Doctrine_Event::TX_COMMIT
* Doctrine_Event::TX_ROLLBACK
* Doctrine_Event::SAVEPOINT_CREATE 
* Doctrine_Event::SAVEPOINT_ROLLBACK 
* Doctrine_Event::SAVEPOINT_COMMIT 
* Doctrine_Event::RECORD_DELETE
* Doctrine_Event::RECORD_SAVE
* Doctrine_Event::RECORD_UPDATE
* Doctrine_Event::RECORD_INSERT
* Doctrine_Event::RECORD_SERIALIZE
* Doctrine_Event::RECORD_UNSERIALIZE

<code type="php">
class MyRecord extends Doctrine_Record
{
    public function preUpdate(Doctrine_Event $event)
    {
        $event->getCode(); // Doctrine_Event::RECORD_UPDATE
    }
}
</code>
+++ getInvoker()

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">
class MyRecord extends Doctrine_Record
{
    public function preUpdate(Doctrine_Event $event)
    {
        $event->getInvoker(); // Object(MyRecord)
    }
}
</code>

+++ 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.

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">
class MyListener extends Doctrine_EventListener
{
    public function preExec(Doctrine_Event $event)
    {
        // some business logic, then:

        $event->skipOperation();
    }
}
</code>


+++ skipNextListener()

When using a chain of listeners you might want to skip the execution of the next listener. It can be achieved as follows:

<code type="php">
class MyListener extends Doctrine_EventListener
{
    public function preExec(Doctrine_Event $event)
    {
        // some business logic, then:

        $event->skipNextListener();
    }
}
</code>