Skip to content

Commit 6f43fcf

Browse files
author
chris.boulton
committed
Add a plugin/event/hook system
1 parent 4d4a5ff commit 6f43fcf

File tree

10 files changed

+430
-19
lines changed

10 files changed

+430
-19
lines changed

README.markdown

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,96 @@ A PECL module (<http://pecl.php.net/package/proctitle>) exists that
227227
adds this funcitonality to PHP, so if you'd like process titles updated,
228228
install the PECL module as well. php-resque will detect and use it.
229229

230+
## Event/Hook System ##
231+
232+
php-resque has a basic event system that can be used by your application
233+
to customize how some of the php-resque internals behave.
234+
235+
You listen in on events (as listed below) by registering with `Resque_Event`
236+
and supplying a callback that you would like triggered when the event is
237+
raised:
238+
239+
Resque_Event::listen('eventName', [callback]);
240+
241+
`[callback]` may be anything in PHP that is callable by `call_user_func_array`:
242+
243+
* A string with the name of a function
244+
* An array containing an object and method to call
245+
* An array containing an object and a static method to call
246+
* A closure (PHP 5.3)
247+
248+
Events may pass arguments (documented below), so your callback should accept
249+
these arguments.
250+
251+
You can stop listening to an event by calling `Resque_Event::stopListening`
252+
with the same arguments supplied to `Resque_Event::listen`.
253+
254+
It is up to your application to register event listeners. When enqueuing events
255+
in your application, it should be as easy as making sure php-resque is loaded
256+
and calling `Resque_Event::listen`.
257+
258+
When running workers, if you run workers via the default `resque.php` script,
259+
your `APP_INCLUDE` script should initialize and register any listeners required
260+
for operation. If you have rolled your own worker manager, then it is again your
261+
responsibility to register listeners.
262+
263+
A sample plugin is included in the `extras` directory.
264+
265+
### Events ###
266+
267+
#### beforeFirstFork ####
268+
269+
Called once, as a worker initializes. Argument passed is the instance of `Resque_Worker`
270+
that was just initialized.
271+
272+
#### beforeFork ####
273+
274+
Called before php-resque forks to run a job. Argument passed contains the instance of
275+
`Resque_Job` for the job about to be run.
276+
277+
`beforeFork` is triggered in the **parent** process. Any changes made will be permanent
278+
for as long as the worker lives.
279+
280+
#### afterFork ####
281+
282+
Called after php-resque forks to run a job (but before the job is run). Argument
283+
passed contains the instance of `Resque_Job` for the job about to be run.
284+
285+
`afterFork` is triggered in the child process after forking out to complete a job. Any
286+
changes made will only live as long as the job is being processed.
287+
288+
#### beforePerform ####
289+
290+
Called before the `setUp` and `perform` methods on a job are run. Argument passed
291+
contains the instance of `Resque_Job` about for the job about to be run.
292+
293+
You can prevent execution of the job by throwing an exception of `Resque_Job_DontPerform`.
294+
Any other exceptions thrown will be treated as if they were thrown in a job, causing the
295+
job to fail.
296+
297+
#### afterPerform ####
298+
299+
Called after the `perform` and `tearDown` methods on a job are run. Argument passed
300+
contains the instance of `Resque_Job` that was just run.
301+
302+
Any exceptions thrown will be treated as if they were thrown in a job, causing the job
303+
to be marked as having failed.
304+
305+
#### onFailure ####
306+
307+
Called whenever a job fails. Arguments passed (in this order) include:
308+
309+
* Exception - The exception that was thrown when the job failed
310+
* Resque_Job - The job that failed
311+
312+
#### afterEnqueue ####
313+
314+
Called after a job has been queued using the `Resque::enqueue` method. Arguments passed
315+
(in this order) include:
316+
317+
* Class - string containing the name of the class the job was scheduled in
318+
* Arguments - array of arguments supplied to the job
319+
230320
## Contributors ##
231321

232322
* chrisboulton

TODO.markdown

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
* Write tests for:
22
* `Resque_Failure`
33
* `Resque_Failure_Redis`
4-
* Plugin/hook type system similar to Ruby version (when done, implement the
5-
setUp and tearDown methods as a plugin)
64
* Change to preforking worker model
75
* Clean up /bin and /demo
86
* Add a way to store arbitrary text in job statuses (for things like progress

lib/Resque.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
require_once dirname(__FILE__) . '/Resque/Event.php';
23
require_once dirname(__FILE__) . '/Resque/Exception.php';
34

45
/**
@@ -103,7 +104,15 @@ public static function size($queue)
103104
public static function enqueue($queue, $class, $args = null, $trackStatus = false)
104105
{
105106
require_once dirname(__FILE__) . '/Resque/Job.php';
106-
return Resque_Job::create($queue, $class, $args, $trackStatus);
107+
$result = Resque_Job::create($queue, $class, $args, $trackStatus);
108+
if ($result) {
109+
Resque_Event::trigger('afterEnqueue', array(
110+
'class' => $class,
111+
'args' => $args,
112+
));
113+
}
114+
115+
return $result;
107116
}
108117

109118
/**

lib/Resque/Event.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
/**
3+
* Resque event/plugin system class
4+
*
5+
* @package Resque/Event
6+
* @author Chris Boulton <[email protected]>
7+
* @copyright (c) 2010 Chris Boulton
8+
* @license http://www.opensource.org/licenses/mit-license.php
9+
*/
10+
class Resque_Event
11+
{
12+
/**
13+
* @var array Array containing all registered callbacks, indexked by event name.
14+
*/
15+
private static $events = array();
16+
17+
/**
18+
* Raise a given event with the supplied data.
19+
*
20+
* @param string $event Name of event to be raised.
21+
* @param mixed $data Optional, any data that should be passed to each callback.
22+
* @return true
23+
*/
24+
public static function trigger($event, $data = null)
25+
{
26+
if (!is_array($data)) {
27+
$data = array($data);
28+
}
29+
30+
if (empty(self::$events[$event])) {
31+
return true;
32+
}
33+
34+
foreach (self::$events[$event] as $callback) {
35+
if (!is_callable($callback)) {
36+
continue;
37+
}
38+
call_user_func_array($callback, $data);
39+
}
40+
41+
return true;
42+
}
43+
44+
/**
45+
* Listen in on a given event to have a specified callback fired.
46+
*
47+
* @param string $event Name of event to listen on.
48+
* @param mixed $callback Any callback callable by call_user_func_array.
49+
* @return true
50+
*/
51+
public static function listen($event, $callback)
52+
{
53+
if (!isset(self::$events[$event])) {
54+
self::$events[$event] = array();
55+
}
56+
57+
self::$events[$event][] = $callback;
58+
return true;
59+
}
60+
61+
/**
62+
* Stop a given callback from listening on a specific event.
63+
*
64+
* @param string $event Name of event.
65+
* @param mixed $callback The callback as defined when listen() was called.
66+
* @return true
67+
*/
68+
public static function stopListening($event, $callback)
69+
{
70+
if (!isset(self::$events[$event])) {
71+
return true;
72+
}
73+
74+
$key = array_search($callback, self::$events[$event]);
75+
if ($key !== false) {
76+
unset(self::$events[$key]);
77+
}
78+
79+
return true;
80+
}
81+
82+
/**
83+
* Call all registered listeners.
84+
*/
85+
public static function clearListeners()
86+
{
87+
self::$events = array();
88+
}
89+
}

lib/Resque/Job.php

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
2+
require_once dirname(__FILE__) . '/Event.php';
23
require_once dirname(__FILE__) . '/Job/Status.php';
4+
require_once dirname(__FILE__) . '/Job/DontPerform.php';
35

46
/**
57
* Resque job.
@@ -25,6 +27,11 @@ class Resque_Job
2527
* @var object Object containing details of the job.
2628
*/
2729
public $payload;
30+
31+
/**
32+
* @var object Instance of the class performing work for this job.
33+
*/
34+
private $instance;
2835

2936
/**
3037
* Instantiate a new instance of a job.
@@ -109,15 +116,32 @@ public function getStatus()
109116
$status = new Resque_Job_Status($this->payload['id']);
110117
return $status->get();
111118
}
112-
119+
113120
/**
114-
* Actually execute a job by calling the perform method on the class
115-
* associated with the job with the supplied arguments.
121+
* Get the arguments supplied to this job.
116122
*
117-
* @throws Resque_Exception When the job's class could not be found or it does not contain a perform method.
123+
* @return array Array of arguments.
118124
*/
119-
public function perform()
125+
public function getArguments()
120126
{
127+
if (!isset($this->payload['args'])) {
128+
return array();
129+
}
130+
131+
return $this->payload['args'];
132+
}
133+
134+
/**
135+
* Get the instantiated object for this job that will be performing work.
136+
*
137+
* @return object Instance of the object that this job belongs to.
138+
*/
139+
public function getInstance()
140+
{
141+
if (!is_null($this->instance)) {
142+
return $this->instance;
143+
}
144+
121145
if(!class_exists($this->payload['class'])) {
122146
throw new Resque_Exception(
123147
'Could not find job class ' . $this->payload['class'] . '.'
@@ -130,18 +154,51 @@ public function perform()
130154
);
131155
}
132156

133-
$instance = new $this->payload['class'];
134-
$instance->args = $this->payload['args'];
157+
$this->instance = new $this->payload['class'];
158+
$this->instance->job = $this;
159+
$this->instance->args = $this->getArguments();
160+
return $this->instance;
161+
}
135162

136-
if(method_exists($instance, 'setUp')) {
137-
$instance->setUp();
138-
}
163+
/**
164+
* Actually execute a job by calling the perform method on the class
165+
* associated with the job with the supplied arguments.
166+
*
167+
* @throws Resque_Exception When the job's class could not be found or it does not contain a perform method.
168+
*/
169+
public function perform()
170+
{
171+
$instance = $this->getInstance();
172+
try {
173+
Resque_Event::trigger('beforePerform', $this);
174+
175+
if(method_exists($instance, 'setUp')) {
176+
$instance->setUp();
177+
}
178+
179+
$instance->perform();
139180

140-
$instance->perform();
181+
if(method_exists($instance, 'tearDown')) {
182+
$instance->tearDown();
183+
}
141184

142-
if(method_exists($instance, 'tearDown')) {
143-
$instance->tearDown();
185+
Resque_Event::trigger('afterPerform', $this);
186+
}
187+
// beforePerform/setUp have said don't perform this job. Return.
188+
catch(Resque_Job_DontPerform $e) {
189+
return false;
190+
}
191+
// Catch any other exception and raise the onFailure event. Rethrow
192+
// the exception
193+
catch(Exception $e) {
194+
Resque_Event::trigger('onFailure', array(
195+
'exception' => $e,
196+
'job' => $this,
197+
));
198+
throw $e;
144199
}
200+
201+
return true;
145202
}
146203

147204
/**

lib/Resque/Job/DontPerform.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
/**
3+
* Exception to be thrown if a job should not be performed/run.
4+
*
5+
* @package Resque/Job
6+
* @author Chris Boulton <[email protected]>
7+
* @copyright (c) 2010 Chris Boulton
8+
* @license http://www.opensource.org/licenses/mit-license.php
9+
*/
10+
class Resque_Job_DontPerform extends Exception
11+
{
12+
13+
}

lib/Resque/Worker.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22
require_once dirname(__FILE__) . '/Stat.php';
3+
require_once dirname(__FILE__) . '/Event.php';
34
require_once dirname(__FILE__) . '/Job.php';
45
require_once dirname(__FILE__) . '/Job/DirtyExitException.php';
56

@@ -180,11 +181,12 @@ public function work($interval = 5)
180181
else {
181182
$this->updateProcLine('Waiting for ' . implode(',', $this->queues));
182183
}
183-
usleep($interval*1000000);
184+
usleep($interval * 1000000);
184185
continue;
185186
}
186187

187188
$this->log('got ' . $job);
189+
Resque_Event::trigger('beforeFork', $job);
188190
$this->workingOn($job);
189191

190192
$this->child = $this->fork();
@@ -231,6 +233,7 @@ public function work($interval = 5)
231233
public function perform(Resque_Job $job)
232234
{
233235
try {
236+
Resque_Event::trigger('afterFork', $job);
234237
$job->perform();
235238
}
236239
catch(Exception $e) {
@@ -314,6 +317,7 @@ private function startup()
314317
{
315318
$this->registerSigHandlers();
316319
$this->pruneDeadWorkers();
320+
Resque_Event::trigger('beforeFirstFork');
317321
$this->registerWorker();
318322
}
319323

0 commit comments

Comments
 (0)