The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

IOC::Slinky::Container - an alternative dependency-injection container

SYNOPSIS

    # in myapp.yml
    ---
    container:
        db_dsn: "DBI:mysql:database=myapp"
        db_user: "myapp"
        db_pass: "myapp"
        logger:
            _class: FileLogger
            _constructor_args:
                filename: "/var/log/myapp/debug.log"
        myapp:
            _class: "MyApp"
            _constructor_args:
                dbh:
                    _class: "DBI"
                    _constructor: "connect"
                    _constructor_args:
                        - { _ref => "db_dsn" }
                        - { _ref => "db_user" }
                        - { _ref => "db_pass" }
                        - { RaiseError => 1 }
                logger:
                    _ref: logger

    # in myapp.pl
    # ...
    use IOC::Slinky::Container;
    use YAML qw/LoadFile/;

    my $c = IOC::Slinky::Container->new( config => LoadFile('myapp.yml') );
    my $app = $c->lookup('myapp');
    $app->run;

DESCRIPTION

This module aims to be a (1) transparent and (2) simple dependency-injection (DI) container; and usually preconfigured from a configuration file.

A DI-container is a special object used to load and configure other components/objects. Each object can then be globally resolved using a unique lookup id.

For more information about the benefits of the technique, see Dependency Injection.

METHODS

CLASS->new( config => $conf )

Returns an container instance based on the configuration specified by the hashref $conf. See "CONFIGURATION" for details.

$container->lookup($key)

Returns the $obj if $key lookup id is found in the container, otherwise it returns undef.

CONFIGURATION

Rules

The configuration should be a plain hash reference.

A single top-level key container should be a hash reference; where its keys will act as global namespace for all objects to be resolved.

    # an empty container
    $c = IOC::Slinky::Container->new( 
        config => {
            container => {
            }
        }
    );

A container value can be one of the following:

Native

These are native Perl data structures.

    $c = IOC::Slinky::Container->new( 
        config => {
            container => {
                null        => undef,
                greeting    => "Hello World",
                pi          => 3.1416,
                plain_href  => { a => 1 },
                plain_aref  => [ 1, 2, 3 ],
            }
        }
    );
Constructed Object

These are objects/values returned via a class method call, typically used for object construction. A constructed object is specified by a hashref with special meta fields:

_class = when present, the container then treats this hashref as a constructed object spec. Otherwise this hash reference will be treated as a native value.

_constructor = optional, overrides the method name to call, defaults to "new"

_constructor_args = optional, can be a scalar, hashref or an arrayref. Hashrefs and arrayrefs are dereferenced as hashes and lists respectively. Scalar values are passed as-is. Nesting is allowed.

_constructor_passthru = optional, boolean, default to 0 (false) when this is TRUE, pass the _constructor_args as is without doing any automatic dereference of hashrefs and arrayrefs.

_singleton = optional, defaults to 1, upon lookup, the object is instantiated once and only once in the lifetime of the container.

_lookup_id = optional, alias of this object.

The rest of the hashref keys will also be treated as method calls, useful for attribute/setters initialization immediately after the constructor was called. (Setter Injection)

    $c = IOC::Slinky::Container->new( 
        config => {
            container => {
                # constructor injection
                dbh => {
                    _class              => "DBI",
                    _constructor        => "connect",
                    _constructor_args   => [
                        "DBD:SQLite:dbname=/tmp/my.db",
                        "user",
                        "pass",
                        { RaiseError => 1 },
                    ],
                },
                # setter injection
                y2k => {
                    _singleton          => 0,
                    _class              => "DateTime",
                    year                => 2000,
                    month               => 1,
                    day                 => 1,
                },
            }
        }
    );
    
    my $dbh = $c->lookup('dbh');

    # is roughly equivalent to (though this is a singleton):
    # my $dbh = DBI->connect(
    #   "DBI:SQlite:dbname=/tmp/my.db",
    #   "user",
    #   "pass",
    #   { RaiseError => 1 }
    # );

    my $y2k = $c->lookup('y2k');

    # is equivalent to:
    # my $y2k = DateTime->new;
    # $y2k->year( 2000 );
    # $y2k->month( 1 );
    # $y2k->day( 1 );
Reference

References are "pointers" to the globally accessible container values. References are defined by a hashref with a special meta field _ref, the value of which will be used to lookup when requested.

    $c = IOC::Slinky::Container->new( 
        config => {
            container => {
                dsn     => "DBI:mysql:database=myapp",
                user    => "myapp",
                pass    => "myapp_password",
                dbh     => {
                    _class => "DBI",
                    _constructor => "connect",
                    _constructor_args => [
                        { _ref => "dsn" },
                        { _ref => "user" },
                        { _ref => "pass" },
                    ],
                }, 
            }
        }
    );

    my $dbh = $c->lookup('dbh');
    # is roughly equivalent to:
    # $dbh = DBI->connect( 
    #   $c->lookup('dsn'),
    #   $c->lookup('user'),
    #   $c->lookup('pass'),
    # );
    

    IOC::Slinky::Container's configuration is simply a hash-reference with a specific structure. It can come from virtually anywhere. Our recommended usage then is to externalize the configuration (e.g. in a file), and to use YAML for conciseness and ease-of-editing.

        use IOC::Slinky::Container;
        use YAML qw/LoadFile/;
        my $c = IOC::Slinky::Container->new( config => LoadFile("/etc/myapp.yml") );
        # ...

    As a best practice, IOC::Slinky::Container should NOT be used as a service locator (see Service Locator Pattern). The container should only be referenced at the integration/top-level code. Most of your modules/classes should not even see or bother about the container in the first place. The goal is to have a modular, pluggable, reusable set of classes.

SEE ALSO

Bread::Broad - a Moose-based DI framework

IOC - the ancestor of Bread::Board

Peco::Container - another DI container

http://en.wikipedia.org/wiki/Dependency_Injection

YAML - for externalized configuration syntax

AUTHOR

Dexter Tad-y, <dtady@cpan.org>

COPYRIGHT AND LICENSE

Copyright 2011 by Dexter Tad-y

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.