Completed
Pushe95b0b...773145
passed — Build
created

FactoryResolver::resolve()   B

↳ Parent: FactoryResolver

Complexity

Conditions 4
Paths 6

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 27
Code Lines 18

Importance

Changes 6
Bugs 0 Features 0
Metric Value
c 6
b 0
f 0
dl 0
loc 27
rs 8.5806
cc 4
eloc 18
nc 6
nop 2
1
<?php
2
3
namespace DI\Definition\Resolver;
4
5
use DI\Definition\Definition;
6
use DI\Definition\Exception\DefinitionException;
7
use DI\Definition\FactoryDefinition;
8
use DI\Invoker\FactoryParameterResolver;
9
use Interop\Container\ContainerInterface;
10
use Invoker\Exception\NotCallableException;
11
use Invoker\Exception\NotEnoughParametersException;
12
use Invoker\Invoker;
13
use Invoker\ParameterResolver\NumericArrayResolver;
14
use Invoker\ParameterResolver\ResolverChain;
15
16
/**
17
 * Resolves a factory definition to a value.
18
 *
19
 * @since 4.0
20
 * @author Matthieu Napoli <[email protected]>
21
 */
22
class FactoryResolver implements DefinitionResolver
23
{
24
    /**
25
     * @var ContainerInterface
26
     */
27
    private $container;
28
29
    /**
30
     * @var Invoker|null
31
     */
32
    private $invoker;
33
34
    /**
35
     * The resolver needs a container. This container will be passed to the factory as a parameter
36
     * so that the factory can access other entries of the container.
37
     *
38
     * @param ContainerInterface $container
39
     */
40
    public function __construct(ContainerInterface $container)
41
    {
42
        $this->container = $container;
43
    }
44
45
    /**
46
     * Resolve a factory definition to a value.
47
     *
48
     * This will call the callable of the definition.
49
     *
50
     * @param FactoryDefinition $definition
51
     *
52
     * [email protected]}
53
     */
54
    public function resolve(Definition $definition, array $parameters = [])
55
    {
56
        if (! $this->invoker) {
57
            $parameterResolver = new ResolverChain([
58
               new FactoryParameterResolver($this->container),
59
               new NumericArrayResolver,
60
            ]);
61
62
            $this->invoker = new Invoker($parameterResolver, $this->container);
63
        }
64
65
        try {
66
            return $this->invoker->call($definition->getCallable(), [$this->container, $definition]);
0 ignored issues
show
Bug introduced by Quim Calpe
It seems like you code against a concrete implementation and not the interface DI\Definition\Definition as the method getCallable() does only exist in the following implementations of said interface: DI\Definition\DecoratorDefinition, DI\Definition\FactoryDefinition.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
67
        } catch (NotCallableException $e) {
68
            throw new DefinitionException(sprintf(
69
                'Entry "%s" cannot be resolved: factory %s',
70
                $definition->getName(),
71
                $e->getMessage()
72
            ));
73
        } catch (NotEnoughParametersException $e) {
74
            throw new DefinitionException(sprintf(
75
                'Entry "%s" cannot be resolved: %s',
76
                $definition->getName(),
77
                $e->getMessage()
78
            ));
79
        }
80
    }
81
82
    /**
83
     * [email protected]}
84
     */
85
    public function isResolvable(Definition $definition, array $parameters = [])
86
    {
87
        return true;
88
    }
89
}
90