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.