Peco::Container - Light Inversion of Control (IoC) container
my $c = Peco::Container->new;
$c->reg( 'My::Class', undef, { foo => 'other' } );
$c->reg( 'my_key', 'My::Class' );
$c->register( 'my_key', 'My::Class', [ @deps ] );
$c->register( 'my_key', 'My::Class', [ @deps ], 'create' );
$c->register( 'my_key', 'My::Class', [ @deps ], 'create', { %attrs } );
$c->register( my_key => 'My::Class', \%deps, 'create', { %attrs } );
$c->register( 'my_key', 'My::Class', undef, 'create' );
$c->register( 'my_key', 'My::Class', undef, 'create', { %attrs } );
$c->register( 'my_key', 'My::Class', [ @deps ], undef, { %attrs } );
my $instance = $c->service('my_key');
my @instances = $c->services();
$c->contains('my_key') ? 1 : 0;
$c->is_empty ? 1 : 0;
$c->multicast( 'method', @args );
Peco::Container is a small, flexible Inversion of Control (IoC) container supporting both Constructor Injection and Setter Injection patterns, as well prototype services (factories) and multicasting.
Inversion of Control is simply a way of delegating object construction, initialisation and location, for a given system, to a framework which takes care of the details for you.
This is done by abstraction into two kinds of objects, a Container, which acts as both registry and locator, and a Service which acts as specifier and wrapper for the object/service which is registered with the container.
The easiest way to understand this is to look at a couple of simple examples.
If we were to have the following logger class which takes an IO::File object has an argument to the constructor:
package My::Logger;
sub new {
my ( $class, $handle ) = @_;
bless {
handle => $handle,
}, $class;
}
sub log { shift->{handle}->print( @_ ) }
We can see that we need to create the file handle before the logger object is created, so the $handle
is a dependency of the logger object. But looking at the documentation for IO::File we see that it too needs to have arguments passed to its constructor, the filename and the mode, so these are its dependencies which we need to inject. With Peco we would describe this dependency hierarchy as follows:
my $c = Peco::Container->new;
$c->register('log_mode', O_APPEND);
$c->register('log_file', '/var/log/my-app.log');
$c->register('log_fh', 'IO::File', ['log_file', 'log_mode']);
$c->register('my_logger', 'My::Logger', ['log_fh']);
Now when we say:
my $logger = $c->service( 'my_logger' );
the dependencies are automatically (and recursively) resolved and a logger instance is handed to us with an opened file handle in the correct state for logging.
The fourth argument to register is a string representing the method name of the constructor to call when instantiating the object. This defaults to 'new' if undefined. To specify an alternative, we can say:
$c->register('log_fh', 'IO::File', ['log_file'], 'new_tmpfile');
A hash reference can be passed as the fifth (and final) parameter to register which will be used to set up fields in the instance where the keys map to the name of the setter and the values to the parameters.
For example, assuming we have a setter in the My::Logger package for setting the log level, called level, then the following specification:
$c->register('my_logger', 'My::Logger', ['log_fh'], undef, { level => 3 });
will effectively call:
$logger->level( 3 );
setting the logging level to '3'.
Ordinarily when calling $container->service( 'something' ), the instance is only created the first time 'service' is called. On subsequent calls, the same instance is returned. Therefore, these instances are basically singletons in the context of the container and are only destroyed when the container is destroyed. However, there are two exceptions:
Constant services are simple scalars or references which are registered with a container, and therefore are never constructed by the container. This follows when the second parameter to register is either a reference (blessed or otherwise), or a simple scalar value. For example:
$c->register('log_file', '/var/log/my-app.log');
Sometimes it is useful to be able to generate a new instance every time the $container->service( 'something' ) method is called. This can be done by passing a code reference as the second parameter to register:
$c->register('log_fh', \&mk_log_fh_pid, [ 'log_file', 'log_mode' ]);
sub mk_log_fh_pid {
my ( $path, $mode ) = @_;
return IO::File->new( "$path-$$", $mode );
}
- register( $key, $class )
- register( $key, $class, \@depends )
- register( $key, $class, \@depends, $create )
- register( $key, $class, \@depends, $create, \%attrs )
- register( $key, $coderef )
- register( $key, $ref_or_object )
-
Register a service with the container identified by $key. The key must be unique as the container will croak() if you try to register twice with the same key.
$class is either a string representing the class name or a code reference or another scalar value. If it is a classname, then a Peco::Spec::Class specifier is created. If it is a code reference, then a Peco::Spec::Code specifier is created. For any other scalar a Peco::Spec::Const specifier is created.
\@depends is an optional array reference of keys which will be resolved and passed to the $class's constructor in the order specified.
$create is an optional string which is the name of the constructor subroutine to call. The default value is: new.
$\%attrs is an optional hash reference of 'setter' => 'value' pairs which is used for setter injection. This is only meaningful where $class is a class and not a scalar.
- service( $key )
-
Returning an instance of $class, resolving dependencies, and constructing it as required, unless $class is a code reference, in which case the code reference is executed instead.
- services
-
Returns instances for all service specifiers registered with this container. Services which have not yet been resolved and constructed are done as a side effect.
- contains( $key )
-
Returns a true value (actually a reference to the Peco::Spec object) if this container has a service registered for $key.
- count
-
Returns the number of service specifiers registered with this container.
- is_empty
-
Returns a true value if there are no service specifiers registered with this container.
- multicast( $method, @args )
-
Attempt to call $method on each instance. The method, if found (via UNIVERSAL::can) is called with the container as first parameter followed by @args.
Services which have not yet been resolved and constructed are done as a side effect.
- dependencies( $key )
-
Returns the \@depends array reference passed to the service specified by $key or an empty array reference if none was given.
Peco::Spec, IOC::Container, http://www.picocontainer.org
Most of this code is ported from Rico, which is a Ruby implementation of PicoContainer... which is Java
Richard Hundt
This program is free software and may be modified and distributed under the same terms as Perl itself.