From 304acca739cdc51a6f2955ed2268b9abe2c9a15b Mon Sep 17 00:00:00 2001 From: Vadim Belman Date: Thu, 29 Sep 2016 20:26:31 -0400 Subject: [PATCH] Item14152: Some more documentation for extensions. --- EmptyExtension/lib/Foswiki/Extension/Empty.pm | 138 +++++++++++++++++- core/lib/Foswiki/Aux/Callbacks.pm | 12 +- 2 files changed, 143 insertions(+), 7 deletions(-) diff --git a/EmptyExtension/lib/Foswiki/Extension/Empty.pm b/EmptyExtension/lib/Foswiki/Extension/Empty.pm index a4681375c6..49ddcc0601 100644 --- a/EmptyExtension/lib/Foswiki/Extension/Empty.pm +++ b/EmptyExtension/lib/Foswiki/Extension/Empty.pm @@ -39,6 +39,7 @@ application's life cycle. It is also mandatory for an extension class to subclass =Foswiki::Extension=. The manager would reject a class registration if this rule is broken. +#SingleExtSet At any given moment of time there is only one active set of extensions accessible via the application's =extensions= attribute. It means that if there is a registered =Sample= extension then whenever we a ask for the extension's @@ -77,10 +78,145 @@ our $API_VERSION = version->declare("2.99.0"); ---++ Foswiki::Class exported subroutines Being used with =extension= parameter =Foswiki::Class= exports a set of -subroutines +subroutines to simplify and improve readibility of some of extensions +functionality. As such, their use is similar to =CPAN:Moo= +[[https://metacpan.org/pod/Moo#IMPORTED-SUBROUTINES][subroutines]]. + +---+++ Extension dependencies + +An extension can claim to be located before or after another one in the list of +extension objects (=extensions= attribute of =Foswiki::Extensions= class). This defines +inheritance and callback execution order. I.e., if =Ext2= goes after =Ext1= and +both register a callback handler for =Foswiki::App::postConfig= then =Ext1= handler +will be called first. + +This doesn't apply to registered macros because there could only be single handler for +a macro. + +The following subs implement this functionality: + +| *Sub name* | *Description* | +| =extBefore @nameList= | Extension must be placed before extensions in =@nameList= | +| =extAfter @nameList= | Extension must be placed after extensions in =@nameList= | + +What these do is define a directed graph of extensions. When all extensions are +loaded and registered the graph gets sorted using topoligical sort. + +The final order of extensions is not guaranteed. For example, =Ext2= could +require to be placed before =Ext1= but it doesn't mean that it will directly +preceed it in the list as other extensions could be inserted between them. A +typical example of such behaviour would be =Ext1= requiring to be placed after +=Ext3=. Besides, nothing is guaranteed about single extensions not requiring any +specific order. They can be inserted anywhere in the list. Same apply to two or +more subsets of extensions not bound to each other with directional relations. + +If the graph happens not to be acyclic and we find a circular dependency then +all extensions involved into the chain are getting disabled. For example, if the +chain looks like the following example: + +Ext1 %M% Ext2 %M% Ext3 %M% Ext4 %M% Ext5 %M% Ext3 + +then not only =Ext[3,4,5]= are disabled but =Ext1= and =Ext2= too. + +This behaviour is considered questionable and may change later. + +=cut + +#extBefore qw(Ext1 Ext2); +#extAfter qw(Foswiki::Extension::Ext3); + +=begin TML + +---+++ Custom macros + +Custom macros are declared using =tagHandler= subroutine. It accepts two parameters: +the first is the macro name; the second is either a coderef or a class name. For coderef +a method named after the macro is generated for extension's class. I.e.: + + +package Foswiki::Extension::Ext; + +... + +tagHandler MYMACRO => sub { + my $this = shift; + my ($attrs, $topicObject, @macroArgs) = @_; + ... +}; + + +would generate a method named =MYMACRO= in =Foswiki::Extension::Ext= class. + +For cases when the second parameter is a class name: + + +... + +tagHandler MYMACRO => 'Foswiki::Extension::Macro::MYMACRO'; + + +it is expected that the class would does =Foswiki::Macro= role. An object of +this class will be created on demand by =Foswiki::Macros=. It won't get any +reference to the extension object. Would the object be needed to expand the +macro then =Foswiki::Extensions= =extObject()= method *must* be used to obtain +the reference. + +=cut + +tagHandler EMPTYMACRO => sub { + my $this = shfit; + return __PACKAGE__ . " version is " . $VERSION; +}; + +=begin TML + +---+++ Callbacks + +Callbacks are here to replace the old *Handler mechanism. They're developed as +more powerful, flexible, and OO-friendly replacement. + +Extension can install a callback handler using =callbackHandler= subroutine. It +receives two parameters: a callback name and a coderef: + + +callbackHandler postConfig => sub { + my $this = shift; + my ($obj, $params) = @_; + + ... +}; + + +The coderef acts as an extension's method. The method gets reference to the +object which actually initiated this callback; and reference to a hash with +parameters supplied by the object – see =params= key of callback arguments in +=Foswiki::Aux::Callbacks=. + +__NOTE:__ The method arguments are different from common callback handler as +described in =Foswiki::Aux::Callbacks= because there is no point of passing the +=data= key of arguments hash. Instead extension callback method can rely on +object's internals whenever needed. + +See =Foswiki::Aux::Callbacks= =cut +callbackHandler postConfig => sub { + my $this = shift; + my ($app) = + @_; # Foswiki::App::postConfig callback doesn't supply any params. +}; + +callbackHandler 'Foswiki::App::handleRequestException' => sub { + my $this = shift; + my ( $app, $params ) = @_; + + if ( $params->{exception}->isa('Foswiki::Exception::DoesntExists') ) { + + # Do something about this class of exceptions. + } +}; + =begin TML ---++ SEE ALSO diff --git a/core/lib/Foswiki/Aux/Callbacks.pm b/core/lib/Foswiki/Aux/Callbacks.pm index 4c7a48d72f..2f1d44d195 100644 --- a/core/lib/Foswiki/Aux/Callbacks.pm +++ b/core/lib/Foswiki/Aux/Callbacks.pm @@ -31,7 +31,7 @@ will work only when the name is used only in one namespace. Otherwise an exception (ASSERT) with error will be raised. Callback is a coderef (a sub) which gets called at certain moments of a class -life cycle. A callback sub is called with the following arguments: +life cycle. A callback sub is supplied with the following arguments: 1 Reference to the object which is calling the callback. 1 A list of key/value pairs where the following keys are defined: @@ -40,12 +40,12 @@ life cycle. A callback sub is called with the following arguments: A named callback may have more than one handler. In this case all handlers are executed in the order they were registerd. No return values are respecred. If a -handler wants to be the last it must raise =Foswiki::Exception::CB::Last= -exception. If set, exception's =returnValue= attribute contains what is returned -by =callback()= method then. +handler wants to be the last it must raise =Foswiki::Exception::Ext::Last= +exception. If set, exception's =rc= attribute contains what is returned by +=callback()= method then. If a callback handler raises any other exception besides of -=Foswiki::Exception::CB::*= then that exception is rethrown further up the call +=Foswiki::Exception::Ext::*= then that exception is rethrown further up the call stack. Example callback handler may look like: @@ -67,7 +67,7 @@ sub cbHandler { # Suppose that $rc is set when the if (defined $rc) { - Foswiki::Exception::CB::Last->throw( returnValue => $rc ); + Foswiki::Exception::Ext::Last->throw( rc => $rc ); } }