guardAgainstMissingAppliedArguments()   A
last analyzed

Complexity

Conditions 3
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 1
nop 1
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Scalp;
6
7
use function Scalp\Reflection\reflectionFunction;
0 ignored issues
show
Bug introduced by
The type Scalp\Reflection\reflectionFunction was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
9
final class PartialApplication
10
{
11
    private $f;
12
    private $arguments;
13
14
    public function __construct(callable $f, array $arguments)
15
    {
16
        $this->guardAgainstMissingArguments($f, $arguments);
17
18
        $this->f = $f;
19
        $this->arguments = $arguments;
20
    }
21
22
    public function __invoke()
23
    {
24
        $appliedArguments = $this->applyArguments(func_get_args());
25
26
        $this->guardAgainstMissingAppliedArguments($appliedArguments);
27
28
        return ($this->f)(...$appliedArguments);
29
    }
30
31
    private function applyArguments(array $arguments): array
32
    {
33
        $argsIterator = new \ArrayIterator($arguments);
34
35
        $replacePlaceholders = function ($arg) use ($argsIterator) {
36
            $replacement = $arg === __ && $argsIterator->valid()
37
                ? $argsIterator->current()
38
                : $arg;
39
40
            if ($replacement !== $arg) {
41
                $argsIterator->next();
42
            }
43
44
            return $replacement;
45
        };
46
47
        return array_map($replacePlaceholders, $this->arguments);
48
    }
49
50
    private function guardAgainstMissingArguments(callable $f, array $arguments): void
51
    {
52
        $required = $this->countRequiredArguments($f);
53
54
        if (\count($arguments) < $required) {
55
            throw new \BadFunctionCallException('Number of passed arguments is less than required arguments. Use `__` const to add placeholder or value to apply.');
56
        }
57
    }
58
59
    private function guardAgainstMissingAppliedArguments(array $appliedArguments): void
60
    {
61
        $placeholders = $this->placeholderArguments($appliedArguments);
62
63
        if ($placeholders !== []) {
64
            throw new \BadFunctionCallException(sprintf(
65
                'Partially applied function has %d missing argument%s at position: %s.',
66
                \count($placeholders),
67
                \count($placeholders) > 1 ? 's' : '',
68
                implode(', ', array_map(function (int $idx): int {
69
                    return $idx + 1;
70
                }, array_keys($placeholders)))
71
            ));
72
        }
73
    }
74
75
    private function countRequiredArguments(callable $f): int
76
    {
77
        $rf = reflectionFunction($f);
78
79
        [$count, $required] = array_reduce($rf->getParameters(), function (array $carry, \ReflectionParameter $parameter): array {
80
            $count = $carry[0] + 1;
81
            $required = $parameter->isOptional() ? $carry[1] : $count;
82
83
            return [$count, $required];
84
        }, [0, 0]);
85
86
        return $required;
87
    }
88
89
    private function placeholderArguments(array $arguments): array
90
    {
91
        return array_filter($arguments, function ($arg): bool { return $arg === __; });
92
    }
93
}
94