| IOC documentation | Contained in the IOC distribution. |
IOC::Container - An IOC Container object
use IOC::Container;
my $container = IOC::Container->new();
$container->register(IOC::Service::Literal->new('log_file' => "logfile.log"));
$container->register(IOC::Service->new('logger' => sub {
my $c = shift;
return FileLogger->new($c->get('log_file'));
}));
$container->register(IOC::Service->new('application' => sub {
my $c = shift;
my $app = Application->new();
$app->logger($c->get('logger'));
return $app;
}));
$container->get('application')->run();
# or a more complex example
# utilizing a tree-like structure
# of services
my $logging = IOC::Container->new('logging');
$logging->register(IOC::Service->new('logger' => sub {
my $c = shift;
return My::FileLogger->new($c->find('/filesystem/filemanager')->openFile($c->get('log_file')));
}));
$logging->register(IOC::Service::Literal->new('log_file' => '/var/my_app.log'));
my $database = IOC::Container->new('database');
$database->register(IOC::Service->new('connection' => sub {
my $c = shift;
return My::DB->connect($c->get('dsn'), $c->get('username'), $c->get('password'));
}));
$database->register(IOC::Service::Literal->new('dsn' => 'dbi:mysql:my_app'));
$database->register(IOC::Service::Literal->new('username' => 'test'));
$database->register(IOC::Service::Literal->new('password' => 'secret_test'));
my $file_system = IOC::Container->new('filesystem');
$file_system->register(IOC::Service->new('filemanager' => sub { return My::FileManager->new() }));
my $container = IOC::Container->new();
$container->addSubContainers($file_system, $database, $logging);
$container->register(IOC::Service->new('application' => sub {
my $c = shift;
my $app = My::Application->new();
$app->logger($c->find('/logging/logger'));
$app->db_connection($c->find('/database/connection'));
return $app;
}));
$container->get('application')->run();
In this IOC framework, the IOC::Container object holds instances of IOC::Service objects keyed by strings. It can also have sub-containers, which are instances of IOC::Container objects also keyed by string.
+------------------+
| IOC::Container |
+---------+--------+
|
+------------------+-----------------+
| | |
(*sub-containers) (*proxies) (*services)
| | |
V V V
+------------------+ +--------------+ +--------------+
| IOC::Container | | IOC::Proxy | | IOC::Service |
+------------------+ +--------------+ +--------------+
|
(instance)
|
V
+-------------------------+
| <Your Component/Object> |
+-------------------------+
A container can be named with the optional $container_name argument, otherwise the container will have the name 'default'.
This will return the name of the container.
Given a $service, this will register the $service as part of this container. The value returned by the name method of the $service object is as the key where this service is stored. This also will call setContainer on the $service and pass in it's own instance.
If $service is not an instance of IOC::Service, or a subclass of it, an IOC::InsufficientArguments exception will be thrown.
If the name of $service already exists, then a IOC::ServiceAlreadyExists exception is thrown.
Given a $name this will remove the service from the container. If there is no service by that $name, then a IOC::ServiceNotFound exception is thrown.
Same as register but also registers a $proxy object to wrap the $service object with.
Adds a $proxy object to wrap the service at $name.
Given a $name this will return the service instance that name corresponds to, if $name is not defined, an exception is thrown.
If there is no service by that $name, then a IOC::ServiceNotFound exception is thrown.
NOTE: If the requested service is currently locked (meaning it is being created), then a deferred service stub is returned. This will allow for cyclical dependencies to work.
Given a $path to a service, this method will attempt to locate that service. It utilizes the IOC::Visitor::ServiceLocator to do this.
Returns a list of all the named services available.
Get the parent container associated with this instance. If there is no container, undef is returned.
Given a $container, this will associate it as the invocant's parent. If the $container is not an instance of IOC::Container (or a subclass of it), an IOC::InsufficientArguments exception will be thrown.
If the invocant does not have a parent, then it is considered a root container and this method will return true (1), otherwise it will return false (0).
This will climb back up the container hierarchy and find the root of the container tree.
Adds a $container to it's keyed list of sub-containers. This has the effect of making the invocant the parent of $container. If $container is not a IOC::Container object (or a subclass of it), then an IOC::InsufficientArguments exception is thrown. If the name of $container is a duplicate of one already stored, then a IOC::ContainerAlreadyExists exception is thrown.
This just loops calling addSubContainer on each of the items in @containers.
This will return true (1) if the invocant has sub-containers, and false (0) otherwise.
This will return a list of strings which the sub-containers are keyed by.
This will return the sub-container associated with $name. If $name is undefined an IOC::InsufficientArguments exception will be thrown. If no sub-container exists by that $name, then an IOC::ContainerNotFound exception will be thrown.
This will return a list of the actual sub-containers stored. This will be in the same order as the list returned by getSubContainerList.
This method is part of the IOC::Visitable interface. It accepts only $visitor objects which implement the IOC::Visitor interface.
None that I am aware of. Of course, if you find a bug, let me know, and I will be sure to fix it.
I use Devel::Cover to test the code coverage of my tests, see the CODE COVERAGE section of IOC for more information.
stevan little, <stevan@iinteractive.com>
Copyright 2004-2007 by Infinity Interactive, Inc.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| IOC documentation | Contained in the IOC distribution. |
package IOC::Container; use strict; use warnings; our $VERSION = '0.14'; use Scalar::Util qw(blessed); use IOC::Interfaces; use IOC::Exceptions; use IOC::Visitor::ServiceLocator; use base 'IOC::Visitable'; sub new { my ($_class, $name) = @_; my $class = ref($_class) || $_class; my $container = {}; bless($container, $class); $container->_init($name); return $container; } sub _init { my ($self, $name) = @_; $self->{services} = {}; $self->{service_locks} = {}; $self->{proxies} = {}; $self->{sub_containers} = {}; $self->{parent_container} = undef; $self->{name} = $name || 'default'; } sub name { my ($self) = @_; return $self->{name}; } # parent containers sub setParentContainer { my ($self, $parent_container) = @_; (blessed($parent_container) && $parent_container->isa('IOC::Container')) || throw IOC::InsufficientArguments "You must provide an IOC::Container object as a parent container"; $self->{parent_container} = $parent_container; } sub getParentContainer { my ($self) = @_; return $self->{parent_container}; } sub isRootContainer { my ($self) = @_; return defined($self->{parent_container}) ? 0 : 1; } sub findRootContainer { my ($self) = @_; return $self if $self->isRootContainer(); my $current = $self; $current = $current->getParentContainer() until $current->isRootContainer(); return $current; } # sub containers sub addSubContainer { my ($self, $container) = @_; (blessed($container) && $container->isa('IOC::Container')) || throw IOC::InsufficientArguments "You must provide an IOC::Container object as a sub-container"; my $name = $container->name(); (!exists ${$self->{sub_containers}}{$name}) || throw IOC::ContainerAlreadyExists "Duplicate Sub-Container Name '${name}' in container '" . $self->{name} . "'"; $self->{sub_containers}->{$name} = $container; $container->setParentContainer($self); $self; } sub addSubContainers { my ($self, @containers) = @_; (@containers) || throw IOC::InsufficientArguments "You must provide at least one IOC::Container to add"; $self->addSubContainer($_) foreach @containers; $self; } sub hasSubContainer { my ($self, $name) = @_; (defined($name)) || throw IOC::InsufficientArguments "You must supply a name of a sub-container"; return (exists ${$self->{sub_containers}}{$name}) ? 1 : 0; } sub hasSubContainers { my ($self) = @_; return scalar(keys(%{$self->{sub_containers}})) ? 1 : 0; } sub getSubContainerList { my ($self) = @_; return keys %{$self->{sub_containers}}; } sub getSubContainer { my ($self, $name) = @_; (defined($name)) || throw IOC::InsufficientArguments "You must supply a name of a sub-container"; (exists ${$self->{sub_containers}}{$name}) || throw IOC::ContainerNotFound "There is no subcontainer by the name '${name}' in container '" . $self->{name} . "'"; return $self->{sub_containers}->{$name}; } sub getAllSubContainers { my ($self) = @_; return values %{$self->{sub_containers}}; } sub accept { my ($self, $visitor) = @_; (blessed($visitor) && $visitor->isa('IOC::Visitor')) || throw IOC::InsufficientArguments "You must pass an IOC::Visitor object to the visit method"; return $visitor->visit($self); } # services sub register { my ($self, $service) = @_; (blessed($service) && $service->isa('IOC::Service')) || throw IOC::InsufficientArguments "You must provide a valid IOC::Service object to register"; my $name = $service->name(); (!exists ${$self->{services}}{$name}) || throw IOC::ServiceAlreadyExists "Duplicate Service Name '${name}'"; $service->setContainer($self); $self->{services}->{$name} = $service; $self; } sub unregister { my ($self, $name) = @_; (defined($name)) || throw IOC::InsufficientArguments "You must provide a service name to unregister"; (exists ${$self->{services}}{$name}) || throw IOC::ServiceNotFound "Unknown Service '${name}'"; my $service = $self->{services}->{$name}; $service->removeContainer(); delete $self->{services}->{$name}; return $service; } sub registerWithProxy { my ($self, $service, $proxy) = @_; $self->register($service); $self->addProxy($service->name(), $proxy); $self; } sub addProxy { my ($self, $name, $proxy) = @_; (defined($name)) || throw IOC::InsufficientArguments "You must provide a valid service name"; (blessed($proxy) && $proxy->isa('IOC::Proxy')) || throw IOC::InsufficientArguments "You must provide a valid IOC::Proxy object to register"; (exists ${$self->{services}}{$name}) || throw IOC::ServiceNotFound "Unknown Service '${name}'"; $self->{proxies}->{$name} = $proxy; $self; } sub get { my ($self, $name, %params) = @_; (defined($name)) || throw IOC::InsufficientArguments "You must provide a name of the service"; (exists ${$self->{services}}{$name}) || throw IOC::ServiceNotFound "Unknown Service '${name}'"; # a literal object can have no dependencies, # and therefore can have no circular refs, so # we can optimize their usage there as well return $self->{services}->{$name}->instance() if $self->{services}->{$name}->isa('IOC::Service::Literal'); if ($self->_isServiceLocked($name)) { # NOTE: # if the service is parameterized # then we cannot defer it - SL ($self->{services}->{$name}->isa('IOC::Service::Parameterized')) && throw IOC::IllegalOperation "The service '$name' is locked, cannot defer a parameterized instance"; # otherwise ... return $self->{services}->{$name}->deferred(); } $self->_lockService($name); my $instance = $self->{services}->{$name}->instance(%params); $self->_unlockService($name); if (blessed($instance) && ref($instance) !~ /\:\:\_\:\:Proxy$/) { return $self->{proxies}->{$name}->wrap($instance) if exists ${$self->{proxies}}{$name}; } return $instance; } sub find { my ($self, $path, $extra_args) = @_; (defined($path)) || throw IOC::InsufficientArguments "You must provide a path of find a service"; return $self->accept(IOC::Visitor::ServiceLocator->new($path, $extra_args)); } sub hasService { my ($self, $name) = @_; (defined($name)) || throw IOC::InsufficientArguments "You must provide a name of the service"; return (exists ${$self->{services}}{$name}) ? 1 : 0; } sub getServiceList { my ($self) = @_; return keys %{$self->{services}}; } sub DESTROY { my ($self) = @_; # this will not DESTROY all the # sub-containers it holds, since # a sub-container might be still # refered to elsewhere. $self->{sub_containers} = undef; # and the same with the parent $self->{parent_container} = undef; # this will DESTROY all the # services it holds, since # a service can only have one # container, then this is okay # to do that, otherwise we would # need to deal with that. foreach my $service (values %{$self->{services}}) { defined $service && $service->DESTROY; } } # private methods sub _lockService { my ($self, $name) = @_; $self->{lock_level}++; $self->{service_locks}->{$name} = $self->{lock_level}; # use Data::Dumper; # print "locking '$name' -> our locks are: " . Dumper($self->{service_locks}); } sub _unlockService { my ($self, $name) = @_; $self->{lock_level}--; delete $self->{service_locks}->{$name}; } sub _isServiceLocked { my ($self, $name) = @_; return (exists ${$self->{service_locks}}{$name}); } 1; __END__