# NAME
Beam::Wire - Lightweight Dependency Injection Container
# VERSION
version 1.016
# STATUS
# SYNOPSIS
# wire.yml
dbh:
class: 'DBI'
method: connect
args:
- 'dbi:mysql:dbname'
- {
PrintError: 1
}
# myscript.pl
use Beam::Wire;
my $wire = Beam::Wire->new( file => 'wire.yml' );
my $dbh = $wire->get( 'dbh' );
$wire->set( 'dbh' => DBI->new( 'dbi:pgsql:dbname' ) );
# DESCRIPTION
Beam::Wire is a dependency injection (DI) container. A DI (dependency injection)
container is a framework/mechanism where dependency creation and instantiation is
handled automatically (e.g. creates instances of classes that implement a given
dependency interface on request). DI does not require a container, in-fact, DI
without a container is possible and simply infers that dependency creation isn't
automatically handled for you (i.e. you have to write code to instantiate the
dependencies manually).
Dependency injection (DI) at it's core is about creating loosely coupled code by
separating construction logic from application logic. This is done by pushing
the creation of services (dependencies) to the entry point(s) and writing the
application logic so that dependencies are provided for its components. The
application logic doesn't know or care how it is supplied with its dependencies;
it just requires them and therefore receives them.
# OVERVIEW
Beam::Wire loads a configuration [file](https://metacpan.org/pod/file) and stores the specified configuration
in the [config](https://metacpan.org/pod/config attribute) which is used to resolve it's services. This section
will give you an overview of how to declare dependencies and services, and shape
your configuration file.
## WHAT IS A DEPENDENCY?
A dependency is a declaration of a component requirement. In layman's terms, a
dependency is a class attribute (or any value required for class construction)
which will likely be used to define services.
## WHAT IS A SERVICE?
A service is a resolvable interface which may be selected and implemented on
behalf of a dependent component, or instantiated and returned per request. In
layman's terms, a service is a class configuration which can be used
independently or as a dependent of other services.
## HOW ARE SERVICES CONFIGURED?
# databases.yml
production_db:
class: 'DBI'
method: connect
args:
- 'dbi:mysql:master'
- { PrintError: 0, RaiseError: 0 }
production_cache:
class: 'CHI'
args:
driver: 'DBI'
dbh: { $ref: 'production_db' }
development_db:
class: 'DBI'
method: connect
args:
- 'dbi:mysql:slave'
- { PrintError: 1, RaiseError: 1 }
development_cache:
class: 'CHI'
args:
driver: 'DBI'
dbh: { $ref: 'development_db' }
### Service Attributes
#### class
The class to instantiate. The class will be loaded and the `method` (below)
method called.
#### method
The class method to call to construct the object. Defaults to `new`.
If multiple methods are needed to initialize an object, `method` can be an
arrayref of hashrefs, like so:
my_service:
class: My::Service
method:
- method: new
args:
foo: bar
- method: set_baz
args:
- Fizz
In this example, first we call `My::Service-`new( foo => "bar" );> to get our
object, then we call `$obj-`set\_baz( "Fizz" );> as a further initialization
step.
To chain methods together, add `return: chain`:
my_service:
class: My::Service
method:
- method: new
args:
foo: bar
- method: set_baz
return: chain
args:
- Fizz
- method: set_buzz
return: chain
args:
- Bork
This example is equivalent to the following code:
my $service = My::Service->new( foo => "bar" )->set_baz( "Fizz" )
->set_buzz( "Bork" );
#### args
The arguments to the `method` method. This can be either an array or a hash,
like so:
# array
dbh:
class: DBI
method: connect
args:
- 'dbi:mysql:dbname'
# hash
cache:
class: CHI
args:
driver: Memory
max_size: 16MB
Using the array of arguments, you can give arrayrefs or hashrefs:
# arrayref of arrayrefs
names:
class: 'Set::CrossProduct'
args:
-
- [ 'Foo', 'Barkowictz' ]
- [ 'Bar', 'Foosmith' ]
- [ 'Baz', 'Bazleton' ]
# arrayrefs of hashrefs
cache:
class: CHI
args:
- driver: Memory
max_size: 16MB
#### extends
Inherit and override attributes from another service.
dbh:
class: DBI
method: connect
args:
- 'dbi:mysql:dbname'
dbh_dev:
extends: 'dbh'
args:
- 'dbi:mysql:devdb'
Hash `args` will be merged seperately, like so:
activemq:
class: My::ActiveMQ
args:
host: example.com
port: 61312
user: root
password: 12345
activemq_dev:
extends: 'activemq'
args:
host: dev.example.com
`activemq_dev` will get the `port`, `user`, and `password` arguments
from the base service `activemq`.
#### with
Compose roles into the service object.
app:
class: My::App
with: My::FeatureRole
otherapp:
class: My::App
with:
- My::FeatureRole
- My::OtherFeatureRole
This lets you break features out into roles, and compose those roles a la carte
on the fly. If you have 20 different optional features, it is difficult to create
every possible combination of them. So, `with` allows you to pick the features
you want.
#### lifecycle
Control how your service is created. The default value, `singleton`, will cache
the resulting service and return it for every call to `get()`. The other
value, `factory`, will create a new instance of the service every time:
today:
class: DateTime
method: today
lifecycle: factory
args:
time_zone: US/Chicago
report_yesterday:
class: My::Report
args:
date: { $ref: today, $method: add, $args: [ "days", "-1" ] }
report_today:
class: My::Report
args:
date: { $ref: today }
`DateTime-`add> modifies the object and returns the newly-modified object (to
allow for method chaining.) Without `lifecycle: factory`, the `today` service
would become yesterday, making it hard to know what `report_today` would
report on.
An `eager` value will be created as soon as the container is created. If you
have an object that registers itself upon instantiation, you can make sure your
object is created as soon as possible by doing `lifecycle: eager`.
#### on
Attach event listeners using [Beam::Emitter](https://metacpan.org/pod/Beam::Emitter).
emitter:
class: My::Emitter
on:
before_my_event:
$ref: listener
$sub: on_before_my_event
my_event:
- $ref: listener
$sub: on_my_event
- $ref: other_listener
$sub: on_my_event
listener:
class: My::Listener
other_listener:
class: My::Listener
Now, when the `emitter` fires off its events, they are dispatched to the
appropriate listeners.
In order to work around a bug in YAML.pm, you can also specify event listeners
as an array of hashes:
emitter:
class: My::Emitter
on:
- before_my_event:
$ref: listener
$sub: on_before_my_event
- my_event:
$ref: listener
$sub: on_my_event
- my_event:
$ref: other_listener
$sub: on_my_event
### Config Services
A config service allows you to read a config file and use it as a service, giving
all or part of it to other objects in your container.
To create a config service, use the `config` key. The value is the path to the
file to read. By default, YAML, JSON, XML, and Perl files are supported (via
[Config::Any](https://metacpan.org/pod/Config::Any)).
# db_config.yml
dsn: 'dbi:mysql:dbname'
user: 'mysql'
pass: '12345'
# container.yml
db_config:
config: db_config.yml
You can pass in the entire config to an object using `$ref`:
# container.yml
db_config:
config: db_config.yml
dbobj:
class: My::DB
args:
conf:
$ref: db_config
If you only need the config file once, you can create an anonymous config
object.
# container.yml
dbobj:
class: My::DB
args:
conf:
$config: db_config.yml
The config file can be used as all the arguments to the service:
# container.yml
dbobj:
class: My::DB
args:
$config: db_config.yml
In this example, the constructor will be called like:
my $dbobj = My::DB->new(
dsn => 'dbi:mysql:dbname',
user => 'mysql',
pass => '12345',
);
You can reference individual items in a configuration hash using `$path`
references:
# container.yml
db_config:
config: db_config.yml
dbh:
class: DBI
method: connect
args:
- $ref: db_config
$path: /dsn
- $ref: db_config
$path: /user
- $ref: db_config
$path: /pass
**NOTE:** You cannot use `$path` and anonymous config objects.
### Inner Containers
Beam::Wire objects can hold other Beam::Wire objects!
inner:
class: Beam::Wire
args:
config:
dbh:
class: DBI
method: connect
args:
- 'dbi:mysql:dbname'
cache:
class: CHI
args:
driver: Memory
max_size: 16MB
Inner containers' contents can be reached from outer containers by separating
the names with a slash character:
my $dbh = $wire->get( 'inner/dbh' );
### Inner Files
inner:
class: Beam::Wire
args:
file: inner.yml
Inner containers can be created by reading files just like the main container.
If the `file` attribute is relative, the parent's `dir` attribute will be
added:
# share/parent.yml
inner:
class: Beam::Wire
args:
file: inner.yml
# share/inner.yml
dbh:
class: DBI
method: connect
args:
- 'dbi:sqlite:data.db'
# myscript.pl
use Beam::Wire;
my $container = Beam::Wire->new(
file => 'share/parent.yml',
);
my $dbh = $container->get( 'inner/dbh' );
If more control is needed, you can set the [dir](https://metacpan.org/pod/dir attribute) on the parent
container. If even more control is needed, you can make a subclass of Beam::Wire.
### Service/Configuration References
chi:
class: CHI
args:
driver: 'DBI'
dbh: { $ref: 'dbh' }
dbh:
class: DBI
method: connect
args:
- { $ref: dsn }
- { $ref: usr }
- { $ref: pwd }
dsn:
value: "dbi:SQLite:memory:"
usr:
value: "admin"
pwd:
value: "s3cret"
The reuse of service and configuration containers as arguments for other
services is encouraged so we have provided a means of referencing those objects
within your configuration. A reference is an arugment (a service argument) in
the form of a hashref with a `$ref` key whose value is the name of another
service. Optionally, this hashref may contain a `$path` key whose value is a
[Data::DPath](https://metacpan.org/pod/Data::DPath) search string which should return the found data
structure from within the referenced service.
It is also possible to use raw-values as services, this is done by configuring a
service using a single key/value pair with a `value` key whose value contains
the raw-value you wish to reuse.
# ATTRIBUTES
## file
The file attribute contains the file path of the file where Beam::Wire container
services are configured (typically a YAML file). The file's contents should form
a single hashref. The keys will become the service names.
## dir
The dir attribute contains the directory path to use when searching for inner
container files. Defaults to the directory which contains the file specified by
the [file](https://metacpan.org/pod/file attribute).
## config
The config attribute contains a hashref of service configurations. This data is
loaded by [Config::Any](https://metacpan.org/pod/Config::Any) using the file specified by the
[file](https://metacpan.org/pod/file attribute).
## services
A hashref of services. If you have any services already built, add them here.
## meta\_prefix
The character that begins a meta-property inside of a service's `args`. This
includes `$ref`, `$path`, `$method`, and etc...
The default value is '$'. The empty string is allowed.
# METHODS
## get( name, \[ overrides \] )
The get method resolves and returns the service named `name`.
`overrides` may be a list of name-value pairs. If specified, get()
will create an anonymous service that extends the `name` service
with the given config overrides:
# test.pl
use Beam::Wire;
my $wire = Beam::Wire->new(
config => {
foo => {
args => {
text => 'Hello, World!',
},
},
},
);
my $foo = $wire->get( 'foo', args => { text => 'Hello, Chicago!' } );
print $foo; # prints "Hello, Chicago!"
This allows you to create factories out of any service, overriding service
configuration at run-time.
## set
The set method configures and stores the specified service.
## get\_config
Get the config with the given name, searching inner containers if required
## new
Create a new container.
# EXCEPTIONS
If there is an error internal to Beam::Wire, an exception will be thrown. If there is an
error with creating a service or calling a method, the exception thrown will be passed-
through unaltered.
## Beam::Wire::Exception
The base exception class
## Beam::Wire::Exception::Constructor
An exception creating a Beam::Wire object
## Beam::Wire::Exception::Config
An exception loading the configuration file.
## Beam::Wire::Exception::Service
An exception with service information inside
## Beam::Wire::Exception::NotFound
The requested service or configuration was not found.
## Beam::Wire::Exception::InvalidConfig
The configuration is invalid:
- Both "value" and "class" or "extends" are defined. These are mutually-exclusive.
# AUTHORS
- Doug Bell
- Al Newkirk
# CONTRIBUTORS
- Bruce Armstrong
- Bruce Armstrong
- Kent Fredric
# COPYRIGHT AND LICENSE
This software is copyright (c) 2015 by Doug Bell.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.