Beam::Wire - A Dependency Injection Container
version 1.000
# 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' ) );
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.
Beam::Wire loads a configuration file and stores the specified configuration in the 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.
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.
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.
# 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' }
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.
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.
My::Service-
$obj-
To chain methods together, add return: chain:
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" );
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
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:
args
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.
activemq_dev
port
user
password
activemq
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:
singleton
get()
factory
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.
DateTime-
lifecycle: factory
today
report_today
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.
eager
lifecycle: eager
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: 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:
file
dir
# 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 attribute on the parent container. If even more control is needed, you can make a subclass of Beam::Wire.
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 search string which should return the found data structure from within the referenced service.
$ref
$path
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.
value
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.
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 attribute.
The config attribute contains a hashref of service configurations. This data is loaded by Config::Any using the file specified by the file attribute.
A hashref of services. If you have any services already built, add them here.
The character that begins a meta-property inside of a service's args. This includes $ref, $path, $method, and etc...
$method
The default value is '$'. The empty string is allowed.
The get method resolves and returns the service named name.
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:
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.
The set method configures and stores the specified service.
Get the config with the given name, searching inner containers if required
Create a new container.
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.
The base exception class
An exception with service information inside
The requested service or configuration was not found.
The configuration is invalid:
Both "value" and "class" or "extends" are defined. These are mutually-exclusive.
Doug Bell <preaction@cpan.org>
Al Newkirk <anewkirk@ana.io>
This software is copyright (c) 2013 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.
To install Beam::Wire, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Beam::Wire
CPAN shell
perl -MCPAN -e shell install Beam::Wire
For more information on module installation, please visit the detailed CPAN module installation guide.