The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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 required or used 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.