Test Failed
Pull Request — master (#42)
by
unknown
07:56
created

DataLoader::__invoke()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 10
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 2
crap 2
1
<?php
2
3
namespace Butler\Graphql;
4
5
use Amp\Deferred;
6
use Amp\Loop;
7
use Closure;
8
use ReflectionFunction;
9
10
class DataLoader
11
{
12
    private $loaders = [];
13
14
    /**
15
     * @param  callable|Closure  $batchLoadFunction
16
     */
17 31
    public function __invoke($batchLoadFunction, $defaultResolveValue = null)
18
    {
19 31
        if (! $batchLoadFunction instanceof Closure) {
20 31
            $batchLoadFunction = Closure::fromCallable($batchLoadFunction);
21
        }
22
23
        $identifier = $this->identifierForClosure($batchLoadFunction);
24
25 9
        return $this->loaders[$identifier] = $this->loaders[$identifier]
26
            ?? $this->makeLoader($batchLoadFunction, $defaultResolveValue);
27 9
    }
28 1
29
    private function identifierForClosure(Closure $closure)
30
    {
31 9
        $reflection = new ReflectionFunction($closure);
32
33 9
        return $reflection->getFileName() . '@' .
34 9
            $reflection->getStartLine() . '-' . $reflection->getEndLine();
35
    }
36
37 9
    private function makeLoader(Closure $batchLoadFunction, $defaultResolveValue)
38
    {
39 9
        return new class ($batchLoadFunction, $defaultResolveValue)
40
        {
41 9
            private array $deferredPromises;
42 9
            private bool $needsResolving = false;
43
44
            public function __construct(private Closure $batchLoadFunction, private $defaultResolveValue)
0 ignored issues
show
Unused Code introduced by
The parameter $batchLoadFunction is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

44
            public function __construct(/** @scrutinizer ignore-unused */ private Closure $batchLoadFunction, private $defaultResolveValue)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $defaultResolveValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

44
            public function __construct(private Closure $batchLoadFunction, /** @scrutinizer ignore-unused */ private $defaultResolveValue)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
45 9
            {
46
            }
47 9
48 9
            public function load($key)
49 9
            {
50 8
                $serializedKey = self::serializeKey($key);
51 8
52
                [$deferred] = $this->deferredPromises[$serializedKey]
53 8
                    ?? $this->deferredPromises[$serializedKey] = [new Deferred(), $key];
54 9
55 9
                $this->scheduleResolveIfNeeded();
56 9
57
                return $deferred->promise();
58
            }
59
60 24
            private function scheduleResolveIfNeeded()
61
            {
62 24
                if (! $this->needsResolving) {
63
                    Loop::defer(Closure::fromCallable([$this, 'resolve']));
64
                    $this->needsResolving = true;
65
                }
66
            }
67
68
            private function resolve()
69
            {
70
                $indexedOriginalKeys = [];
71
                foreach ($this->deferredPromises as [$_, $originalKey]) {
72
                    $indexedOriginalKeys[] = $originalKey;
73
                }
74
75
                $result = ($this->batchLoadFunction)($indexedOriginalKeys);
76
77
                $currentIndex = 0;
78
                foreach ($result as $key => $value) {
79
                    if ($key === $currentIndex) {
80
                        $key = $indexedOriginalKeys[$key] ?? $key;
81
                    }
82
                    $key = self::serializeKey($key);
83
84
                    if ([$deferred] = $this->deferredPromises[$key] ?? null) {
85
                        $deferred->resolve($value);
86
                        unset($this->deferredPromises[$key]);
87
                    }
88
89
                    ++$currentIndex;
90
                }
91
92
                foreach ($this->deferredPromises as $key => [$deferredPromise]) {
93
                    $deferredPromise->resolve($this->defaultResolveValue);
94
                }
95
96
                $this->deferredPromises = [];
97
                $this->needsResolving = false;
98
            }
99
100
            private static function serializeKey($key)
101
            {
102
                if (is_object($key)) {
103
                    return spl_object_hash($key);
104
                } elseif (is_array($key)) {
105
                    return md5(json_encode($key));
106
                }
107
                return (string) $key;
108
            }
109
        };
110
    }
111
112
    public function run(): void
113
    {
114
        Loop::run();
115
    }
116
}
117