1Object::Pluggable(3) User Contributed Perl Documentation Object::Pluggable(3)
2
3
4
6 Object::Pluggable - A base class for creating plugin-enabled objects
7
9 # A simple POE Component that sends ping events to registered sessions
10 # and plugins every second.
11
12 {
13 package SimplePoCo;
14
15 use strict;
16 use warnings;
17 use base qw(Object::Pluggable);
18 use POE;
19 use Object::Pluggable::Constants qw(:ALL);
20
21 sub spawn {
22 my ($package, %opts) = @_;
23 my $self = bless \%opts, $package;
24
25 $self->_pluggable_init(
26 prefix => 'simplepoco_',
27 types => [qw(EXAMPLE)],
28 debug => 1,
29 );
30
31 POE::Session->create(
32 object_states => [
33 $self => { shutdown => '_shutdown' },
34 $self => [qw(_send_ping _start register unregister __send_event)],
35 ],
36 );
37
38 return $self;
39 }
40
41 sub shutdown {
42 my ($self) = @_;
43 $poe_kernel->post($self->{session_id}, 'shutdown');
44 }
45
46 sub _pluggable_event {
47 my ($self) = @_;
48 $poe_kernel->post($self->{session_id}, '__send_event', @_);
49 }
50
51 sub _start {
52 my ($kernel, $self) = @_[KERNEL, OBJECT];
53 $self->{session_id} = $_[SESSION]->ID();
54
55 if ($self->{alias}) {
56 $kernel->alias_set($self->{alias});
57 }
58 else {
59 $kernel->refcount_increment($self->{session_id}, __PACKAGE__);
60 }
61
62 $kernel->delay(_send_ping => $self->{time} || 300);
63 return;
64 }
65
66 sub _shutdown {
67 my ($kernel, $self) = @_[KERNEL, OBJECT];
68
69 $self->_pluggable_destroy();
70 $kernel->alarm_remove_all();
71 $kernel->alias_remove($_) for $kernel->alias_list();
72 $kernel->refcount_decrement($self->{session_id}, __PACKAGE__) if !$self->{alias};
73 $kernel->refcount_decrement($_, __PACKAGE__) for keys %{ $self->{sessions} };
74
75 return;
76 }
77
78 sub register {
79 my ($kernel, $sender, $self) = @_[KERNEL, SENDER, OBJECT];
80 my $sender_id = $sender->ID();
81 $self->{sessions}->{$sender_id}++;
82
83 if ($self->{sessions}->{$sender_id} == 1) {
84 $kernel->refcount_increment($sender_id, __PACKAGE__);
85 $kernel->yield(__send_event => 'simplepoco_registered', $sender_id);
86 }
87
88 return;
89 }
90
91 sub unregister {
92 my ($kernel, $sender, $self) = @_[KERNEL, SENDER, OBJECT];
93 my $sender_id = $sender->ID();
94 my $record = delete $self->{sessions}->{$sender_id};
95
96 if ($record) {
97 $kernel->refcount_decrement($sender_id, __PACKAGE__);
98 $kernel->yield(__send_event => 'simplepoco_unregistered', $sender_id);
99 }
100
101 return;
102 }
103
104 sub __send_event {
105 my ($kernel, $self, $event, @args) = @_[KERNEL, OBJECT, ARG0..$#_];
106
107 return 1 if $self->_pluggable_process(EXAMPLE => $event, \@args) == PLUGIN_EAT_ALL;
108 $kernel->post($_, $event, @args) for keys %{ $self->{sessions} };
109 }
110
111 sub _send_ping {
112 my ($kernel, $self) = @_[KERNEL, OBJECT];
113
114 $kernel->yield(__send_event => 'simplepoco_ping', 'Wake up sleepy');
115 $kernel->delay(_send_ping => $self->{time} || 1);
116 return;
117 }
118 }
119
120 {
121 package SimplePoCo::Plugin;
122 use strict;
123 use warnings;
124 use Object::Pluggable::Constants qw(:ALL);
125
126 sub new {
127 my $package = shift;
128 return bless { @_ }, $package;
129 }
130
131 sub plugin_register {
132 my ($self, $pluggable) = splice @_, 0, 2;
133 print "Plugin added\n";
134 $pluggable->plugin_register($self, 'EXAMPLE', 'all');
135 return 1;
136 }
137
138 sub plugin_unregister {
139 print "Plugin removed\n";
140 return 1;
141 }
142
143 sub EXAMPLE_ping {
144 my ($self, $pluggable) = splice @_, 0, 2;
145 my $text = ${ $_[0] };
146 print "Plugin got '$text'\n";
147 return PLUGIN_EAT_NONE;
148 }
149 }
150
151 use strict;
152 use warnings;
153 use POE;
154
155 my $pluggable = SimplePoCo->spawn(
156 alias => 'pluggable',
157 time => 1,
158 );
159
160 POE::Session->create(
161 package_states => [
162 main => [qw(_start simplepoco_registered simplepoco_ping)],
163 ],
164 );
165
166 $poe_kernel->run();
167
168 sub _start {
169 my $kernel = $_[KERNEL];
170 $kernel->post(pluggable => 'register');
171 return;
172 }
173
174 sub simplepoco_registered {
175 print "Main program registered for events\n";
176 my $plugin = SimplePoCo::Plugin->new();
177 $pluggable->plugin_add('TestPlugin', $plugin);
178 return;
179 }
180
181 sub simplepoco_ping {
182 my ($heap, $text) = @_[HEAP, ARG0];
183 print "Main program got '$text'\n";
184 $heap->{got_ping}++;
185 $pluggable->shutdown() if $heap->{got_ping} == 3;
186 return;
187 }
188
190 Object::Pluggable is a base class for creating plugin enabled objects.
191 It is a generic port of POE::Component::IRC's plugin system.
192
193 If your object dispatches events to listeners, then Object::Pluggable
194 may be a good fit for you.
195
196 Basic use would involve subclassing Object::Pluggable, then overriding
197 _pluggable_event() and inserting _pluggable_process() wherever you
198 dispatch events from.
199
200 Users of your object can then load plugins using the plugin methods
201 provided to handle events generated by the object.
202
203 You may also use plugin style handlers within your object as
204 _pluggable_process() will attempt to process any events with local
205 method calls first. The return value of these handlers has the same
206 significance as the return value of 'normal' plugin handlers.
207
209 Subclassing Object::Pluggable gives your object the following 'private'
210 methods:
211
212 "_pluggable_init"
213 This should be called on your object after initialisation, but before
214 you want to start processing plugins. It accepts a number of
215 argument/value pairs:
216
217 'types', an arrayref of the types of events that your poco will support,
218 OR a hashref with the event types as keys and their abbrevations
219 (used as plugin event method prefixes) as values. This argument is
220 mandatory.
221
222 'prefix', the prefix for your events (default: 'pluggable_');
223 'reg_prefix', the prefix for the register()/unregister() plugin methods
224 (default: 'plugin_');
225 'debug', a boolean, if true, will cause a warning to be printed every time a
226 plugin call fails.
227
228 Notes: 'prefix' should probably end with a '_'. The types specify the
229 prefixes for plugin handlers. You can specify as many different types
230 as you require.
231
232 "_pluggable_destroy"
233 This should be called from any shutdown handler that your poco has. The
234 method unloads any loaded plugins.
235
236 "_pluggable_process"
237 This should be called before events are dispatched to interested
238 sessions. This gives pluggable a chance to discard events if requested
239 to by a plugin.
240
241 The first argument is a type, as specified to _pluggable_init().
242
243 sub _dispatch {
244 my ($self, $event, $type, @args) = @_;
245
246 # stuff
247
248 my $type = ...
249
250 return 1 if $self->_pluggable_process($type, $event, \@args)) == PLUGIN_EAT_ALL;
251
252 # dispatch event to interested sessions.
253 }
254
255 A reference to the argument array is passed. This allows the plugin
256 system to mangle the arguments or even add new ones.
257
258 "_pluggable_event"
259 This method should be overridden in your class so that pipeline can
260 dispatch events through your event dispatcher. Pipeline sends a
261 prefixed 'plugin_add' and 'plugin_del' event whenever plugins are added
262 or removed, respectively. A prefixed 'plugin_error' event will be sent
263 if a plugin a) raises an exception, b) fails to return a true value
264 from its register/unregister methods, or c) fails to return a valid EAT
265 constant from a handler.
266
267 sub _pluggable_event {
268 my $self = shift;
269 $poe_kernel->post($self->{session_id}, '__send_event', @_);
270 }
271
272 There is an example of this in the SYNOPSIS.
273
275 Subclassing Object::Pluggable gives your object the following public
276 methods:
277
278 "pipeline"
279 Returns the Object::Pluggable::Pipeline object.
280
281 "plugin_add"
282 Accepts two arguments:
283
284 The alias for the plugin
285 The actual plugin object
286 Any number of extra arguments
287
288 The alias is there for the user to refer to it, as it is possible to
289 have multiple plugins of the same kind active in one Object::Pluggable
290 object.
291
292 This method goes through the pipeline's push() method, which will call
293 "$plugin->plugin_register($pluggable, @args)".
294
295 Returns the number of plugins now in the pipeline if plugin was
296 initialized, "undef"/an empty list if not.
297
298 "plugin_del"
299 Accepts the following arguments:
300
301 The alias for the plugin or the plugin object itself
302 Any number of extra arguments
303
304 This method goes through the pipeline's remove() method, which will
305 call "$plugin->plugin_unregister($pluggable, @args)".
306
307 Returns the plugin object if the plugin was removed, "undef"/an empty
308 list if not.
309
310 "plugin_get"
311 Accepts the following arguments:
312
313 The alias for the plugin
314
315 This method goes through the pipeline's get() method.
316
317 Returns the plugin object if it was found, "undef"/an empty list if
318 not.
319
320 "plugin_list"
321 Takes no arguments.
322
323 Returns a hashref of plugin objects, keyed on alias, or an empty list
324 if there are no plugins loaded.
325
326 "plugin_order"
327 Takes no arguments.
328
329 Returns an arrayref of plugin objects, in the order which they are
330 encountered in the pipeline.
331
332 "plugin_register"
333 Accepts the following arguments:
334
335 The plugin object
336 The type of the hook (the hook types are specified with _pluggable_init()'s 'types')
337 The event name[s] to watch
338
339 The event names can be as many as possible, or an arrayref. They
340 correspond to the prefixed events and naturally, arbitrary events too.
341
342 You do not need to supply events with the prefix in front of them, just
343 the names.
344
345 It is possible to register for all events by specifying 'all' as an
346 event.
347
348 Returns 1 if everything checked out fine, "undef"/an empty list if
349 something is seriously wrong.
350
351 "plugin_unregister"
352 Accepts the following arguments:
353
354 The plugin object
355 The type of the hook (the hook types are specified with _pluggable_init()'s 'types')
356 The event name[s] to unwatch
357
358 The event names can be as many as possible, or an arrayref. They
359 correspond to the prefixed events and naturally, arbitrary events too.
360
361 You do not need to supply events with the prefix in front of them, just
362 the names.
363
364 It is possible to register for all events by specifying 'all' as an
365 event.
366
367 Returns 1 if all the event name[s] was unregistered, undef if some was
368 not found.
369
371 The basic anatomy of a pluggable plugin is:
372
373 # Import the constants, of course you could provide your own
374 # constants as long as they map correctly.
375 use Object::Pluggable::Constants qw( :ALL );
376
377 # Our constructor
378 sub new {
379 ...
380 }
381
382 # Required entry point for pluggable plugins
383 sub plugin_register {
384 my($self, $pluggable) = @_;
385
386 # Register events we are interested in
387 $pluggable->plugin_register($self, 'SERVER', qw(something whatever));
388
389 # Return success
390 return 1;
391 }
392
393 # Required exit point for pluggable
394 sub plugin_unregister {
395 my($self, $pluggable) = @_;
396
397 # Pluggable will automatically unregister events for the plugin
398
399 # Do some cleanup...
400
401 # Return success
402 return 1;
403 }
404
405 sub _default {
406 my($self, $pluggable, $event) = splice @_, 0, 3;
407
408 print "Default called for $event\n";
409
410 # Return an exit code
411 return PLUGIN_EAT_NONE;
412 }
413
414 As shown in the example above, a plugin's "_default" subroutine (if
415 present) is called if the plugin receives an event for which it has no
416 handler.
417
418 The special exit code CONSTANTS are documented in
419 Object::Pluggable::Constants. You could provide your own as long as the
420 values match up, though.
421
423 Better documentation >:]
424
426 Chris 'BinGOs' Williams <chris@bingosnet.co.uk>
427
429 Copyright "(c)" Chris Williams, Apocalypse, Hinrik Örn Sigurðsson and
430 Jeff Pinyan
431
432 This module may be used, modified, and distributed under the same terms
433 as Perl itself. Please see the license that came with your Perl
434 distribution for details.
435
437 APOCAL for writing the original POE::Component::IRC plugin system.
438
439 japhy for writing POE::Component::IRC::Pipeline which improved on it.
440
441 All the happy chappies who have contributed to POE::Component::IRC over
442 the years (yes, it has been years) refining and tweaking the plugin
443 system.
444
445 The initial idea was heavily borrowed from X-Chat, BIG thanks go out to
446 the genius that came up with the EAT_* system :)
447
449 POE::Component::IRC
450
451 Object::Pluggable::Pipeline
452
453 Both POE::Component::Client::NNTP and POE::Component::Server::NNTP use
454 this module as a base, examination of their source may yield further
455 understanding.
456
457
458
459perl v5.36.0 2023-01-20 Object::Pluggable(3)