NAME
persona - control which code will be loaded for an execution context
SYNOPSIS
$ PERSONA=cron perl foo.pl
foo.pl
=================
use persona only_for => '*'; # all modules, maybe regex
use Foo;
Foo.pm
=================
package Foo;
# code to be compiled always
#PERSONA cron || app || book
# code to be compiled only for the "cron", "app" and "book" personas
#PERSONA
# code to be compiled always
#PERSONA !cron
# code to be compiled for all personas except "cron"
#PERSONA !( app || book )
# code to be compiled for all personas except "app" and "book"
my $limit = PERSONA eq 'app' ? 100 : 10; # code using the constant
VERSION
This documentation describes version 0.12.
DESCRIPTION
This module was born out of the need to be able to easily specify which subroutines of a module should be available (as in "compiled") in different sets of mod_perl environments (e.g. the visitors front end web servers, or the personnel's back-office web servers). This both from a memory, database and CPU usage point of view, as well as from the viewpoint of security.
This is most useful when using a database abstraction layer such as Class::DBI or DBIx::Class, where all of the code pertaining to an object is located in one file, while only parts of the code are actually needed (or wanted) on specific execution contexts.
OVERVIEW
By specifying an environment variable, by default PERSONA
, it is possible to indicate the persona for which the source code should be compiled. Any modules that are indicated to support persona dependent code will then be checked for existence of persona conditional markers, and any code that is after a persona marker that does not match the currently selected persona, will be discarded during compilation.
Most likely, not all modules that you load need to be checked for persona specific code. Therefor you must indicate which modules you want this check to be performed for. This can be done with the only_for
parameter when loading the persona
module:
use persona only_for => 'Foo';
will check all files that start with Foo
, such as:
Foo.pm
FooBar.pm
Foo/Bar.pm
but not:
Bar.pm
You can also specify a regular expression that way:
use persona only_for => qr/^(?:Foo|Bar)\.pm$/;
will only check the Foo.pm
and Bar.pm
files. Usually the modules of a certain context that you want checked, share a common prefix. It is then usually easier to specify the setting on the command line:
$ PERSONA=cron perl -Mpersona=only_for,Foo script.pl
would execute the script script.pl
for the persona cron
and have all modules that start with Foo
checked for persona dependent code. Only code that is to be included for all personas, or specifically for the cron
persona, will be compiled.
Suppose we want to have a method override_access
available only for the backoffice
persona. This can be done this way:
#PERSONA backoffice
sub override_access { # only for the back office persona
# code...
}
#PERSONA
sub has_access { # for all personas
# code...
}
It is also possible to have code compiled for all personas except a specific one:
#PERSONA !cron
sub not_for_cron {
# code...
}
#PERSONA
would make the subroutine not_for_cron
available for personas except cron
. It is also possible to have code compiled for a set of personas:
#PERSONA cron || backoffice
sub for_cron_and_backoffice {
# code...
}
#PERSONA
would make the subroutine for_cron_and_backoffice
available for the personas cron
and backoffice
.
Or it is possible to have code compiled for all personas except for a set of personas:
#PERSONA !( app || book )
sub not_for_app_or_book {
# code...
}
would make the subroutine C<not_for_app_or_book> available for all personas
B<except> C<app> and C<book>.
Basically any valid expression consisting of \\w \\s ( ) ! ||
is allowed: if that expression yields a true value, then that code will be compiled.
If you're lazy, and you don't care about any overhead while compiling code, you can indicate that you want all modules checked for PERSONA specific code by specifying '*'
as the indication of which files should be checked.
use persona only_for => '*';
If you want to specify multiple conditions, you can specify only_for
more than once:
use persona only_for => 'Foo', only_for => 'Bar';
To facilitate more complex persona dependencies, all namespaces seen by this module automatically have the constant PERSONA imported into it. This allows the constant to be used in actual code (which will then be optimized away by the Perl compiler so that the code that shouldn't be compiled for that persona, really isn't available for execution in the end).
If you want to make sure that the use of the PERSONA
constant in a file will not break code when using strict (which you should!), you can add:
use strict;
use persona; # compilation error without this
print "Running code for persona " . PERSONA . "\n"
if PERSONA;
in that file. That will export the PERSONA constant, even when it is not set. Another example from Class::DBI / DBIx::Class::
__PACKAGE__->columns( All => ( PERSONA eq 'backoffice' ? @all : @subset ) );
which will only use all columns when executing as the backoffice persona. Otherwise only a subset of columns will be available.
SPECIFYING PERSONA WITHOUT ENVIRONMENT VARIABLES
In order to be able to easily support operating systems that have shells that do not support easy setting of environment variables on the command line, you can also specify the persona from the command line while loading this module:
$ perl -Mpersona=cron bar.pl
will run set the persona to "cron". This can also be combined with other parameters, such as:
$ perl -Mpersona=only_for,*,persona,cron bar.pl
would process all files loaded for the cron
persona. Alternately, the same is possible in source:
use persona 'cron';
would select the cron
persona, but only if no other persona was selected before.
EXAMPLES
The test-suite contains some examples. More to be added as time permits.
THEORY OF OPERATION
When the import
class method of persona
is first called, it looks at whether there is a ENV_PERSONA
environment variable is specified. If it is, its value is used as the name of the environment variable to check for the value to be assigned to the persona. If the ENV_PERSONA
environment variable is not found, PERSONA
will be assumed for the name to check.
If there is a non-empty persona value specified, then an @INC
handler is installed. This handler is offered each file that is require
d or use
d from that moment onward. If it is not a file that should be checked for persona conditional code, it is given back to the normal require
handling.
If the import method determines it is being called from a script that is being called from the command line, it will do
the script and then exit
. This causes the script itself be called with require
, and thus be handled by the @INC
handler we installed.
If it is a file that should be checked, it is searched in the @INC
array. If found, it is opened and all the lines that should be part of the code for the current persona, are added to an in-memory buffer. Then a memory file handle is opened on that buffer and returned for normal require
handling. To make sure that any errors or stack traces show the right line numbers, appropriate #line directives are added to the source being offered to the perl compilation process.
Please do:
perldoc -f require
for more information about @INC handlers.
CLASS METHODS
Some class methods are provided as building bricks for more advanced usage of the persona functionality.
path2source
my $source_ref = persona->path2source($path); # current persona
my ( $source_ref, $skipped ) = persona->path2source( $path, $persona );
Process the file given by the absolute path name for the given persona. Assume the current process' persona if none given. Returns a reference to the scalar containing the processed source, or undef if the file could not be opened. Optionally also returns the number of lines in the original source that were skipped.
This functionality is specifically handy for deployment procedures where source files are pre-processed for execution in their intended context, rather than doing this at compilation time each time. This removes the need for having this module installed in production environments and reduces possible problems with wrong persona settings in an execution context.
REQUIRED MODULES
(none)
MODULE RATING
If you want to find out how this module is appreciated by other people, please check out this module's rating at http://cpanratings.perl.org/p/persona (if there are any ratings for this module). If you like this module, or otherwise would like to have your opinion known, you can add your rating of this module at http://cpanratings.perl.org/rate/?distribution=persona.
ACKNOWLEDGEMENTS
Inspired by the function of load and ifdef modules from the same author. And thanks to the pressure (perhaps unknowingly) exerted by the Amsterdam Perl Mongers.
CAVEATS
%INC SETTING
Please note that if any lines were removed from the source, the path name in %INC
will be postfixed with the string:
(skipped %d lines for persona '%s')
where the %d will be filled with the number of lines skipped, and the %s will be filled with the persona for which the lines were removed. Also note that the __FILE__ compiler constant will not have this information postfixed, as that is more or less expected to be just containing a path at all times.
AUTHOR
Elizabeth Mattijsen, <liz@dijkmat.nl>.
Please report bugs to <perlbugs@dijkmat.nl>.
HISTORY
Developed for the mod_perl environment at Booking.com.
COPYRIGHT
Copyright (c) 2009, 2012 Elizabeth Mattijsen <liz@dijkmat.nl>. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.