package IOC::Service::ConstructorInjection; use strict; use warnings; our $VERSION = '0.01'; use IOC::Exceptions; use base 'IOC::Service'; sub new { my ($_class, $name, $component_class, $component_constructor, $parameters, $container) = @_; my $class = ref($_class) || $_class; my $service = {}; bless($service, $class); $service->_init($name, $component_class, $component_constructor, $parameters, $container); return $service; } sub _init { my ($self, $name, $component_class, $component_constructor, $parameters, $container) = @_; (defined($component_class) && defined($component_constructor)) || throw IOC::InsufficientArguments "You must provide a class and a constructor method"; (defined($parameters) && ref($parameters) eq 'ARRAY') || throw IOC::InsufficientArguments "You must provide a set of parameters for the constructor as an Array reference"; $self->SUPER::_init( $name, sub { # get the IOC::Container object my $c = shift; eval { no strict 'refs'; # check if there is an entry in the # symbol table for this class already # (meaning it has been loaded) and if # not, then require it %{"${component_class}::"} || require $component_class }; # throw our exception if the class fails to load throw IOC::ClassLoadingError "The class '$component_class' could not be loaded" => $@ if $@; # check to see if the specified # constructor is there my $constructor = $component_class->can($component_constructor); # if it is not, then throw our exception (defined($constructor)) || throw IOC::ConstructorNotFound "The constructor '$component_constructor' could not be found for class '$component_class'"; # now take care of the parameters for (my $i = 0; $i < scalar @{$parameters}; $i++) { # if the parameter is not been blessed # into the pseudo-package ComponentParameter # then we skip it, however ... next unless UNIVERSAL::isa($parameters->[$i], "ComponentParameter"); # if it has been, then we derference it # into the name of the service expected # and use the IOC::Container to get an # instance of that service and replace # that in the parameters array $parameters->[$i] = $c->get(${$parameters->[$i]}); } # now we have the class loaded, # the constructor confirmed, and # the parameters realized, so we # can create the instance now return $component_class->$constructor(@{$parameters}); }, $container ); } # class method sub ComponentParameter { shift; bless(\(my $param = shift), "ComponentParameter") }; 1; __END__ =head1 NAME IOC::Service::ConstructorInjection - An IOC Service object which uses Constructor Injection =head1 SYNOPSIS use IOC::Service::ConstructorInjection; # this will call : # FileLogger->new() # when it creates a logger # component instance my $service = IOC::Service::ConstructorInjection->new('logger' => ('FileLogger', 'new', [])); # this will call : # FileLogger->new($container->get('log_file'), "some other argument") # when it creates a logger # component instance my $service = IOC::Service::ConstructorInjection->new('logger' => ( 'FileLogger', 'new', [ IOC::Service::ConstructorInjection->ComponentParameter('log_file'), "some other argument" ])); =head1 DESCRIPTION In this IOC framework, the IOC::Service::ConstructorInjection object holds instances of components to be managed. =head1 METHODS =over 4 =item B Creates a service with a C<$name>, and uses the C<$component_class> and C<$component_constructor> string arguments to initialize the service on demand. An optional fourth argument is the C<$container> which tells the service which container is belongs in. This can also be set with the C method. If the C<$component_class> and C<$component_constructor> arguments are not defined, an B exception will be thrown. Upon request of the component managed by this service, an attempt will be made to load the C<$component_class>. If that loading fails, an B exception will be thrown with the details of the underlying error. If the C<$component_class> loads successfully, then it will be inspected for an available C<$component_constructor> method. If the C<$component_constructor> method is not found, an B exception will be thrown. If the C<$component_constructor> method is found, then it will be called with the values found in C<$paramaters>. However, before C<$paramaters> are passed, they are first looped through looking for any "ComponentParameters" (these are place holders created with the class method C (see below)), and replaces these items with the proper values extracted from the IOC framework. =back =head1 CLASS METHODS =over 4 =item B Given a C<$component_name> this will create a place holder suitable for placement in the C<$parameters> argument of the C method. The C<$component_name> must be the name of a valid service within the container of the requesting service. =back =head1 TO DO =head1 BUGS None that I am aware of. Of course, if you find a bug, let me know, and I will be sure to fix it. =head1 CODE COVERAGE I use B to test the code coverage of my tests, see the CODE COVERAGE section of L for more information. =head1 SEE ALSO =over 4 =item Constructor Injection in the PicoContainer is explained on this page L =back =head1 AUTHOR stevan little, Estevan@iinteractive.comE =head1 COPYRIGHT AND LICENSE Copyright 2004 by Infinity Interactive, Inc. L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut