NAME
Class::DBI::Factory - offers factory interface to a set of Class::DBI classes
SYNOPSIS
my $factory = Class::DBI::Factory->new( '/path/to/config.file');
my $factory = Class::DBI::Factory->instance($site_id, '/path/to/config.file');
$ENV{_SITE_ID} = 'foo';
$ENV{_CONFIG_DIR} = '/home/bar/conf/';
my $factory = Class::DBI::Factory->instance();
then
my $cd = $factory->retrieve('cd', 2);
my $track = $factory->random('track');
my $artists = $factory->search_like('artist', title => 'Velvet%');
my $new_album = $factory->create('album', {
artist => $artist,
title => 'string',
...
});
my @columns = $factory->columns('cd', 'All');
my @input = grep { $factory->find_column('cd', $_) } $query->param;
my $list = $factory->list('cd',
genre => $genre,
year => 1975,
start_at => 0,
per_page => 20,
sort_by => 'title',
sort_order => 'asc',
);
my $cd_pager = $factory->pager('cd');
my $iterator = $factory->retrieve('artist', 2)->albums;
my $tracks = $factory->list_from( $iterator );
INTRODUCTION
Class::DBI::Factory can be used as a quick, clean way to hold a few cdbi classes together and access their class methods, or as a full framework for mod_perl-based web applications. It comes with five little helpers that provide configuration, list-paging, exception-handling, pre-objects and a handler base class, but you can ignore all that unless you need to use it.
There used to be several pages of cheerful explanation here: that has all been dumped into Class::DBI::Factory::Howto, where it awaits transformation into a useful cookbook. What remains is a much shorter but still reasonably comprehensive overview:
It is possible to use Class::DBI::Factory by itself and without subclassing, especially if you're happy to use the template toolkit. Class::DBI's canonical CD library can be web-enabled with just this little bit of configuration and three page templates:
in your (virtual)host definition:
PerlSetEnv _SITE_TITLE my.site.com
PerlSetEnv _CONFIG_DIR /path/to/files/
<Location "/demo">
SetHandler perl-script
PerlHandler Class::DBI::Factory::Handler
</Location>
And in /path/to/files/cdf.conf
db_type = mysql
db_name = something
db_username = someone
db_password = something
template_path = /path/to/templates
template_suffix = 'html'
class = My::CD
class = My::Artist
class = My::Album
class = My::Genre
There's a sample application included with this distribution. It isn't big or clever, but it shows the basic principles at work and you might even be able to use it as a starting point. It uses SQLite and TT, and should be very easy to set up provided you have a mod_perl-enabled Apache around. It's in ./demo
and comes with a dim but enthusiastic installer and some very basic documentation.
KNOWN ISSUES
- This version of CDF is unlikely to work with any combination other than Class::DBI 0.96 and Ima::DBI 0.33.
- CDF under mod_perl is not compatible with the unique-object-cache introduced in Class::DBI v0.96, and cannot be made so since the cache is held as class data and assumes that an object of a class with a certain id is always the same object. The next version of CDBI will fix this: there are plans to introduce a more structured object cache, and/or to make it possible to subclass some of its storage and retrieval mechanisms. It is possible to get around this by patching Class::DBI, but much easier to avoid it altogether by adding this line to your mod_perl startup.pl:
-
$Class::DBI::Weaken_Is_Available = 0; #disables unique-object stash
- Class::DBI and Apache::DBI are not entirely compatible. This is because Ima::DBI has its own caching mechanism for database handles. It's not a serious problem unless you're using database transactions, in which case some necessary cleaning up doesn't happen, but it's easily avoided just by omitting Apache::DBI from your setup.
SUBCLASSING
In serious use, Class::DBI::Factory and all its helper modules expect to be subclassed and extended. The methods you will want to look at first are probably:
CDF::Handler::build_page()
CDF::Handler::factory_class()
CDF::pre_require()
CDF::post_require()
CDF::extra_methods()
CDF::pager_class()
CDF::list_class()
CDF::Config::skeleton()
CDF::Config::list_parameters()
CDF::Config::hash_parameters()
CDF::Config::default_values()
All of which have been separated out and surrounded with ancillary methods in order to facilitate selective replacement. See the method descriptions below, and in the helper modules, which will go on about it in exhausting detail.
CONFIGURATION
Each factory object is directed by a set of configuration files. They are read in the following order, and any which are not specified or do not exist are just ignored:
- 1. global configuration file (server-wide and applied to all sites)
- 2. local site package-selection file
- 3. package configuration files (server-wide but invoked selectively by sites)
- 4. local site configuration file
- 5. any files specified by include_file directives
The addresses of those files are normally supplied by host-specific environment variables, which are defined by PerlSetEnv
directives in the (virtual)host definition. You can override that mechanism by subclassing any of these methods:
site_id()
Used by the instance
method to identify the site and return the corresponding factory object. By default, just returns $ENV{_SITE_TITLE}
.
config_dir()
Should return the full path to the directory containing configuration files for this instance of the application. By default, returns $ENV{_CONFIG_DIR}
.
It is hoped that at least two conventionally-named files exist within that directory. You can override the package_file_name and config_file_name methods to dictate what those filenames should be.
package_file_name()
Returns the name of the package file that we should look for in config_dir
.
config_file_name()
Returns the name of the configuration file that we should look for in config_dir
.
site_config_file() site_package_file() global_config_file()
...each return the path to the relevant configuration file, if the file exists and is readable. The global config file is not assumed to be in config_dir
: we will use $ENV{_CDF_CONFIG}
.
Any of these methods can return undef if that file is not needed.
CONSTRUCTION
In which a factory is built according to the instructions in the one or more configuration files defined above.
new()
This is the main constructor. It calls build_config
to assemble all the configuration information, creates an empty Factory object and then calls load_classes
to start populating it. It can be supplied with the addresses of configuration files: if none are given, it will use the mechanisms described above to locate them.
my $factory = Class::DBI::Factory->new(
$global_config_file,
$site_package_file,
$site_config_file
);
instance()
Returns the factory corresponding to the supplied site id. If no id is supplied then site_id
is called, which by default will look for $ENV{'_SITE_TITLE'}
. If that doesn't work, we will attempt to use Apache's $ENV{SITE_NAME}
.
If no factory exists for the relevant tag, one will be constructed and stored. Any parameters passed to the instance method after the initial site identifier will be passed on to new
if it is called (but parameters other than the site tag will not have any effect if the tag successfully identifies a factory and no construction is required).
If no site id is available from any source then a singleton factory object will be returned to all requests.
my $factory = Class::DBI::Factory->instance();
# will use environment variables for site id and configuration file
my $factory = Class::DBI::Factory->instance( $site_id );
my $factory = Class::DBI::Factory->instance(
$global_config_file,
$site_package_file,
$site_config_file
);
build_config()
Loads the configuration class and reads all the configuration files it can find into a single configuration object, which it returns (presumably to the constructor).
my $config = Class::DBI::Factory->build_config(
$global_config_file,
$site_package_file,
$site_config_file
);
Note that there is no real difference between the three configuration files, except the fact that they are read in a particular order. The config object will try to work out the addresses of files if they are not given.
refresh_config()
Rebuilds the configuration object if any configuration files have been modified since they were read. This is accomplished just by calling $config->refresh()
, so bear it in mind if you are doing anything clever with the configuration class.
If a refresh_interval parameter has been defined anywhere in the configuration files, this method will check first that the necessary amount of time has passed, and then that none of the configuration files have been updated in that period. Any that have will be read again. The order of reading is preserved.
The refresh_interval is just an economy: it saves us having to check the file dates on every call to instance
. Anything longer than the likely span of a single request will do.
config_class()
Should return the Full::Class::Name that will be used to handle factory configuration. Defaults to Class::DBI::Factory::Config. You will almost certainly want to override build_config if you change this.
config()
Returns the configuration object which the factory is using, with which any settings can be retrieved or set.
If you're using Class::DBI::Factory::Config, then the config object is just a thinly-wrapped AppConfig object.
id()
Returns the site tag by which this factory would be retrieved. This ought to be the same as site_id
, which looks in the host configuration, unless something has gone horribly wrong.
using()
Returns true if the named package has been successfully loaded.
[% 'latest bloggings' IF factory.using('blogpackagename') %]
load_classes()
Each class that has been specified in a configuration file somewhere (the list is retrieved by calling class_names
, if you felt like changing it) is require
d here, in the usual eval-and-check way, and its moniker stored as a retrieval key. This is mostly accomplished by way of calls to the following methods:
pre_require()
This method is called before the loading begins. It can act on the configuration data to affect the list of classes called, or it can just make whatever other preparations you require. The default does nothing.
load_class()
This method handles the details of incorporating each individual class into the application. It requires the module, checks that the require has worked, and among other things makes calls to assimilate_class
and one of post_require
and failed_require
:
assimilate_class()
This method is called to store information about the class in the factory object. The default version here assumes that each class will have at least some of the following methods:
* moniker: a tag by which to refer to the class, eg 'cd'
* class_title: a proper name by which the class can be described, eg 'CD'
* class_plural: plural form of the title
* class_description: a blurb about the class
* class_precedes: see precedence, below
only the moniker is compulsory, and the standard cdbi moniker provides a fallback for that, so you can safely ignore all this unless it seems useful.
post_require
This is another placeholder: it's called after each class is loaded, and supplied with the moniker and class name. The most likely use for this method is to make the factory, template, configuration or other system component available as a class variable, but remember that will break under mod_perl if you want more than one instance of the application.
By default post_require does nothing. The return value is not checked.
package_providing()
$factory->package_providing($view);
$factory->package_providing($moniker);
$factory->package_providing($view || $moniker );
[% package = factory.package_providing(view || moniker) %]
Tries to find out which package provided the view or moniker supplied. Returns the package name.
CLASS RELATIONS
The second function of the factory is to pass instructions to its collected classes and pass their responses back to the caller. This is handled in a fairly intuitive and simple way: a command of the form
My::Class->foo($bar);
can be written
$factory->foo($moniker, $bar);
Provided foo is in the permitted set of methods passed through to data classes, and $moniker maps onto a class that we know, it should just work. The business of passing commands along is handled by a fairly simple AUTOLOAD sub which uses a dispatch table to screen commands and translate them into their real form.
The dispatch table is built by way of calls to two separate subs which define the set of permitted operations as a hash of (factory_method => class_method):
permitted_methods()
This method defines a core set of method calls that the factory will accept and pass on to data classes: the Class::DBI API, basically, along with the extensions provided by Class::DBI::mysql and a few synonyms to cover old changes (has_column == find_column, for example) or simplify template code. Subclass this method to replace the factory API with one of your own.
extra_methods()
This is a hook to allow subclasses to extend (or selectively override) the set of permitted method calls with a minimum of bother. It is common for a local subclass of Class::DBI to add a few custom operations to the normal cdbi set: a retrieve_latest
here, a retrieve_by_serial_code
there. To expose those functions through the factory, you just need to put them in the hashref returned by extra_methods
. It's also a nice chance to omit the verbal clutter used to avoid clashes with column names:
sub extra_methods {
return {
latest => retrieve_latest,
by_serial => retrieve_by_serial_code,
by_title => retrieve_by_title,
}
}
The keys of this hash become visible as factory methods, and the corresponding values are used to pass the call on to the data class. In this case,
$factory->latest('cd', foo, bar);
would be passed on as
My::CD->latest(foo, bar);
Which will of course fail if no latest
method has been defined (or no My::CD package has been loaded).
use_classes()
You can pass a list of Full::Class::Names directly to this method and skip the whole configuration bother. This has to be done first, before any other factory method is called. It can be combined with configuration data, but it's a recent and experimental addition, so strangeness is likely.
classes()
returns an array reference containing the list of monikers. This is populated by the load_classes
method and includes only those classes which were successfully loaded.
class_names()
returns an array reference containing the list of full class names: this is taken straight from the configuration file and may include classes that have failed to load, since it is from this list that we try to require
the classes.
class_name()
Returns the full class name for a given moniker.
has_class()
Returns true if the supplied value is a valid moniker.
ghost_class()
Override to use a ghost class other than Class::DBI::Factory::Ghost (eg if you have subclassed it).
ghost_object( moniker, columns_hashref )
Creates and returns an object of the ghost class, which is just a data-holder used to populate forms.
ghost_from( data_object )
Returns a ghost object based on the class and properties of the supplied real object. Useful to keep a record of an object about to be deleted, for example.
(In which case the deleted object can be reconsituted with a call to $ghost-\
make>.)
title() plural() description()
each return the corresponding value defined in the data class, as in:
Which of these [% factory.plural('track') %]
has not been covered by a boy band?
GOODS AND SERVICES
The rest of the factory's functions are designed to provide support to Class::DBI applications. The factory is an efficient place to store widely used components like database handles and template engines, pagers, searches and lists, and to keep useful tools like escape
and unescape
, so that's what we do:
set_db()
Can be used to set database connection values if for some reason you don't want them in a config file. Expects to receive a hashref of parameters. The tests for CDF use this approach, if you want a look.
$factory->set_db({
db_type => '...', # defaults to 'SQLite'
db_host => '...', # in which case no other parameters
db_port => '...', # are needed except a path/to/file
db_name => '...', # in db_name
db_username => '...',
db_password => '...',
});
Defaults can be supplied by Class::DBI::Config::default_values
, which is called early in the configuration process.
dsn()
Returns the $data_string that CDF is using to create handles for this factory. Some modules - like Class::DBI::Loader - want to be given a dsn rather than a database handle: sending them $factory->dsn should just work.
If a db_dsn parameter is supplied, it is accepted intact. Otherwise we will look for db_type, db_name, db_host, db_server and db_port parameters to try and build a suitable data source string. You will probably also want to db_username and db_password settings unless you're using SQLite.
dbh()
Returns the database handle which is used by this factory.
Each factory normally has one handle, created according to its configuration instructions and then made available to all its data classes. The main point of this is to get around the one class -> one table assumptions of Class::DBI: each factory can provide a different database connection to the data using different data.
For this to be useful you must also override db_Main in your Class::DBI subclass, eg:
sub db_Main { return shift->factory->dbh(@_); }
Should do it, except that you will probably have subclassed CDF, and should use the name of your subclass instead.
You can safely ignore all this unless your data clases need access to configuration information, template handler, unrelated other data classes or some other factory mechanism.
_dbc()
Taps into the terrible innards of Ima::DBI to retrieve a closure that returns a database handle of the right kind for use here, but instead of being incorprated as a method, the closure is stored in the factory object's hashref.
(All dbh
really does is to execute the closure held in $self->{_dbc}.)
This depends on close tracking of internal features of Ima::DBI and Class::DBI, since there is no easy way to make use of the handle-creation defaults from the outside. It will no doubt have to change with each update to cdbi.
tt()
Like the database handle, each factory object can hold and make available a single Template object. This is almost always called by handlers during the return of a page, but you sometimes find that the data classes themselves need to make use of a template, eg. to publish a page or send an email. If you don't intend to use the Template Toolkit, you can override process
or just ignore all this: the Toolkit is not loaded until tt
is called.
Template paths can be supplied in two ways: as simple template_dir parameters, or by supplying a single template_root and several template_subdir parameters. The two can be combined: See Class::DBI::Factory::Config for details.
process()
$self->process( $template_path, $output_hashref, $outcome_scalar_ref );
Uses the local Template object to display the output data you provide in the template you specify and store the resulting text in the scalar (or request object) you supply (or to STDOUT if you don't). If you're using a templating system other than TT, this should be the only method you need to override.
Note that process
returns Apache's OK on success and SERVER_ERROR on failure, and OK is zero. It means you can close a method handler with return $self-
process(...)> but can't say $self-<gt
process(...) or ... >
This is separated out here so that all data classes and handlers can use the same method for template-parsing. It should be easy to replace it with some other templating system, or amend it with whatever strange template hacks you like to apply before returning pages.
pager()
returns a pager object for the class you specify. Like all these methods, it defers loading the pager class until you call for it.
my $pager = $factory->pager('artist');
pager_class()
Should return the Full::Class::Name that will be used to create pagers. Defaults to Class::DBI::Pager.
list()
returns a list object with the parameters you specify. These can include column values as well as display parameters:
my $list = $factory->list('cd',
year => 1969,
artist => $artist_object,
sort_by => 'title',
sort_order => 'asc',
step => 20,
);
The default list module (Class::DBI::Factory::List) will build a query from the criteria you specify, turn it into an iterator and provide hooks that make it easy to display and paginate lists.
list_from()
For situations where the list
method doesn't quite provide the right access, you can also create a list object from any iterator by calling:
my $list = $factory->list_from($iterator);
Which will provide display and pagination support without requiring you to jump through so many hoops.
list_class()
Should return the Full::Class::Name that will be used to handle paginated lists. Defaults to Class::DBI::Factory::List.
iterator_from( class, listref )
Returns an iterator built around the list of supplied ids. A list of objects can also be used instead: it's not very efficient, but sometimes it's necessary.
iterator_class()
Should return the Full::Class::Name that will be used to construct an iterator. Defaults to Class::DBI::Iterator.
reify()
reify
and type_map
are probably redundant these days, but they do no harm and so remain here for now.
Reify is a sort of filter, most commonly used to sift through input data and inflate any objects it finds there. When supplied with a pair of moniker => content:
if the content is already an object, it comes straight back
if the moniker is valid, you get an inflated object back
otherwise you just get the content back as it is
It is less useful now than it used to be: with the abstraction of inflate
and deflate
methods and the odd bit of overloading, it seems that Class::DBI minds a lot less whether it gets objects or ids.
type_map()
This is another lookup method: you can use it to map column names onto monikers if for some reason you haven't just made them the same thing. The usual reason for this is that a class has two separate relations to a foreign class. Consider:
My::Artist->has_a( best_cd => 'My::CD' );
My::Artist->has_a( worst_cd => 'My::CD' );
You can't call both columns 'cd', so the factory needs to be told that a 'best_cd' column is actually a reference to an object of type 'cd'.
By default the only mapping is that 'parent' is assumed to be a relationship within the same class.
ADMINISTRIVIA
log()
Whatever you send to log
is pushed onto the log...
report()
...ready to be read back out again when you call report
. In scalar it returns the latest item, in list the whole lot in ascending date order.
debug()
Set debug_level to a value greater than zero and the inner monologue of the handler (and some other modules, and any that you add) will be directed to STDERR. The first value supplied to debug should be an integer, the rest messages. If the number is less than debug_level, the messages will be printed.
$self->factory->debug(2, "session id is $s", " session key is $k");
$self->factory->debug(-1, "this will always appear in the log");
debug_level()
Sets and gets the threshold for display of debugging messages. Defaults to the config file value (set by the debug_level parameter). Roughly:
- debug_level = 1
-
prints a few important messages: usually ways in which this request or operation differs from the normal run of things
- debug_level = 2
-
prints markers as well, to record the stages of a request or operation as it is handled. This is a useful trace when trying to locate a failure.
- debug_level = 2
-
adds more detail, and...
- debug_level = 4+
-
...prints pretty much everything as it happens.
email_message( parameter_hashref )
Sends email (using Email::Send). The hashref of parameters must include at least 'to' and 'subject' or we'll bail silently. It can also include either a 'message' parameter containing the text of the message, or a 'template' parameter containing the address of the TT template that should be used to produce the message. The whole parameter hashref will be passed on to the template, so any other variables you want to make available can just be included there.
If both message and template parameters are supplied, we will use the template and hope that it has a [% message %] somewhere. If neither is supplied, the result will be an empty message with the subject and address you supply (which might be all that's required).
Parameter names are all lower-case, but remember to capitalise the From, To and Subject in message templates.
If you're having trouble with this, check your configuration's 'default_mailer' parameter, and compare against the documentation for Email::Send. It has probably defaulted to Sendmail.
email_admin( parameter_hashref )
A shortcut that will send a message to the configured admin address. This is mostly useful for error messages, which can be as simple as:
$factory->email_admin({
subject => 'uh oh',
message => 'Something terrible has happened.',
};
timestamp()
A get or set method which is normally used to mark the factory with the time its configuration files were last read.
version()
Returns $VERSION
.
DEBUGGING
add_status_menu()
If CDF is loaded under mod_perl and Apache::Status is in your mod_perl configuration, then calling Class-
add_status_menu> will add a menu item to the main page. The obvious place to call it from is startup.pl.
The report it produces is useful in debugging multiple-site configurations, solving namespace clashes and tracing startup problems, none of which should happen if the module is working properly, but you know.
Remember that the server must be started in single-process mode for the reports to be of much use, and that factories are not created until they're needed (eg. on the first request, not on server startup), so you need to blip each site before you can see its factory in the report.
BUGS
Are likely. Please use http://rt.cpan.org/ to report them, or write to wross@cpan.org to suggest more sweeping changes and new features. I'm keen to get this right and likely to respond quickly.
TODO
Ensure cross-database compatibility (I've only used this with mysql and sqlite). Especially problematic for CDF::List, probably.
Improve Apache::Status reports. Include optional logs and error reports.
Write direct tests for the other modules
REQUIRES
- Class::DBI
- AppConfig (unless you replace the configuration mechanism)
- Apache::Request (if you use CDF::Handler)
- Apache::Cookie (if you use CDF::Handler);
- DBD::SQLite (but only for tests and demo)
SEE ALSO
Class::DBI Class::DBI::Factory::List Class::DBI::Factory::Config Class::DBI::Factory::Handler Class::DBI::Factory::Exception Class::DBI::Factory::Ghost
AUTHOR
William Ross, wross@cpan.org
COPYRIGHT
Copyright 2001-4 William Ross, spanner ltd.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.