Completed
Pull Request — master (#7)
by Daniel
05:04
created

ArgumentResolver::validateAgainstExtraArguments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
3
namespace ArgumentResolver;
4
5
use ArgumentResolver\Argument\ArgumentDescription;
6
use ArgumentResolver\Argument\ArgumentDescriptions;
7
use ArgumentResolver\Argument\ArgumentDescriptor;
8
use ArgumentResolver\Exception\ArgumentResolutionException;
9
use ArgumentResolver\Exception\ResolutionException;
10
use ArgumentResolver\Resolution\ConstraintResolver;
11
use ArgumentResolver\Resolution\Resolution;
12
use ArgumentResolver\Resolution\ResolutionConstraint;
13
use ArgumentResolver\Resolution\ResolutionConstraintCollection;
14
use ArgumentResolver\Resolution\Resolutions;
15
16
class ArgumentResolver
17
{
18
    /**
19
     * @var ArgumentDescriptor
20
     */
21
    private $argumentDescriptor;
22
23
    /**
24
     * @var ConstraintResolver
25
     */
26
    private $constraintResolver;
27
28
    /**
29
     * @param ArgumentDescriptor $argumentDescriptor
30
     * @param ConstraintResolver $constraintResolver
31
     */
32
    public function __construct(ArgumentDescriptor $argumentDescriptor, ConstraintResolver $constraintResolver)
33
    {
34
        $this->argumentDescriptor = $argumentDescriptor;
35
        $this->constraintResolver = $constraintResolver;
36
    }
37
38
    /**
39
     * Resolve the arguments needed by the given callable and the order of these
40
     * arguments.
41
     *
42
     * It returns an array with the value of arguments in the right order.
43
     *
44
     * @param mixed $callable
45
     * @param array $availableArguments
46
     *
47
     * @return array
48
     */
49
    public function resolveArguments($callable, array $availableArguments = [])
50
    {
51
        $descriptions = $this->argumentDescriptor->getDescriptions($callable)->sortByPosition();
52
        $constraints = $this->constraintResolver->resolveConstraints($descriptions);
53
54
        $resolutions = new Resolutions();
55
        foreach ($descriptions as $description) {
56
            $resolutions->addCollection(
57
                $this->getArgumentResolutions($constraints, $description, $availableArguments)
58
            );
59
        }
60
61
        $this->addMissingResolutions($resolutions, $descriptions);
62
63
        $arguments = $resolutions->sortByPriority()->toArgumentsArray();
64
65
        $this->validateAgainstExtraArguments($availableArguments, $resolutions);
66
67
        return $arguments;
68
    }
69
70
    /**
71
     * @param ResolutionConstraintCollection $constraints
72
     * @param ArgumentDescription            $description
73
     * @param array                          $availableArguments
74
     *
75
     * @return Resolution[]
76
     */
77
    private function getArgumentResolutions(ResolutionConstraintCollection $constraints, ArgumentDescription $description, array $availableArguments)
78
    {
79
        $resolutions = [];
80
81
        foreach ($availableArguments as $argumentName => $argumentValue) {
82
            $priority = $this->getArgumentPriority($constraints, $description, $argumentName, $argumentValue);
83
84
            if ($priority > 0) {
85
                $resolutions[] = new Resolution($description->getPosition(), $argumentName, $argumentValue, $priority);
86
            }
87
        }
88
89
        return $resolutions;
90
    }
91
92
    /**
93
     * @param ResolutionConstraintCollection $constraints
94
     * @param ArgumentDescription            $description
95
     * @param string                         $argumentName
96
     * @param mixed                          $argumentValue
97
     *
98
     * @return int
99
     */
100
    private function getArgumentPriority(ResolutionConstraintCollection $constraints, ArgumentDescription $description, $argumentName, $argumentValue)
101
    {
102
        $priority = 0;
103
        if ($description->getName() === $argumentName) {
104
            $priority++;
105
        }
106
107
        if ($description->isScalar()) {
108
            return $priority;
109
        }
110
111
        if ($description->getType() === $this->argumentDescriptor->getValueType($argumentValue)) {
112
            $priority += 2;
113
        } elseif ($constraints->hasConstraint(ResolutionConstraint::STRICT_MATCHING, [
114
            'type' => $description->getType(),
115
        ])) {
116
            throw new ResolutionException(sprintf(
117
                'Strict matching for type "%s" can\'t be resolved',
118
                $description->getType()
119
            ));
120
        }
121
122
        return $priority;
123
    }
124
125
    /**
126
     * @param Resolutions          $resolutions
127
     * @param ArgumentDescriptions $descriptions
128
     *
129
     * @throws ResolutionException
130
     */
131
    private function addMissingResolutions(Resolutions $resolutions, ArgumentDescriptions $descriptions)
132
    {
133
        $missingResolutionPositions = $resolutions->getMissingResolutionPositions($descriptions->count());
134
135
        foreach ($missingResolutionPositions as $position) {
136
            $description = $descriptions->getByPosition($position);
137
            if ($description->isRequired()) {
138
                throw new ArgumentResolutionException(
139
                    sprintf(
140
                        'Argument at position %d is required and wasn\'t resolved',
141
                        $description->getPosition()
142
                    ),
143
                    $description
0 ignored issues
show
Bug introduced by
It seems like $description defined by $descriptions->getByPosition($position) on line 136 can be null; however, ArgumentResolver\Excepti...xception::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
144
                );
145
            }
146
147
            $resolutions->add(new Resolution($description->getPosition(), $description->getName(), $description->getDefaultValue(), 0));
148
        }
149
    }
150
151
    private function validateAgainstExtraArguments(array $availableArguments, Resolutions $resolutions)
152
    {
153
        $unrecognisedArguments = array_diff(array_keys($availableArguments), $resolutions->argumentNames());
154
        if (count($unrecognisedArguments) > 1) {
155
            throw new RuntimeException(sprintf(
156
                'The following arguments are not known: "%s", known arguments: "%s"',
157
                implode('", "', $unrecognisedArguments), implode('", "', $resolutions->argumentNames())
158
            ));
159
        }
160
    }
161
}
162