package Bread::Board::Service::WithDependencies; our $AUTHORITY = 'cpan:STEVAN'; # ABSTRACT: Services with dependencies $Bread::Board::Service::WithDependencies::VERSION = '0.37'; use Moose::Role; use Try::Tiny; use Bread::Board::Types; use Bread::Board::Service::Deferred; use Bread::Board::Service::Deferred::Thunk; with 'Bread::Board::Service'; has 'dependencies' => ( traits => [ 'Hash', 'Clone' ], is => 'rw', isa => 'Bread::Board::Service::Dependencies', lazy => 1, coerce => 1, default => sub { +{} }, trigger => sub { my $self = shift; $_->parent($self) foreach values %{$self->dependencies}; }, handles => { 'add_dependency' => 'set', 'get_dependency' => 'get', 'has_dependency' => 'exists', 'has_dependencies' => 'count', 'get_all_dependencies' => 'kv', } ); around 'init_params' => sub { my $next = shift; my $self = shift; +{ %{ $self->$next() }, $self->resolve_dependencies } }; after 'get' => sub { (shift)->clear_params }; sub resolve_dependencies { my $self = shift; my %deps; if ($self->has_dependencies) { foreach my $dep ($self->get_all_dependencies) { my ($key, $dependency) = @$dep; my $service = $dependency->service; # NOTE: # this is what checks for # circular dependencies if ($service->is_locked) { confess "You cannot defer a parameterized service" if $service->does('Bread::Board::Service::WithParameters') && $service->has_parameters; $deps{$key} = Bread::Board::Service::Deferred->new(service => $service); } else { # since we can't pass in parameters here, # we return a deferred thunk and you can do # with it what you will. if ( $service->does('Bread::Board::Service::WithParameters') && $service->has_required_parameters && (not $service->has_parameter_defaults) && (not $dependency->has_service_params) ) { $deps{$key} = Bread::Board::Service::Deferred::Thunk->new( thunk => sub { my %params = @_; $service->lock; return try { $service->get( %params ) } finally { $service->unlock } catch { die $_ } } ); } else { $service->lock; try { $deps{$key} = $dependency->has_service_params ? $service->get( %{ $dependency->service_params }) : $service->get; } finally { $service->unlock } catch { die $_ }; } } } } return %deps; } no Moose::Role; 1; __END__ =pod =encoding UTF-8 =head1 NAME Bread::Board::Service::WithDependencies - Services with dependencies =head1 VERSION version 0.37 =head1 DESCRIPTION This is a sub-role of L, for services with dependencies. It provides the mechanism to recursively resolve dependencies. =head1 ATTRIBUTES =head2 C Hashref, constrained by L<< C|Bread::Board::Types/Bread::Board::Service::Dependencies >>. Values must be instances of L, but can be coerced from various other types, see L. =head1 METHODS =head2 C $service->add_dependency(name=>$dep); Adds a new dependency. =head2 C my $dep = $service->get_dependency('name'); Gets a dependency by name. =head2 C if ($service->has_dependency('name')) { ... } Returns true if this service has a dependency with the given name. =head2 C if ($service->has_dependencies) { ... } Returns true if this service has any dependency. =head2 C my %deps = $service->get_all_dependencies; Returns all the dependencies for this service, as a key-value list. =head2 C Builder for the service parameters, augmented to inject all the L into the L<< C|Bread::Board::Service/params >> attribute, so that C can use them. =head2 C I the C method, the L<< C|Bread::Board::Service/params >> attribute is cleared, to make sure that dependencies will be resolved again on the next call (of course, if the service is using a L, the whole "getting" only happens once). =head2 C my %name_object_map = $self->resolve_dependencies; For each element of L, calls its L<< C|Bread::Board::Dependency/service >> method to retrieve the service we're dependent on, then tries to instantiate the value of the service. This can happen in a few different ways: =over 4 =item the service is not locked, and does not require any parameter just call C on it =item the service is not locked, requires parameters, but the dependency has values for them call C<< $service->get(%{$dependency->service_params}) >> =item the service is not locked, requires parameters, and we don't have values for them we can't instantiate anything at this point, so we use a L instance, on which you can call the C method, passing it all the needed parameters, to get the actual instance =item the service is locked we return a L that will proxy to the instance that the service will eventually return; yes, this means that in many cases circular dependencies can be resolved, at the cost of a proxy object =back =head1 AUTHOR Stevan Little =head1 BUGS Please report any bugs or feature requests on the bugtracker website https://github.com/stevan/BreadBoard/issues When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature. =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2019, 2017, 2016, 2015, 2014, 2013, 2011, 2009 by Infinity Interactive. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut