NAME
Class::DBI::Factory - factory interface to a set of Class::DBI classes, with optional mod_perl2 application skeleton
SYNOPSIS
# in a simple script:
my $factory = Class::DBI::Factory->new;
$factory->set_db({
db_type => 'mysql',
db_name => 'items',
db_username => 'me',
db_password => 'password',
});
$factory->use_classes(qw(My::Item));
my @columns = $factory->columns('item');
my $item = $factory->retrieve('item', 1);
my $iterator = $factory->search('item', year => 1980);
# under mod_perl or another persistent environment
$ENV{_SITE_ID} = 'mysite';
my $factory = Class::DBI::Factory->instance();
# in an apache host configuration:
PerlSetEnv _SITE_ID '_my_site'
<Location "/directory">
SetHandler perl-script
PerlResponseHandler Class::DBI::Factory::Handler
</Location>
# and on a template somewhere:
<p>
[% FOREACH album IN factory.search('album',
'year', input.year,
'artist', artist,
) %][% album.title %]<br>[% END %]
</p>
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 a mod_perl-based web application that deals with all the tricky aspects of using Class::DBI with more than one instance of the same application. Yes, Veronica, you can serve as many hosts as you like with one cdbi app.
In the simplest case - you've hacked up a few cdbi classes and you want a quick and easy way to move information through them - you just need to pass in connection parameters and class names:
use Class::DBI::Factory;
my $factory = Class::DBI::Factory->new;
$factory->set_db({
db_type => 'mysql',
db_name => 'items',
db_username => 'me',
db_password => 'password',
});
$factory->use_classes(qw(My::Item));
my $item = $factory->retrieve('item', 1);
You'll soon want to put all that in a configuration file, though:
# in limited-access config file './items.conf'
db_type = 'mysql',
db_name = 'items',
db_username = 'me',
db_password = 'password',
class = My::Item
class = My::Item::Category
class = My::Person
# in your script
use Class::DBI::Factory;
my $factory = Class::DBI::Factory->new('./items.conf');
my $item = $factory->retrieve('item', 1);
It does get a little more complicated after that, but CDF comes with a set of five helpers that might make the rest of your job easier too:
- Class::DBI::Factory::Config
-
Wraps around Andy Wardley's AppConfig to provide a simple, friendly configuration mechanism for CDF and the rest of your application. This is very likely to be loaded during even the simplest use of CDF, but you can supply your own configuration mechanism instead. See CONFIGURATION below.
- Class::DBI::Factory::Handler
-
A fairly comprehensive base class for mod_perl handlers, providing standard ways of retrieving and displaying one or many cdbi objects. If you're happy to use the Template Toolkit, then almost all of your work is already done here in a nice clean MVC-friendly sort of way. See the Class::DBI::Factory::Handler docs for much much more.
- Class::DBI::Factory::Exception
-
Pervasive but fairly basic exception-handling routines for CDF-based applications based on Apache return codes. CDF::Handler uses try/catch for everything, and most of the other classes here will throw a CDF::Exception on error, unless told not to. See
fail()
, below. - Class::DBI::Factory::List
-
A fairly comprehensive builder and paginater of lists. It's iterator-based, so should be able to paginate most normal CDBI query results. You can also supply search criteria during list construction. See
list()
andlist_from()
, below. - Class::DBI::Factory::Ghost
-
'Ghost' objects are cdbi prototypes: each one is associated with a data class but doesn't belong to it. The ghost object will act like the cdbi object in most simple ways: column values can be set and relationships defined, but without any effect on the database. I find this useful before the creation of an object (when populating forms) and after its deletion (for displaying confirmation page and deciding what next), but some people frown on such laziness. See
ghost_object
andghost_from
, below. - Class::DBI::Factory::Mailer
-
This is a simple interface to Email::Send: it can send messages raw or use the factory's Template object to format them, and it will use the factory's configuration settings to decide how email should be sent. See
email_message
below.
None of these modules is loaded unless you make a call that requires it, and all of them are easily replaced with your own subclass or alternative module.
PERSISTENCE AND CONCURRENCY
The factory object will do as little work and load as little machinery as possible, but it's still a relatively expensive thing to build. Fortunately, you should hardly ever have to build one: it's designed to stay in memory, responding to calls from much lighter, briefer Handler objects constructed to deal with each incoming request. The normal sequence goes like this:
- 1. Apache directs an incoming request to mod_perl
- 2. Mod_perl creates a new Handler object to deal with the request.
- 3. Handler object calls up an existing Factory object to access its database, configuration, template-processing machinery and the other centralised resources needed to deal with the request.
- 4. Handler object returns output to Apache and is destroyed.
- 5. Factory waits in memory for next request.
This is all made more useful by the fact that each factory is stored with a distinct id. You can keep several factories active in memory at once, each with a different configuration object and therefore its own cache of database handles, connection parameters, template paths and so on.
Because the data classes have been bullied into asking the factory for database access, this means that you can use the same set of data classes in as many sites as you like without any bother.
The factories don't really 'sleep', of course. They're rather mundanely held in a class-data hash in Class::DBI::Factory. When a handler (or data class, or other script) calls CDF->instance to get its factory object, the instance method will consult its input and environment, work out a factory id and return the factory it's holding against that hash key. If no such factory exists, it will be created, and then left available for future requests.
In most cases, the 'id' associated with the factory will be the name of a website. In that case, all you have to do is include an $ENV{_SITE_ID} in Apache's virtualhost definition:
PerlSetEnv _SITE_ID = 'mysite.com'
See CONFIGURATION
below for the rest of the virtualhost.
The same mechanism can be adapted any other situation where you want to keep one or more factory objects handy without having to pass them round all the time. The factory id can be specified directly:
my $factory = Class::DBI::Factory->instance('cd_collection');
or based on any environment variable you specify, such as:
$Class::DBI::Factory::factory_id_from = 'SITE_NAME';
which will get whatever was defined in Apache's ServerName directive, and
$Class::DBI::Factory::factory_id_from = 'USER';
will keep one factory per user. Note changing $factory_id_from has universal effect within the current namespace.
If no id can be retrieved from anywhere to single out a particular factory, then CDF will return the same singleton factory object on every call to instance(). This is often a useful shortcut, but if you don't like it you can always call new(blah)
and get an entirely new factory object instead.
MANAGED CLASSES
Each factory gathers up a set of what I've been calling 'data classes': that's your standard Class::DBI subclass with columns and relationships and whatnot. The set of classes is defined either by including a number of 'class' parameters in the configuration file(s) for each site, or by calling
$factory->use_classes( list of names );
directly. Or both. The factory will ask each class for its moniker, along with a few other bits of useful information, then it will drop in a couple of methods that force the class to use the factory for database access. The result is as if you had added this in your base data class:
sub _factory { Class::DBI::Factory->instance; }
sub db_Main { shift->_factory->dbh; }
and that should be all that's required to get your application hooked up to the factory. For convenience, I usually add these methods too:
sub config { shift->_factory->config; }
sub debug { shift->_factory->debug(@_); }
sub report { shift->_factory->report(@_); }
sub send_message { shift->_factory->send_message(@_); }
...but I don't want to trample on anyone's columns, so I'll leave that up to you.
Note Class::DBI will follow relationships in the usual way, require()ing the foreign class and hooking it up. You have to declare all those classes in the factory configuration if you want it to provide access to them, or them to have access to it. If you miss out a class in the configuration but mention it in a has_many relationship, the barriers between your sites will break down and bad strangeness will result.
FACTORY INTERFACE
Having bundled up a set of classes, the main purpose of the factory is to pass information between you and them. This is handled in a fairly intuitive and simple way: any mormal cdbi command of the form
My::Class->foo(bar);
can be written
$factory->foo($moniker, bar);
Provided that
My::Class->moniker == $moniker;
and foo() is in the permitted set of methods, it should just work. If $moniker doesn't give us a valid class, we fail silently with only a debugging message. If foo() isn't allowed, we fail noisily.
CONFIGURATION
CDF will look for two configuration files: a global server config and a local site config. You can supply the addresses of these files either by passing them to the constructor or filling in a couple of environment variables:
my $factory = Class::DBI::Factory->new('/global/config.file', '/local/config.file');
#or
$ENV{_CDF_CONFIG} = '/global/config.file';
$ENV{_CDF_SITE_CONFIG} = '/local/config.file';
my $factory = Class::DBI::Factory->new;
There is no functional difference between these files, except the order in which they are read, so if there's only one file you can put it in either position.
Here's an example of a simple configuration file:
db_type = 'SQLite2'
db_name = '/home/cdfdemo/data/cdfdemo.db'
site_url = 'www.myrecords.com'
site_title = 'My Record Collection'
template_dir = '/home/myrecords/templates'
template_suffix = 'html'
class = 'My::Album'
class = 'My::Artist'
class = 'My::Track'
class = 'My::Genre'
debug_level = 4
mailer = 'Qmail'
default_template = 'holder.html'
default_view = 'front'
All of which should be self-explanatory. The config object is available to templates and data classes too, so there is no limit to the sort of information you might want to include there.
There's a sample application included with this distribution, using a configuration much like this one. 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.
CONSTRUCTION METHODS
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:
my $factory = Class::DBI::Factory->new(
$global_config_file,
$site_config_file
);
Note that configuration files and data classes are not loaded until they're needed, so the raw factory object returned by new() is still empty. The _load_classes() and _build_config() calls are deferred for as long as possible.
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(
$site_id,
$global_config_file,
$site_config_file
);
factory_id_from()
A handy mutator that gets or sets the name of the environment variable that we will use as the key when storing and retrieving a factory object. It defaults to the arbitrary _SITE_ID, as explained above.
Note that this has global effect. If you want to do odd things with a particular factory's id, you have to supply it directly to the construction and retrieval methods. The easiest way to do that is call to instance( $site_id ) for the initial construction step.
CONFIGURATION METHODS
There used to be a lot of clutter here, but most of it has been stripped out. CDF now looks for two configuration files: a global file, whose address is given by $ENV{_CDF_CONFIG}
, and a site configuration file whose address is given by $ENV{_CDF_SITE_CONFIG}
. Either file can be skipped (and will be, silently, if it's not found). There is no practical difference between the two files, and instructions can be moved between them or either omitted.
global_config_file() site_config_file()
Mutators for the respective configuration file addresses.
_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).
config_class()
Should return the Full::Class::Name that will be used to handle factory configuration. Defaults to Class::DBI::Factory::Config.
If you change this, you will almost certainly want to override _build_config too.
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.
use_classes()
For quick and dirty work you can skip any or all of these configuration mechanisms and just pass in a list of Full::Class::Names. You can also set configuration parameters, so this will work:
my $factory = Class::DBI::Factory->new;
$factory->use_classes( qw(My::Movie My::Actor My::Role) );
$factory->config->set( worst_effects_ever => 'All out monsters attack!' );
This has to be done first, before any factory method is called that depends on the data classes being loaded, which is nearly all of them.
_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.
Normally this is done only once, and before anything else happens, but if you call _load_classes(1)
(or with any other true value), you force it to require everything again. This doesn't unload the already required classes, so you can't, currently, use this to change the list of managed classes.
This is mostly accomplished by way of calls to the following methods:
pre_require()
This method is called once before the loading of classes begins (unlike post_require, which is called for each class). It can act on the configuration data to affect the list of classes called, or 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 then post_require
for each class:
assimilate_class()
This method is called to store information about the class in the factory object. The default version here hopes 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
Only the moniker is actually required and the standard cdbi moniker mechanism provides a fallback for that, so you can safely ignore all this unless it seems useful.
post_require
This is called for each class after it has been loaded and assimilated, and is supplied with the moniker and full class name. Here it is used to place factory()
and db_Main()
methods in each data class: you may want to override it to prevent or extend this behaviour.
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.
It does this by returning a hash of
factory_method_name => cdbi_object_method_name,
which is used as a dispatch table by AUTOLOAD. Subclass this method to replace the standard factory interface with a reduced or different set of allowed methods.
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 returns a reference to a hash that is appended to the hash returned by permitted_methods
, with the same factory method => cdbi method structure.
It's common for a local subclass of Class::DBI to add custom operations to the normal cdbi set: a retrieve_latest
here, a delete_older_than
there. To access these methods through the factory, you need to add a local factory subclass next to the cdbi subclass, containing at least an extra_methods
method.
package My::Factory;
use base qw( Class::DBI::Factory );
sub extra_methods {
return {
latest => retrieve_latest,
purge => delete_older_than,
by_title => retrieve_by_title,
}
}
The default extra_methods method doesn't do anything, so it can be overridden freely.
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.
relationships( $moniker, $type )
A handy gadget that looks into Class::DBI's meta_info to find the relationships entered into by the monikered class. The relationship type defaults to 'has_a', and we return a hash of method names => foreign class monikers.
$factory->relationships( 'album' );
in the supplied demo application would return ('genre', 'artist').
moniker_from_class()
Given a full class name, returns the moniker. This is hardly ever needed.
inflate_if_possible()
Accepts a column name => value pair and inflates the value, if possible, into a member of the class monikered by the column name.
translate_to_moniker()
Some column names don't match the moniker of the objects they contain: perhaps because there is more than one column containing that type, or perhaps just for readability. get_moniker maps the column name onto the moniker.
The method defined here (which expects to be overridden), strips _id off the end of a column name, and maps 'parent' onto the moniker of the present class.
ghost_class()
Override to use a ghost class other than Class::DBI::Factory::Ghost (eg if you've subclassed it).
ghost_object( moniker, columns_hashref )
Creates and returns an object of the ghost class, which is just a data-holder able to mimic a cdbi object well enough to populate a template, but no more.
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>. You will lose anything that was removed in a cascading delete, though. This is not nearly good enough to serve as an undo mechanism unless you exted the ghost to ghost all its relatives too).
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?
We only have these values if you add the corresponding class_title, class_plural and class_description methods to your data classes.
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.
db_options()
Returns the hash of attributes that will be used to create database connections. Separated out here for subclassing.
db_rootclass()
Returns the full name of the root class for $dbh. It is very unlikely that this will not be DBIx::ContextualFetch, but I suppose you might have subclassed that.
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.
throw_exceptions()
A mutator sets or returns the throw_exceptions flag for this factory. If the flag is set to false, we'll mostly just die instead of throwing a more detailed exception.
fail( $parameters )
A general-purpose failure-handler. Usually throws a SERVER_ERROR exception with the supplied -text parameter, but if throw_exceptions returns false we'll just die instead. Parameters are passed on to the exception handler.
mailer_class()
Returns the full::name of the class we should call on to send email messages. Defaults to Class::DBI::Factory::Mailer.
mailer()
Returns an object of the mailer class. These are usual very dumb creatures with only a send_message method and a few bits and pieces. We'll hang on to it and it should be used for all subsequent emailing duties.
send_message()
Sends an email message. See Class::DBI::Factory::Mailer for more about the required and possible parameters, but to begin with:
$factory->send_message({
to => 'someone@there',
from => 'someone@here',
subject => 'down with this kind of thing',
message => 'Careful now',
});
If you pass through a template parameter, the usual templating mechanism will be used to generate the message, and all the values you have supplied will be passed to the template. Otherwise, the mailer will look for a message parameter and treat that as finished message text.
email_admin()
Sends a message to the standard admin address associated with this factory configuration. Otherwise exactly the same as send_message.
DEBUGGING
The factory provides a general-purpose logger that prints to STDERR. Each debugging message has an importance value, and the configuration of each factory defines a threshold: if the message importance is less than the threshold, the message will be printed.
Note that including debugging lines always incurs some small cost, since this method is called and the threshold comparison performed each time, even if the message isn't printed.
$self->factory->debug_level(1);
$self->factory->debug(2,
"session id is $s",
"session key is $k",
"these messages will not be logged",
);
$self->factory->debug(0, "but this will appear in the log");
debug( $importance, @messages )
Checks the threshold and prints the messages. Each message is prepended with a [site_id] marker, but even so nothing will make much sense if requests overlap. For debugging processes you probably want to run apache in single-process mode.
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 events
- debug_level = 2
-
prints markers as well, to record the stages of a request or operation as it is handled. This can be a useful trace when trying to locate a failure.
- debug_level = 3
-
adds more detail, including AUTOLOAD calls and other bulky but useful notes.
- debug_level = 4
-
prints pretty much everything as it happens.
- debug_level = 5
-
won't shut up.
If debug()
is called as a class method, configuration information will not be available. In that case the global value
$Class::DBI::Factory::class_debug_threshold
will be used. It defaults to zero. Changing it will have global effect within the current namespace (eg all factories within a given apache process).
version()
Returns the global $Class::DBI::Factory::VERSION
, so your subclass will probably want its own version method.
add_status_menu()
If CDF is loaded under mod_perl and Apache::Status is in your mod_perl configuration, then calling CDF-
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.
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 above, and in the helper modules, which will go on about all this in exhausting detail.
I'm trying to keep CDF minimal and to the point, with variable success. It's always tempting to implement something at this level, so that it's universally available, so the development cycle has mostly consisted of throwing stuff in and then pruning carefully. This version (0.9) is mostly the result of pruning, so you can imagine how bushy some of the others have been :)
For an example of how much can be done on this platform, have a look at www.spanner.org/delivery/
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 probably give me a way to work with 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. Until then, the factory disables the cache immediately upon loading.
- 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.
- I haven't tried using CDF with the various setup_table methods provided by cdbi subclasses like Class::DBI::mysql. There's no reason why they shouldn't work, but no reason why they should, either.
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 use this all the time and am likely to respond quickly.
TODO
Ensure cross-database compatibility (I've only used this with mysql and sqlite). This is especially problematic for CDF::List, probably.
Improve Apache::Status reports, eg with optional logs and error reports.
Wiki and mailing list. you know you know.
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::SQLite2 (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.