NAME
SPOPS::ClassFactory - Create SPOPS classes from configuration and code
SYNOPSIS
# Using SPOPS::Initialize (recommended)
my $config = { ... };
SPOPS::Initialize->process({ config => $config });
# Using SPOPS::ClassFactory
my $config = {};
my $classes_created = SPOPS::ClassFactory->create( $config );
foreach my $class ( @{ $classes_created } ) {
$class->class_initialize();
}
DESCRIPTION
This class creates SPOPS classes. It replaces SPOPS::Configure
-- if you try to use SPOPS::Configure
you will (for the moment) get a warning about using a deprecated interface and call this module, but that will not last forever.
DISCUSSION
(This section will probably be removed or merged into others as the behavior firms up.)
So with configuration, we would create a number of slots into which classes could install behaviors. The slots might look something like:
- manipulate_installed_configuration
- id_method
- read_code
- fetch_by
- has_a (relationship)
- links_to (relationship)
- add_rule
(These are not definite yet and will probably change with actual usage.)
A class in the hierarchy for an object could install a behavior in none or all of the slots. So for instance, SPOPS::Configure::DBI
would go away and be replaced by a 'links_to' behavior being installed by SPOPS::DBI.
Multiple behaviors can be installed in each slot. I am still a little unclear about how things will be ordered -- I suspect that by doing a depth-first inheritance walk we will be ok. The processing of each slot can use a (slightly modified) 'Chain of Responsibility' pattern -- a behavior can decide to perform or not perform any action and continue (OK), to perform an action, to declare the slot finished (DONE), to stop the process entirely (ERROR) or that the behavior has made changes which necessitates refreshing the behavior listing (RESTART)..
As a completely untested example of a behavior, say we wanted to ensure that all of our objects are using a particular SPOPS::DBI subclass:
my $USE_CLASS = 'SPOPS::DBI::Pg';
sub check_spops_subclass {
my ( $config ) = @_;
foreach ( @{ $config->{isa} } ) {
s/^SPOPS::DBI::.*$/$USE_CLASS/;
}
return SPOPS::ClassFactory::OK;
}
We would just put this method in a common parent to all our objects and install the behavior in the 'manipulate_configuration' slot. When the class is configured the rule would be executed and we would never have to worry about our objects using the wrong DBI class again. (This is common in OpenInteract when you install new packages and forget to run 'oi_manage change_spops_driver'.)
I believe this would enable more focused and flexible behaviors. For instance, we could create one 'links_to' behavior for DBI to handle the current configuration style and another to handle the proposed (and more robust) ESPOPS configuration style. The first could step through the 'links_to' configuration items and process only those it can, while the second could do the same.
We could also do wacky stuff, like install a 'read_code' behavior to use LWP to grab a module and checksums off a code repository somewhere. If the checksum and code match up, we can bring the code into the SPOPS class.
This new scheme might sound more complicated, but I believe most of the complicated stuff will be done behind the scenes. These behaviors can be extremely simple and therefore easy to code and understand.
SLOTS
We use the term 'slots' to refer to the different steps we walk through to create, configure and auto-generate methods for an SPOPS class. Each 'slot' can have multiple behaviors attached to it, and the behaviors can come from any of the classes in the @ISA for the generated class.
Here are the current slots and a description of each. Note that they might change -- in particular, the 'links_to' and 'has_a' slots might be merged into a single 'relationship' slot.
manipulate_configuration: Modify the configuration as necessary. SPOPS comes with one method to transform arrayrefs (for easy typing) into hashref (for easy lookup). Other options might be to set application-specific information accessible from all your objects, futz around with the @ISA, etc.
id_method: Very focused: generate an
id( [ $new_id ] )
method. SPOPS uses these to ensure it can get the crucial information from every object -- class and ID -- without having to know what the ID field is.SPOPS comes with a default method for this that will probably work fine for you -- see SPOPS::ClassFactory::DefaultBehavior.
read_code: Reads in code from another class to the class being created/configured. SPOPS comes with a method to read the value(s) from the configuration key 'code_class', find them along @INC and read them in.
But you can perform any action you need here -- you could even issue a SOAP request to read Perl code (along with checksums) off the net, check the code then read it in.
fetch_by: Process the 'fetch_by' configuration key. SPOPS comes with autogenerated methods to do this, but you can modify it and implement your own.
has_a: Process the 'has_a' configuration key. Usually this is implementation-specific and involves auto-generating methods. SPOPS comes with a default for this, but an implementation class can elect to not use it by returning the 'DONE' constant.
links_to: Process the 'links_to' configuration key. Usually this is implementation-specific and involves auto-generating methods.
add_rule: You will probably never need to create a behavior here: SPOPS has one that performs the same duties as
SPOPS::Configure::Ruleset
used to -- it scans the @ISA of a class, finds the ruleset generation methods from all the parents and installs these coderefs to the class.
BEHAVIOR GENERATOR
The behavior generator is called 'behavior_factory' (the name can be imported in the constant 'FACTORY_METHOD') and it takes a single argument, the name of the class being generated. It should return a hashref with the slot names as keys. A value should either be a coderef (for a single behavior) or an arrayref of coderefs (for multiple behaviors).
Here is an example, directly from from SPOPS
:
sub behavior_factory {
my ( $class ) = @_;
require SPOPS::ClassFactory::DefaultBehavior;
DEBUG() && _w( 1, "Installing SPOPS default behaviors for ($class)" );
return { manipulate_configuration => \&SPOPS::ClassFactory::DefaultBehavior::conf_modify_config,
read_code => \&SPOPS::ClassFactory::DefaultBehavior::conf_read_code,
id_method => \&SPOPS::ClassFactory::DefaultBehavior::conf_id_method,
has_a => \&SPOPS::ClassFactory::DefaultBehavior::conf_relate_hasa,
fetch_by => \&SPOPS::ClassFactory::DefaultBehavior::conf_relate_fetchby,
add_rule => \&SPOPS::ClassFactory::DefaultBehavior::conf_add_rules, };
}
BEHAVIOR DESCRIPTION
Behaviors can be simple or complicated, depending on what you need them to do. Here is an example of a behavior installed to the 'manipulate_configuration' slot:
my $USE_CLASS = 'SPOPS::DBI::Pg';
sub check_spops_subclass {
my ( $class ) = @_;
foreach ( @{ $class->CONFIG->{isa} } ) {
s/^SPOPS::DBI::.*$/$USE_CLASS/;
}
return SPOPS::ClassFactory::OK;
}
# NOTE: WE NEED TO DEAL WITH THIS ISA ISSUE SPECIFICALLY, SINCE YOU # HAVE @ISA AND \@$class->CONFIG->{isa}. Do they get synchronized?
...
There can be a few wrinkles, although you will probably never encounter any of them. One of the main ones is: what if a behavior modifies the 'ISA' of a class?
METHODS
create( \%multiple_config, \%params )
This is the main interface into the class factory, and generally the only one you need. That said, most users will only ever require the SPOPS::Initialize
window into this functionality.
Return value is an arrayref of classes created;
The first parameter is a series of SPOPS configurations, in the format:
{ alias => { ... },
alias => { ... },
alias => { ... } }
The second parameter is a hashref of options. Currently there is only one parameter supported, but the future could bring more options.
alias_list (\@) (optional)
List of aliases to process from
\%multiple_config
. If not given we simply read the keys of\%multiple_config
(screening out those that begin with '_').Use this if you only want to process a limited number of the SPOPS class definitions available in
\%multiple_config
.
Multiple Configuration Methods
These methods are basically wrappers around the "Individual Configuration Methods" below, calling them once for each class to be configured.
create_all_stubs( \%multiple_config, \%params )
Creates all the necessary classes and installs the available configuration to each class.
Calls create_stub()
and install_configuration()
.
find_all_behavior( \%multiple_config, \%params )
Retrieves behavior routines from all necessary classes.
Calls find_behavior()
.
exec_all_behavior( \%multiple_config, \%params )
Executes behavior routines in all necessary classes.
Calls exec_behavior()
clean_all_behavior( \%multiple_config, \%params )
Removes behavior routines and tracking information from the configuration of all necessary classes.
Calls: nothing.
Individual Configuration Methods
find_behavior( $class )
Find all the factory method-generators in all members of the inheritance tree for an SPOPS class, then run each of the generators and keep track of the slots each generator uses (behavior map).
Return value is the behavior map, a hashref with keys as class names and values as arrayrefs of slot names. For instance:
my $b_map = SPOPS::ClassFactory->find_behavior( 'My::SPOPS' );
print "Behaviors retrieved for My::SPOPS\n";
foreach my $class_name ( keys %{ $b_map } ) {
print " -- Retrieved from ($class_name): ",
join( ', ' @{ $b_map->{ $class_name } } ), "\n";
}
exec_behavior( $slot_name, $class )
Execute behavior rules in slot $slot_name
collected by find_behavior()
for $class
.
Executing the behaviors in a slot succeeds if there are no behaviors to execute or if all the behaviors execute without returning an ERROR
.
If a behavior returns an ERROR
, the entire process is stopped and a die
is thrown with the message returned from the behavior.
Return value: true if success, die
s on failure.
create_stub( \%config )
Creates the class specified by \%config
, sets its @ISA
to what is set in \%config
and ensures that all members of the @ISA
are require
d.
Return value: same as any behavior (OK or ERROR plus message).
require_isa( \%config )
Runs a 'require' on all members of the 'isa' key in \%config
.
Return value: same as a behavior (OK or ERROR plus message).
install_configuration( $class, \%config )
Installs the configuration \%config
to the class $class
. This is a simple copy and we do not do any transformation of the data.
Return value: same as a behavior (OK or ERROR plus message).
Utility Methods
get_alias_list( \%multiple_config, \%params )
Looks at the 'alias_list' key in \%params
for an arrayref of aliases; if it does not exist, pulls out the keys in \%multiple_config
that do not begin with '_'.
Returns: arrayref of alias names.
find_parent_methods( $class, @method_list )
Walks through the inheritance tree for $class
and finds all instances of any member of @method_list
. The first match wins, and only one match will be returned per class.
Returns: arrayref of two-element arrayrefs describing all the places that $method_name can be executed in the inheritance tree; the first item is the class name, the second a code reference.
Example:
my $parent_info = SPOPS::ClassFactory->find_parent_methods(
'My::Class', 'method_factory', 'method_generate' );
foreach my $method_info ( @{ $parent_info } ) {
print "Class $method_info->[0] found sub which has the result: ",
$method_info->[1]->(), "\n";
}
compare_behavior_map( \%behavior_map, \%behavior_map )
Returns 1 if the two are equivalent, 0 if not.
BUGS
New
This is a very new process, so if you have problems (functionality or backward compatibility) please contact the author or (preferably) the mailing list 'openinteract-help@lists.sourceforge.net'.
TO DO
Nothing known.
SEE ALSO
COPYRIGHT
Copyright (c) 2001 intes.net, inc.. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
AUTHORS
Chris Winters <chris@cwinters.com>