| 1 | package Class::Hookable;
|
|---|
| 2 |
|
|---|
| 3 | use strict;
|
|---|
| 4 | use warnings;
|
|---|
| 5 |
|
|---|
| 6 | use Carp ();
|
|---|
| 7 | use Scalar::Util();
|
|---|
| 8 |
|
|---|
| 9 | our $VERSION = '0.01';
|
|---|
| 10 |
|
|---|
| 11 | sub new { bless {}, shift }
|
|---|
| 12 |
|
|---|
| 13 | sub hooks {
|
|---|
| 14 | my ( $self ) = @_;
|
|---|
| 15 |
|
|---|
| 16 | if ( ref $self->{'Class::Hookable'} ne 'HASH' ) {
|
|---|
| 17 | $self->{'Class::Hookable'} = {
|
|---|
| 18 | hooks => {},
|
|---|
| 19 | };
|
|---|
| 20 | }
|
|---|
| 21 |
|
|---|
| 22 | return $self->{'Class::Hookable'}->{'hooks'};
|
|---|
| 23 | }
|
|---|
| 24 |
|
|---|
| 25 | sub register_hook {
|
|---|
| 26 | my ( $self, $plugin, @hooks ) = @_;
|
|---|
| 27 |
|
|---|
| 28 | Carp::croak "Plugin object is not blessed object or class name"
|
|---|
| 29 | if ( ref $plugin && ! Scalar::Util::blessed($plugin) );
|
|---|
| 30 |
|
|---|
| 31 | while ( my ( $hook, $callback ) = splice @hooks, 0, 2 ) {
|
|---|
| 32 | my $action = {
|
|---|
| 33 | plugin => $plugin,
|
|---|
| 34 | callback => $callback,
|
|---|
| 35 | };
|
|---|
| 36 |
|
|---|
| 37 | if ( $self->filter_plugin( $hook, $action ) ) {
|
|---|
| 38 | $self->hooks->{$hook} = []
|
|---|
| 39 | if ( ref $self->hooks->{$hook} ne 'ARRAY' );
|
|---|
| 40 |
|
|---|
| 41 | push @{ $self->hooks->{$hook} }, $action;
|
|---|
| 42 | }
|
|---|
| 43 | }
|
|---|
| 44 | }
|
|---|
| 45 |
|
|---|
| 46 | sub filter_plugin { 1 }
|
|---|
| 47 |
|
|---|
| 48 | sub registered_hooks {
|
|---|
| 49 | my ( $self, $object ) = @_;
|
|---|
| 50 |
|
|---|
| 51 | if ( ref $object && ! Scalar::Util::blessed( $object ) ) {
|
|---|
| 52 | Carp::croak "Argument is not blessed object or class name.";
|
|---|
| 53 | }
|
|---|
| 54 |
|
|---|
| 55 | my $is_class = ( ! ref $object ) ? 1 : 0 ;
|
|---|
| 56 | my @hooks = ();
|
|---|
| 57 |
|
|---|
| 58 | for my $hook ( keys %{ $self->hooks } ) {
|
|---|
| 59 | for my $action ( @{ $self->hooks->{$hook} } ) {
|
|---|
| 60 | my $plugin = $action->{'plugin'};
|
|---|
| 61 | my $class = ref $plugin || $plugin;
|
|---|
| 62 | if ( $is_class ) {
|
|---|
| 63 | push @hooks, $hook if ( $class eq $object );
|
|---|
| 64 | }
|
|---|
| 65 | else {
|
|---|
| 66 | push @hooks, $hook if ( $plugin eq $object );
|
|---|
| 67 | }
|
|---|
| 68 | }
|
|---|
| 69 | }
|
|---|
| 70 |
|
|---|
| 71 | return @hooks;
|
|---|
| 72 | }
|
|---|
| 73 |
|
|---|
| 74 | sub registered_plugins {
|
|---|
| 75 | my ( $self, $hook ) = @_;
|
|---|
| 76 |
|
|---|
| 77 | Carp::croak "Hook name is not specified." if ( ! defined $hook );
|
|---|
| 78 |
|
|---|
| 79 | my $list = $self->hooks->{$hook};
|
|---|
| 80 | $list ||= [];
|
|---|
| 81 |
|
|---|
| 82 | return @{ $list };
|
|---|
| 83 | }
|
|---|
| 84 |
|
|---|
| 85 | sub delete_plugin {
|
|---|
| 86 | my ( $self, $object, @hooks ) = @_;
|
|---|
| 87 |
|
|---|
| 88 | if ( ref $object && ! Scalar::Util::blessed($object) ) {
|
|---|
| 89 | Carp::croak "Argument is not blessed object or class name.";
|
|---|
| 90 | }
|
|---|
| 91 |
|
|---|
| 92 | my $is_class = ( ! ref $object ) ? 1 : 0 ;
|
|---|
| 93 | @hooks = keys %{ $self->hooks } if ( @hooks == 0 );
|
|---|
| 94 |
|
|---|
| 95 | for my $hook ( $self->registered_hooks( $object ) ) {
|
|---|
| 96 | next if ( ! grep { $hook eq $_ } @hooks );
|
|---|
| 97 |
|
|---|
| 98 | my @actions = ();
|
|---|
| 99 | for my $action ( $self->registered_plugins( $hook ) ) {
|
|---|
| 100 | my $plugin = $action->{'plugin'};
|
|---|
| 101 | my $class = ref $plugin || $plugin;
|
|---|
| 102 | if ( $is_class ) {
|
|---|
| 103 | push @actions, $action if ( $class ne $object );
|
|---|
| 104 | }
|
|---|
| 105 | else {
|
|---|
| 106 | push @actions, $action if ( $plugin ne $object );
|
|---|
| 107 | }
|
|---|
| 108 | }
|
|---|
| 109 |
|
|---|
| 110 | $self->hooks->{$hook} = \@actions;
|
|---|
| 111 | }
|
|---|
| 112 |
|
|---|
| 113 | }
|
|---|
| 114 |
|
|---|
| 115 | sub delete_hook {
|
|---|
| 116 | my ( $self, $hook, @plugins ) = @_;
|
|---|
| 117 |
|
|---|
| 118 | Carp::croak "Hook is not specified." if ( ! defined $hook );
|
|---|
| 119 |
|
|---|
| 120 | if ( @plugins == 0 ) {
|
|---|
| 121 | $self->hooks->{$hook} = [];
|
|---|
| 122 | }
|
|---|
| 123 | else {
|
|---|
| 124 | for my $plugin ( @plugins ) {
|
|---|
| 125 | $self->delete_plugin( $plugin, $hook );
|
|---|
| 126 | }
|
|---|
| 127 | }
|
|---|
| 128 | }
|
|---|
| 129 |
|
|---|
| 130 | sub run_hook {
|
|---|
| 131 | my ( $self, $hook, $args, $once, $callback ) = @_;
|
|---|
| 132 |
|
|---|
| 133 | if ( defined $callback && ref $callback ne 'CODE' ) {
|
|---|
| 134 | Carp::croak "callabck is not code reference.";
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 |
|
|---|
| 138 | my @results;
|
|---|
| 139 |
|
|---|
| 140 | my $context = ( defined $self->context ) ? $self->context : $self ;
|
|---|
| 141 |
|
|---|
| 142 | for my $action ( $self->registered_plugins( $hook ) ) {
|
|---|
| 143 | if ( $self->dispatch_plugin( $hook, $args, $action ) ) {
|
|---|
| 144 | my $plugin = $action->{'plugin'};
|
|---|
| 145 | my $result = $action->{'callback'}->( $plugin, $context, $args );
|
|---|
| 146 | $callback->( $result ) if ( $callback );
|
|---|
| 147 | if ( $once ) {
|
|---|
| 148 | return $result if ( defined $once );
|
|---|
| 149 | }
|
|---|
| 150 | else {
|
|---|
| 151 | push @results, $result;
|
|---|
| 152 | }
|
|---|
| 153 | }
|
|---|
| 154 | }
|
|---|
| 155 |
|
|---|
| 156 | return if ( $once );
|
|---|
| 157 | return @results;
|
|---|
| 158 | }
|
|---|
| 159 |
|
|---|
| 160 | sub run_hook_once {
|
|---|
| 161 | my ( $self, $hook, $args, $callback ) = @_;
|
|---|
| 162 | return $self->run_hook( $hook, $args, 1, $callback );
|
|---|
| 163 | }
|
|---|
| 164 |
|
|---|
| 165 | sub dispatch_plugin { 1 }
|
|---|
| 166 |
|
|---|
| 167 | sub context {
|
|---|
| 168 | my $self = shift;
|
|---|
| 169 |
|
|---|
| 170 | if ( ref $self->{'Class::Hookable'} ne 'HASH' ) {
|
|---|
| 171 | $self->{'Class::Hookable'} = {
|
|---|
| 172 | hooks => {},
|
|---|
| 173 | };
|
|---|
| 174 | }
|
|---|
| 175 |
|
|---|
| 176 | if ( @_ ) {
|
|---|
| 177 | my $context = shift;
|
|---|
| 178 | $self->{'Class::Hookable'}->{'context'} = $context;
|
|---|
| 179 | }
|
|---|
| 180 | else {
|
|---|
| 181 | return $self->{'Class::Hookable'}->{'context'};
|
|---|
| 182 | }
|
|---|
| 183 | }
|
|---|
| 184 |
|
|---|
| 185 | 1;
|
|---|
| 186 | __END__
|
|---|
| 187 |
|
|---|
| 188 | =head1 NAME
|
|---|
| 189 |
|
|---|
| 190 | Class::Hookable - Base class for hook mechanism
|
|---|
| 191 |
|
|---|
| 192 | =head1 SYNOPSIS
|
|---|
| 193 |
|
|---|
| 194 | package MyApp::Plugins;
|
|---|
| 195 | use base qw( Class::Hookable );
|
|---|
| 196 |
|
|---|
| 197 | my $hook = MyApp::Plugins->new;
|
|---|
| 198 |
|
|---|
| 199 | $hook->register_hook(
|
|---|
| 200 | $plugin,
|
|---|
| 201 | 'hook.name' => $plugin->can('callback'),
|
|---|
| 202 | );
|
|---|
| 203 |
|
|---|
| 204 | $hook->run_hook('hook.name', $args);
|
|---|
| 205 |
|
|---|
| 206 | =head1 DESCRIPTION
|
|---|
| 207 |
|
|---|
| 208 | Class::Hookable is the simple base class for the hook mechanism.
|
|---|
| 209 | This module supports only a hook mechanism.
|
|---|
| 210 |
|
|---|
| 211 | This module was made by making reference to the hook mechanism of L<Plagger>.
|
|---|
| 212 | I thank L<Tatsuhiko miyagawa|http://search.cpan.org/~miyagawa/> who made wonderful application.
|
|---|
| 213 |
|
|---|
| 214 | =head1 METHODS
|
|---|
| 215 |
|
|---|
| 216 | =head2 new
|
|---|
| 217 |
|
|---|
| 218 | my $hook = Class::Hookalbe->new;
|
|---|
| 219 |
|
|---|
| 220 | This method is a constructor of Class::Hookable.
|
|---|
| 221 | Nothing but that is being done.
|
|---|
| 222 |
|
|---|
| 223 | =head2 register_hook
|
|---|
| 224 |
|
|---|
| 225 | $hook->register_hook(
|
|---|
| 226 | $plugin,
|
|---|
| 227 | 'hook.A' => $plugin->can('callbackA'),
|
|---|
| 228 | 'hook.B' => $plugin->can('callbackB'),
|
|---|
| 229 | );
|
|---|
| 230 |
|
|---|
| 231 | This method registers a plugin object and callbacks which corresponds to hooks.
|
|---|
| 232 |
|
|---|
| 233 | The plugin object is specified as the first argument,
|
|---|
| 234 | and one after that is specified by the order of C<'hook' =E<gt> \&callabck>.
|
|---|
| 235 |
|
|---|
| 236 | =head2 filter_plugin
|
|---|
| 237 |
|
|---|
| 238 | sub filter_plugin {
|
|---|
| 239 | my ( $self, $hook, $action ) = @_;
|
|---|
| 240 | my ( $plugin, $callback ) = @{ $action }{qw( plugin callback )};
|
|---|
| 241 | # your filter code
|
|---|
| 242 | }
|
|---|
| 243 |
|
|---|
| 244 | When registering a plugin, this method is filtered a plugin.
|
|---|
| 245 | Arguments are passed by the order of C<$hook> and C<$action>.
|
|---|
| 246 |
|
|---|
| 247 | =over 2
|
|---|
| 248 |
|
|---|
| 249 | =item C<$hook>
|
|---|
| 250 |
|
|---|
| 251 | The hook name specified as the run_hook method.
|
|---|
| 252 |
|
|---|
| 253 | =item C<$action>
|
|---|
| 254 |
|
|---|
| 255 | The hash reference including plugin and callback.
|
|---|
| 256 |
|
|---|
| 257 | =back
|
|---|
| 258 |
|
|---|
| 259 | When this method has returned ture, plugin and hook are registered,
|
|---|
| 260 | and when having returned false, it isn't registered.
|
|---|
| 261 |
|
|---|
| 262 | This method exists to rewrite when inheriting.
|
|---|
| 263 |
|
|---|
| 264 | =head2 registered_hooks
|
|---|
| 265 |
|
|---|
| 266 | my @hooks = $hook->registered_hooks( $plugin );
|
|---|
| 267 | my @hooks = $hook->registered_hooks( 'ClassName' );
|
|---|
| 268 |
|
|---|
| 269 | This method returns hooks with which a plugin is registered.
|
|---|
| 270 | An argument is plugin object or class name.
|
|---|
| 271 |
|
|---|
| 272 | =head2 registered_plugins
|
|---|
| 273 |
|
|---|
| 274 | for my $action ( $hook->registered_plugins('hook.name') ) {
|
|---|
| 275 | my ( $plugin, $callback ) = @{ $action }{qw( plugin callback )};
|
|---|
| 276 | # some code
|
|---|
| 277 | }
|
|---|
| 278 |
|
|---|
| 279 | This method returns plugin and callback registered with a hook.
|
|---|
| 280 | Return value is a list of hash reference including plugin and callback.
|
|---|
| 281 |
|
|---|
| 282 | =head2 delete_plugin
|
|---|
| 283 |
|
|---|
| 284 | $hook->delete_plugin( $plugin );
|
|---|
| 285 | $hook->delete_plugin( 'ClassName', 'hook.A', 'hook.B' );
|
|---|
| 286 |
|
|---|
| 287 | This method delete a registered plugin.
|
|---|
| 288 |
|
|---|
| 289 | When specifying only a plugin object (or class name) as an argument,
|
|---|
| 290 | a plugin is deleted from all hooks.
|
|---|
| 291 |
|
|---|
| 292 | And when specifying a plugin object (or class name) and hooks as arguments,
|
|---|
| 293 | a plugin is deleted from specified hooks.
|
|---|
| 294 |
|
|---|
| 295 | =head2 delete_hook
|
|---|
| 296 |
|
|---|
| 297 | $hook->delete_hook( 'hook.name' );
|
|---|
| 298 | $hook->delete_hook( 'hook.name', $pluginA, 'ClassName' );
|
|---|
| 299 |
|
|---|
| 300 | This method delete a registered hook.
|
|---|
| 301 |
|
|---|
| 302 | When specifying only a hook as an argument,
|
|---|
| 303 | all plugin registered with the hook are deleted.
|
|---|
| 304 |
|
|---|
| 305 | And when specifying a hook and plugin object (or class name) as arguments,
|
|---|
| 306 | specified plugins are deleted from a specified hook.
|
|---|
| 307 |
|
|---|
| 308 | =head2 run_hook
|
|---|
| 309 |
|
|---|
| 310 | $hook->run_hook( $hook, $args, $once, $callback );
|
|---|
| 311 | my @results = $hook->run_hook('hook.name', \%args, undef, \&callback);
|
|---|
| 312 | my $result = $hook->run_hook('hook.name', \%args, 1, \&callback);
|
|---|
| 313 |
|
|---|
| 314 | This method calls callback of the registered plugin to hook by the registered order.
|
|---|
| 315 | Arguments are specified by the order of C<$hook>, C<$args>, C<$once> and C<$callback>.
|
|---|
| 316 |
|
|---|
| 317 | B<Arguments to run_hook method>:
|
|---|
| 318 |
|
|---|
| 319 | =over 4
|
|---|
| 320 |
|
|---|
| 321 | =item C<$hook>
|
|---|
| 322 |
|
|---|
| 323 | Designation of the hook with which a plugin was registered.
|
|---|
| 324 | This argument is indispensable.
|
|---|
| 325 |
|
|---|
| 326 | =item C<$args>
|
|---|
| 327 |
|
|---|
| 328 | The argument which passes it to callback.
|
|---|
| 329 | This argument is optional.
|
|---|
| 330 |
|
|---|
| 331 | =item C<$once>
|
|---|
| 332 |
|
|---|
| 333 | When this argument becomes true, this method finishes calling callback
|
|---|
| 334 | when the first return value has been received.
|
|---|
| 335 |
|
|---|
| 336 | This argument is optional.
|
|---|
| 337 |
|
|---|
| 338 | =item C<$callback>
|
|---|
| 339 |
|
|---|
| 340 | my $callback = sub {
|
|---|
| 341 | my ( $result ) = @_;
|
|---|
| 342 | # some code
|
|---|
| 343 | }
|
|---|
| 344 |
|
|---|
| 345 | This argument specifies code reference.
|
|---|
| 346 |
|
|---|
| 347 | When having received a return value from callback of the registered,
|
|---|
| 348 | the callback specified by this argument is called.
|
|---|
| 349 |
|
|---|
| 350 | A return value of callback of registered plugin is passed to an argument of this callback.
|
|---|
| 351 |
|
|---|
| 352 | =back
|
|---|
| 353 |
|
|---|
| 354 | B<Argument to callback of the registered plugin>:
|
|---|
| 355 |
|
|---|
| 356 | sub callback {
|
|---|
| 357 | my ( $plugin, $context, $args ) = @_;
|
|---|
| 358 | # some code
|
|---|
| 359 | }
|
|---|
| 360 |
|
|---|
| 361 | The argument by which it is passed to callback is C<$plugin>, C<$context>, C<$args>.
|
|---|
| 362 |
|
|---|
| 363 | =over 3
|
|---|
| 364 |
|
|---|
| 365 | =item C<$plugin>
|
|---|
| 366 |
|
|---|
| 367 | The plugin object which passed a plugin and callback to the register_hook method when registering.
|
|---|
| 368 |
|
|---|
| 369 | =item C<$context>
|
|---|
| 370 |
|
|---|
| 371 | The context object.
|
|---|
| 372 |
|
|---|
| 373 | When C<$hook-E<gt>context> is specified, the specified object is passed,
|
|---|
| 374 | and when it isn't so, C<$hook>(Class::Hookable) object is passed.
|
|---|
| 375 |
|
|---|
| 376 | see also L<"context"> method.
|
|---|
| 377 |
|
|---|
| 378 | =item C<$args>
|
|---|
| 379 |
|
|---|
| 380 | C<$args> specified by the run_hook method.
|
|---|
| 381 |
|
|---|
| 382 | =back
|
|---|
| 383 |
|
|---|
| 384 | =head2 run_hook_once
|
|---|
| 385 |
|
|---|
| 386 | my $result = $hook->run_hook_once( $hook, $args, $callback );
|
|---|
| 387 |
|
|---|
| 388 | This method is an alias of C<$hook-E<gt>run_hook( $hook, $args, 1, \&callback )>.
|
|---|
| 389 |
|
|---|
| 390 | =head2 dispatch_plugin
|
|---|
| 391 |
|
|---|
| 392 | sub dispatch_plugin {
|
|---|
| 393 | my ( $self, $hook, $args, $action ) = @_;
|
|---|
| 394 | my ( $plugin, $callabck ) = @{ $action }{qw( plugin callback )};
|
|---|
| 395 | # some code
|
|---|
| 396 | }
|
|---|
| 397 |
|
|---|
| 398 | When calling a hook, this method does a dispatch of a plugin.
|
|---|
| 399 | Argument are passed by the order of C<$hook>, C<$args>, C<$action>.
|
|---|
| 400 |
|
|---|
| 401 | =over 3
|
|---|
| 402 |
|
|---|
| 403 | =item C<$hook>
|
|---|
| 404 |
|
|---|
| 405 | The hook name specified by the run_hook method.
|
|---|
| 406 |
|
|---|
| 407 | =item C<$args>
|
|---|
| 408 |
|
|---|
| 409 | The argument specified by the run_hook method.
|
|---|
| 410 |
|
|---|
| 411 | =item C<$action>
|
|---|
| 412 |
|
|---|
| 413 | The hash reference including the plugin and the callback.
|
|---|
| 414 |
|
|---|
| 415 | =back
|
|---|
| 416 |
|
|---|
| 417 | When this method has returned true, callback of a plugin is called,
|
|---|
| 418 | and when having returned false, callback isn't called.
|
|---|
| 419 |
|
|---|
| 420 | This method exists to rewrite when inheriting.
|
|---|
| 421 |
|
|---|
| 422 | =head2 context
|
|---|
| 423 |
|
|---|
| 424 | my $context = $hook->context;
|
|---|
| 425 | $hook->context( $context );
|
|---|
| 426 |
|
|---|
| 427 | This method is accessor of context object.
|
|---|
| 428 |
|
|---|
| 429 | When specifying object by this method,
|
|---|
| 430 | it's passed to callback of the plugin as context object.
|
|---|
| 431 |
|
|---|
| 432 | see also L<"run_hook"> method.
|
|---|
| 433 |
|
|---|
| 434 | =head2 hooks
|
|---|
| 435 |
|
|---|
| 436 | my $hooks = $hook->hooks;
|
|---|
| 437 |
|
|---|
| 438 | This method is accessor to hash reference which keeps hooks.
|
|---|
| 439 | all method of Class::Hookable is accessing hooks through this method.
|
|---|
| 440 |
|
|---|
| 441 | =head1 AUTHOR
|
|---|
| 442 |
|
|---|
| 443 | Naoki Okamura (Nyarla) E<lt>thotep@nayrla.netE<gt>
|
|---|
| 444 |
|
|---|
| 445 | =head1 LICENSE
|
|---|
| 446 |
|
|---|
| 447 | This library is free software; you can redistribute it and/or modify
|
|---|
| 448 | it under the same terms as Perl itself.
|
|---|
| 449 |
|
|---|
| 450 | =cut
|
|---|