anonymous//src/DataLoader.php$0   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 77
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 13
c 0
b 0
f 0
dl 0
loc 77
ccs 35
cts 35
cp 1
rs 10
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