preaction / Beam-Wire

A simple configuration and dependency injection container
Other
11 stars 9 forks source link

NAME

Beam::Wire - Lightweight Dependency Injection Container

VERSION

version 1.025

STATUS

Coverage Status

SYNOPSIS

# wire.yml
captain:
    class: Person
    args:
        name: Malcolm Reynolds
        rank: Captain
first_officer:
    $class: Person
    name: Zoë Alleyne Washburne
    rank: Commander

# script.pl
use Beam::Wire;
my $wire = Beam::Wire->new( file => 'wire.yml' );
my $captain = $wire->get( 'captain' );
print $captain->name; # "Malcolm Reynolds"

DESCRIPTION

Beam::Wire is a configuration module and a dependency injection container. In addition to complex data structures, Beam::Wire configures and creates plain old Perl objects.

A dependency injection (DI) container creates an inversion of control: Instead of manually creating all the dependent objects (also called "services") before creating the main object that we actually want, a DI container handles that for us: We describe the relationships between objects, and the objects get built as needed.

Dependency injection is sometimes called the opposite of garbage collection. Rather than ensure objects are destroyed in the right order, dependency injection makes sure objects are created in the right order.

Using Beam::Wire in your application brings great flexibility, allowing users to easily add their own code to customize how your project behaves.

For an introduction to the Beam::Wire service configuration format, see Beam::Wire::Help::Config.

ATTRIBUTES

file

The path of the file where services are configured (typically a YAML file). The file's contents should be a single hashref. The keys are service names, and the values are service configurations.

dir

The directory path to use when searching for inner container files. Defaults to the directory which contains the file specified by the file attribute.

config

The raw configuration data. By default, this data is loaded by Config::Any using the file specified by the file attribute.

See Beam::Wire::Help::Config for details on what the configuration data structure looks like.

If you don't want to load a file, you can specify this attribute in the Beam::Wire constructor.

services

A hashref of cached services built from the configuration. If you want to inject a pre-built object for other services to depend on, add it 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

my $service = $wire->get( $name );
my $service = $wire->get( $name, %overrides )

The get method resolves and returns the service named $name, creating it, if necessary, with the create_service method.

%overrides is an optional list of name-value pairs. If specified, get() will create an new, anonymous service that extends the named service with the given config overrides. For example:

# 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.

If $name contains a slash (/) character (e.g. foo/bar), the left side (foo) will be used as the name of an inner container, and the right side (bar) is a service inside that container. For example, these two lines are equivalent:

$bar = $wire->get( 'foo/bar' );
$bar = $wire->get( 'foo' )->get( 'bar' );

Inner containers can be nested as deeply as desired (foo/bar/baz/fuzz).

set

$wire->set( $name => $service );

The set method configures and stores the specified $service with the specified $name. Use this to add or replace built services.

Like the get() method, above, $name can contain a slash (/) character to traverse through nested containers.

get_config

my $conf = $wire->get_config( $name );

Get the config with the given $name. Like the get() method, above, $name can contain slash (/) characters to traverse through nested containers.

normalize_config

my $out_conf = $self->normalize_config( $in_conf );

Normalize the given $in_conf into to hash that the create_service method expects. This method allows a service to be defined with prefixed meta-names ($class instead of class) and the arguments specified without prefixes.

For example, these two services are identical.

foo:
    class: Foo
    args:
        fizz: buzz

foo:
    $class: Foo
    fizz: buzz

The $in_conf must be a hash, and must already pass an is_meta check.

create_service

my $service = $wire->create_service( $name, %config );

Create the service with the given $name and %config. Config can contain the following keys:

This method uses the parse_args method to parse the args key, resolving references as needed.

merge_config

my %merged = $wire->merge_config( %config );

If %config contains an extends key, merge the extended config together with this one, returning the merged service configuration. This works recursively, so a service can extend a service that extends another service just fine.

When merging, hashes are combined, with the child configuration taking precedence. The args key is handled specially to allow a hash of args to be merged.

The configuration returned is a safe copy and can be modified without effecting the original config.

parse_args

my @args = $wire->parse_args( $for_name, $class, $args );

Parse the arguments ($args) for the given service ($for_name) with the given class ($class).

$args can be an array reference, a hash reference, or a simple scalar. The arguments will be searched for references using the find_refs method, and then a list of arguments will be returned, ready to pass to the object's constructor.

Nested containers are handled specially by this method: Their inner references are not resolved by the parent container. This ensures that references are always relative to the container they're in.

find_refs

my @resolved = $wire->find_refs( $for_name, @args );

Go through the @args and recursively resolve any references and services found inside, returning the resolved result. References are identified with the is_meta method.

If a reference contains a $ref key, it will be resolved by the resolve_ref method. Otherwise, the reference will be treated as an anonymous service, and passed directly to the create_service method.

This is used when creating a service to ensure all dependencies are created first.

is_meta

my $is_meta = $wire->is_meta( $ref_hash, $root );

Returns true if the given hash reference describes some kind of Beam::Wire service. This is used to identify service configuration hashes inside of larger data structures.

A service hash reference must contain at least one key, and must either contain a prefixed key that could create or reference an object (one of class, extends, config, value, or ref) or, if the $root flag exists, be made completely of unprefixed meta keys (as returned by the get_meta_names method).

The $root flag is used by the get method to allow unprefixed meta keys in the top-level hash values.

get_meta_names

my %meta_keys = $wire->get_meta_names;

Get all the possible service keys with the meta prefix already attached.

resolve_ref

my @value = $wire->resolve_ref( $for_name, $ref_hash );

Resolves the given dependency from the configuration hash ($ref_hash) for the named service ($for_name). Reference hashes contain the following keys:

fix_refs

my @fixed = $wire->fix_refs( $for_container_name, @args );

Similar to the find_refs method. This method searches through the @args and recursively fixes any reference paths to be absolute. References are identified with the is_meta method.

This is used by the get_config method to ensure that the configuration can be passed directly in to the create_service method.

new

my $wire = Beam::Wire->new( %attributes );

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:

EVENTS

The container emits the following events.

configure_service

This event is emitted when a new service is configured, but before it is instantiated or any classes loaded. This allows altering of the configuration before the service is built. Already-built services will not fire this event.

Event handlers get a Beam::Wire::Event::ConfigService object as their only argument.

This event will bubble up from child containers.

build_service

This event is emitted when a new service is built. Cached services will not fire this event.

Event handlers get a Beam::Wire::Event::BuildService object as their only argument.

This event will bubble up from child containers.

ENVIRONMENT VARIABLES

AUTHORS

CONTRIBUTORS

COPYRIGHT AND LICENSE

This software is copyright (c) 2018-2021 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.