DataLoader   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 113
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 17
eloc 50
c 1
b 0
f 0
dl 0
loc 113
ccs 48
cts 48
cp 1
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A hp$0 ➔ load() 0 10 1
B hp$0 ➔ resolve() 0 30 6
A hp$0 ➔ __construct() 0 7 1
A hp$0 ➔ run() 0 3 1
A __invoke() 0 10 2
run() 0 3 ?
A identifierForClosure() 0 6 1
A hp$0 ➔ serializeKey() 0 8 3
A hp$0 ➔ scheduleResolveIfNeeded() 0 5 2
makeLoader() 0 79 ?
B hp$0 ➔ makeLoader() 0 79 3
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 8
    public function __invoke($batchLoadFunction, $defaultResolveValue = null)
18
    {
19 8
        if (! $batchLoadFunction instanceof Closure) {
20 1
            $batchLoadFunction = Closure::fromCallable($batchLoadFunction);
21
        }
22
23 8
        $identifier = $this->identifierForClosure($batchLoadFunction);
24
25 8
        return $this->loaders[$identifier] = $this->loaders[$identifier]
26 8
            ?? $this->makeLoader($batchLoadFunction, $defaultResolveValue);
27
    }
28
29 8
    private function identifierForClosure(Closure $closure)
30
    {
31 8
        $reflection = new ReflectionFunction($closure);
32
33 8
        return $reflection->getFileName() . '@' .
34 8
            $reflection->getStartLine() . '-' . $reflection->getEndLine();
35
    }
36
37 8
    private function makeLoader(Closure $batchLoadFunction, $defaultResolveValue)
38
    {
39 8
        return new class ($batchLoadFunction, $defaultResolveValue)
40
        {
41
            private $batchLoadFunction;
42
            private $defaultResolveValue;
43
44
            private $deferredPromises;
45
            private $needsResolving;
46
47
            public function __construct(Closure $batchLoadFunction, $defaultResolveValue = null)
48
            {
49 8
                $this->batchLoadFunction = $batchLoadFunction;
50 8
                $this->defaultResolveValue = $defaultResolveValue;
51
52 8
                $this->deferredPromises = [];
53 8
                $this->needsResolving = false;
54
            }
55
56
            public function load($key)
57
            {
58 8
                $serializedKey = self::serializeKey($key);
59
60 8
                [$deferred] = $this->deferredPromises[$serializedKey]
61 8
                    ?? $this->deferredPromises[$serializedKey] = [new Deferred(), $key];
62
63 8
                $this->scheduleResolveIfNeeded();
64
65 8
                return $deferred->promise();
66
            }
67
68
            private function scheduleResolveIfNeeded()
69
            {
70 8
                if (! $this->needsResolving) {
71 8
                    Loop::defer(Closure::fromCallable([$this, 'resolve']));
72 8
                    $this->needsResolving = true;
73
                }
74
            }
75
76
            private function resolve()
77
            {
78 8
                $indexedOriginalKeys = [];
79 8
                foreach ($this->deferredPromises as [$_, $originalKey]) {
80 8
                    $indexedOriginalKeys[] = $originalKey;
81
                }
82
83 8
                $result = ($this->batchLoadFunction)($indexedOriginalKeys);
84
85 7
                $currentIndex = 0;
86 7
                foreach ($result as $key => $value) {
87 7
                    if ($key === $currentIndex) {
88 7
                        $key = $indexedOriginalKeys[$key] ?? $key;
89
                    }
90 7
                    $key = self::serializeKey($key);
91
92 7
                    if ([$deferred] = $this->deferredPromises[$key] ?? null) {
93 7
                        $deferred->resolve($value);
94 7
                        unset($this->deferredPromises[$key]);
95
                    }
96
97 7
                    ++$currentIndex;
98
                }
99
100 7
                foreach ($this->deferredPromises as $key => [$deferredPromise]) {
101 1
                    $deferredPromise->resolve($this->defaultResolveValue);
102
                }
103
104 7
                $this->deferredPromises = [];
105 7
                $this->needsResolving = false;
106
            }
107
108
            private static function serializeKey($key)
109
            {
110 8
                if (is_object($key)) {
111 1
                    return spl_object_hash($key);
112 8
                } elseif (is_array($key)) {
113 1
                    return md5(json_encode($key));
114
                }
115 8
                return (string) $key;
116
            }
117
        };
118
    }
119
120 28
    public function run(): void
121
    {
122 28
        Loop::run();
123
    }
124
}
125