Chris Winters


OpenInteract2::Config::Initializer - Observable configuration initialization events


 # Add an initializer in your package.conf
 name    mypackage
 version 1.10
 config_watcher OpenInteract2::MyInitializerSpops
 config_watcher OpenInteract2::MyInitializerAction
 # And the code in our package -- we'll dynamically add a rule from
 # 'My::Googlable' to a class where 'is_googlable' is set to 'yes'
 package OpenInteract2::MyInitializerSpops;
 use strict;
 sub update {
     my ( $class, $type, $config ) = @_;
     return unless ( $type eq 'spops' );
     if ( $config->{is_googlable} eq 'yes' ) {
         push @{ $config->{rules_from} }, 'My::Googable';
 # Add a configuration observer in the server configuration
 class = OpenInteract2::MyInitializerAction
 # and in the class we'll dynamically add a filter to an action where
 # 'is_googlable' is 'yes'
 package OpenInteract2::MyInitializerAction;
 use strict;
 use OpenInteract2::Context qw( CTX );
 sub update {
     my ( $class, $type, $config ) = @_;
     return unless ( $type eq 'action' );
     if ( 'yes' eq lc $config->{is_googlable} ) {
         CTX->map_observer( google => $config->{class} );


How it works

This class provides a hook for observers to react to individual configuration events at server startup. The pseudocode for processing action and SPOPS configurations looks like this:

 foreach package
    foreach config from package
        set core data
        do basic sanity checking
        trigger event

You can also catch events generated when we create the classes used for localization (via Locale::Maketext, although the pseudocode for processing these is a little different:

 foreach package
    foreach message_file from package
        add messages to server-wide message store
 process all messages into generated classes
 foreach generated class
     trigger event

The event code can do whatever you like. This can be additional (but boring) checks on the data, such as ensuring that certain parameters are always arrayrefs, or always sorted in the same manner. This allows your implementation code to assume that everything will always be setup properly

More interesting: you can provide concise hooks in your configuration that get expanded at runtime to something more complex.

Built-in examples

For example, if you have read OpenInteract2::Manual::SPOPS you know that OpenInteract 2.x allows you to declare security for an SPOPS object with:

 is_secure = yes

In 1.x you had to add a class to the ISA. Which do you think is easier to read and maintain?

Or to enable fulltext searching of your object you can just add to your SPOPS configuration:

 is_searchable = yes

and list the fields whose content you would like indexed without caring about how they are indexed. These are both implemented using this same event-based scheme.

What happens in the first case is that for every object that is tagged with 'is_secure' we simply add SPOPS::Secure to the object 'isa' field. And in the second case we add OpenInteract2::FullTextRules to the 'isa'.


Everything (or nearly everything) you can do in the event can be done in the configuration, so why bother? The primary reason is that it makes for much more concise configuration files. More concise configuration means you are less likely to mess it up and that you will hopefully be more willing to modify it when necessary rather than throwing up your hands and hacking an ugly solution.

This is also done for the same reason that you create accessors instead of allowing direct access to your object data structures. For instance, we may modify the full text indexing implementation to require only an SPOPS ruleset rather than full inheritance.

With the simple declaration we do not have to change any of our SPOPS configurations with the change. If we added the class directly to the 'isa' people would have to change the configuration manually, or we would have to add a runtime hook to modify the 'isa' anyway.


This class also contains the default SPOPS and action configuration observers.


These are the initialization handlers for SPOPS events.


Configurations with 'is_secure' set to 'yes' get SPOPS::Secure added to the 'isa' key.

Creation Security

Configurations with the 'creation_security' key set to hashref have the 'group' key rewritten to accommodate the modifications from CREATION SECURITY_CONVERSION in OpenInteract2::Manual::SPOPS.

Date Conversion

Configurations with one or more 'convert_date_field' entries get SPOPS::Tool::DateConvert added to the 'rules_from' key. Also issues a warning if 'convert_date_format' not defined

Fulltext Searching

Configurations with 'is_searchable' set get OpenInteract2::FullText added to 'isa' as long as at least one field is listed in 'fulltext_field'.

Display Munging

Configurations defining 'display' with 'ACTION' and 'TASK' keys get a 'url' key with the properly rewritten URL; those with both 'ACTION' and 'TASK_EDIT' keys get a 'url_edit' key as well.

Field Discovery

Configurations with 'field_discover' set to 'yes' get SPOPS::Tool::DBI::DiscoverField added to the 'rules_from' key.

'has_a' Munging

Because we have restricted configurations to three levels deep some features cannot be adequately representetd. Complicated 'has_a' relationships are one of these.

In the Perl data structure you might have something like:

   user => {
     has_a => {
        'My::User' => {
           updater => 'updated_by',
           poster  => 'posted_by',

But if you try to do this with an unmodified configuration you will get this far:

 [user has_a]
 My::User = ARGH!

Instead you can use a shortcut:

 [user has_a]
 My::User = updater: updated_by; poster: posted_by

So each name and field is separated by a ':' and each set is separated by a ';'.

Alternatively you can also represent them as a list:

 My::User = updater: updated_by
 My::User = poster: posted_by

or a list using the lovable INI syntax addition:

 @,My::User = updater: updated_by, poster: posted_by

And the system will do the right thing.

DBI Class

Configurations using a DBI datasource get SPOPS::DBI and the database-specific class (e.g., SPOPS::DBI::Sybase) added to 'isa'.

LDAP Class

Configurations using a LDAP datasource get SPOPS::LDAP added to 'isa'.


These are the handlers for action configuration events:

Assign Action Defaults

Read the hashref data from the 'action_info.default' server configuration key and assign it to the configuration where the configuration does not already have data defined.

Security Level Codes

In the action configuration you can use verbose descriptions of security levels like 'READ' and 'WRITE'. These get translated to the codes exported by SPOPS::Secure.

Caching Parameters

If the 'cache_param' key is defined ensure that the internal representation is an arrayref and the parameter names are always in the same order.

Normalized Parameters

This just ensures parameters that can have zero or more values are set to empty arrayrefs (if none defined) or an arrayref with only one value (if one defined). The parameters are: 'url_alt'


There are no built-in observers to catch localization events. If you would like to write your own, the type is 'localization' and the only argument is the name of the class generated:

 sub my_localization_observer {
     my ( $init_class, $type, $localization_class ) = @_;
     return unless ( $type eq 'localization' );
     print "Processing '$localization_class':\n";
     # browse the keys for these localization messages
     no strict 'refs';
     my $this_lexicon = \%{ $localization_class . '::Lexicon' };
     foreach my $msg_key ( keys  %{ $this_lexicon } ) {
         print "   $msg_key: $this_lexicon->{ $msg_key }\n";


You should never be using this class directly. But just in case...


Creates a new object. (Does not hold anything right now.)


Class method to read the configuration observers from the server configuration and ask each package for its observers. These are collected and added to the observer list for this class -- this means you can create new objects at will and each will use the observers from the class.


Copyright (c) 2003-2004 Chris Winters. All rights reserved.


Chris Winters <>