akho has asked for the wisdom of the Perl Monks concerning the following question:

This is the problem: an application is split over several modules. However, I want to have a single configuration file (with paths, database data and the like) for the whole application; using one of the excellent Config:: modules and providing the full path to the config file in every module that uses it seems quite unnatural. Is there a simple and standard way to do such a thing?

I currently use a special class that keeps the configuration in a class variable.

Here is the code:

use strict; package Config::Once; use Config::Auto; our $Config; sub init { my ($inv, $config_path) = @_; my $class = ref ($inv) || $inv; my $config_varname = $class . '::Config'; no strict 'refs'; $$config_varname = Config::Auto::parse( $config_path ); return $$config_varname; } sub cfg { my ($inv, $param) = @_; my $class = ref ($inv) || $inv; my $config_varname = $class . '::Config'; no strict 'refs'; return $$config_varname->{$param} if (defined $param); return $$config_varname; } 1;

Then every application subclasses it:

use strict; package MyApp::Config; use base qw/ Config::Once /; 1;

and the modules can do MyApp::Config->cfg('smth').

Is this the right way to do it? Is something like that (or a better solution) available from CPAN?

Replies are listed 'Best First'.
Re: Application-wide configuration
by blue_cowdawg (Monsignor) on Jul 27, 2007 at 18:47 UTC
        Is this the right way to do it? Is something like that (or a better solution) available from CPAN?

    Someone once asked me to help them learn the right way to fish. My answer to that person was you learn the way to fish that is right for you and the kind of fish you want to catch.

    Same is very true in my humble opinion about your question. There are many right ways to do it.

    First off I'm a big fan of using XML::Simple to parse in an XML file that I have stored on the system that the script is going to run on someplace predictable. Just so happens that with XML::Simple this can be a no brainer because the default behavior for the method XMLin, one of the methods the module defines, is to look for a file with the same base name as the script you are running, in the directory the script is running from with the extension ".xml". The module also provides for pathing behavior as well.

    The module you show in your example is a good start. One trick I've used is to use $0, strip off any extensions to it and use that as a key into my XML file for where the application data lives.


    Peter L. Berghold -- Unix Professional
    Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg
      Config::Auto has similar magic, actually. I needed a different thing: I need to use a configuration file not only in the script itself, but also in several modules (such as database-related stuff). So I need to keep the configuration data (or at least a path to the config file) in some public place.
Re: Application-wide configuration
by ikegami (Patriarch) on Jul 27, 2007 at 18:43 UTC

    There's no need for subclassing.
    There's no reason to accept a reference.

    use strict; use warnings; package Config::Once; use Config::Auto; sub init { my ($class, $config_path) = @_; ... } sub cfg { my ($class, $param) = @_; my $config_varname = $class . '::Config'; no strict 'refs'; return $$config_varname->{$param} if (defined $param); return $$config_varname; } 1;

    From the main program to initialize:

    Config::Auto->init($config_path);

    From anywhere to get data:

    Config::Auto->cfg('varname');
      Subclassing is there so that the modules are slightly more reusable (I use this Config::Once in several applications; don't want to pollute it).

      If we don't need that, we can also drop no strict 'refs' and just do

      use strict; use warnings; package Config::Once; use Config::Auto; our $Config; sub init { my ($class, $config_path) = @_; ... } sub cfg { my ($class, $param) = @_; return $Config->{$param} if (defined $param); return $Config; } 1;

      And the ref was reflectory. Most probably not needed.

      That's not to the point, though.

        The only think I can see subclassing be useful for is if you have to deal with more than one configuration file per application. I read the problem as needing to have a single config file shared by many modules.

        Reusability is not affected. Any other application that needs to have a single config file shared by many modules can reuse this module.

Re: Application-wide configuration
by skx (Parson) on Jul 27, 2007 at 19:37 UTC

    I found it interesting to add a Singleton wrapper around an existing Configuration reading object - so that I could gain easy access to configuration settings from my CGI applications.

    Steve
    --
      Yeah, that looks much like what I'm doing.

      Why do you need the get method? Shouldn't that be accessible through the instance?

      You can also put it the other way round: why do you need instances if you can get things with a class method?

      I may misunderstand something.

      Discussion on subclassing and its uses (above) may also apply if your usage patterns are similar to mine; that's a matter of taste, though. (You'll need to use a fully qualified name for $_config in this module if you do subclass).

        In the code above you need the get method because the singleton returns an instance of itself, not of the configuration object.

        So usage becomes:

        my $config = Singleton::Config->instance(); my $random = $config->get( "block", "setting" );
        Steve
        --
Re: Application-wide configuration
by clinton (Priest) on Jul 28, 2007 at 08:49 UTC
    Check out Config::Loader (disclaimer: I'm the author)

    You provide it with the path to the directory which contains all of your config files and sub-directories, and it will recurse through the tree, loading and merging the files for you.

    This is done only once at application startup, and is easily accessible to the rest of your application:

    • Either you can save the $config object in a global, as in:
      our $C = Config::Loader->new('/path/to/config')
    • or Config::Loader will create a subclass of your choosing:
      use Config::Loader ('My::Config' => '/path/to/config');
      which you use in any module that requires access. In this case, it exports the single function C() which gives you access to the config singleton:
      use My::Config; $config = C(); $db = $config->{global}{hosts}{db}{2}; or $db = C('global.hosts.db.2');

    Config::Loader will handle files containing XML, YAML, JSON, Config::General, INI or Perl. Also, it has a defined merge policy that allows you to overwrite the production configuration locally (eg for development). If you don't like the merge policy, it provides you with hooks to alter it.

    Clint

      Wow.

      Thanks.