StopwatchDecorator::decorate()   B
last analyzed

Complexity

Conditions 7
Paths 3

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 3
nop 1
dl 0
loc 24
ccs 14
cts 14
cp 1
crap 7
rs 8.8333
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * /src/Decorator/StopwatchDecorator.php
5
 *
6
 * @author TLe, Tarmo Leppänen <[email protected]>
7
 */
8
9
namespace App\Decorator;
10
11
use App\Entity\Interfaces\EntityInterface;
12
use ProxyManager\Factory\AccessInterceptorValueHolderFactory;
13
use ReflectionClass;
14
use ReflectionMethod;
15
use Symfony\Component\Stopwatch\Stopwatch;
16
use Throwable;
17
use function array_filter;
18
use function is_object;
19
use function str_contains;
20
use function str_starts_with;
21
22
/**
23
 * Class StopwatchDecorator
24
 *
25
 * @package App\Decorator
26
 * @author TLe, Tarmo Leppänen <[email protected]>
27
 */
28
class StopwatchDecorator
29
{
30 6
    public function __construct(
31
        private readonly AccessInterceptorValueHolderFactory $factory,
32
        private readonly Stopwatch $stopwatch,
33
    ) {
34 6
    }
35
36 6
    public function decorate(object $service): object
37
    {
38 6
        $class = new ReflectionClass($service);
39 6
        $className = $class->getName();
40
41
        // Do not process core or extensions or already wrapped services
42 6
        if ($class->getFileName() === false
43 5
            || $class->isFinal()
44 5
            || str_starts_with($class->getName(), 'ProxyManagerGeneratedProxy')
45 5
            || str_contains($class->getName(), 'RequestStack')
46 6
            || str_contains($class->getName(), 'Mock_')
47
        ) {
48 2
            return $service;
49
        }
50
51 5
        [$prefixInterceptors, $suffixInterceptors] = $this->getPrefixAndSuffixInterceptors($class, $className);
52
53
        try {
54 5
            $output = $this->factory->createProxy($service, $prefixInterceptors, $suffixInterceptors);
55 1
        } catch (Throwable) {
56 1
            $output = $service;
57
        }
58
59 5
        return $output;
60
    }
61
62
    /**
63
     * @return array{0: array<string, \Closure>, 1: array<string, \Closure>}
64
     */
65 5
    private function getPrefixAndSuffixInterceptors(ReflectionClass $class, string $className): array
66
    {
67 5
        $prefixInterceptors = [];
68 5
        $suffixInterceptors = [];
69
70 5
        $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
71 5
        $methods = array_filter($methods, static fn ($method): bool => !$method->isStatic() && !$method->isFinal());
72
73 5
        foreach ($methods as $method) {
74 5
            $methodName = $method->getName();
75 5
            $eventName = "{$class->getShortName()}->{$methodName}";
76
77 5
            $prefixInterceptors[$methodName] = function () use ($eventName, $className): void {
78 3
                $this->stopwatch->start($eventName, $className);
79 5
            };
80
81 5
            $suffixInterceptors[$methodName] = function (
82 5
                mixed $p,
83 5
                mixed $i,
84 5
                mixed $m,
85 5
                mixed $params,
86 5
                mixed &$returnValue
87 5
            ) use ($eventName): void {
88 3
                $this->stopwatch->stop($eventName);
89
90
                /**
91
                 * Decorate returned values as well
92
                 *
93
                 * Note that this might cause some weird errors on some edge
94
                 * cases - we should fix those when those happens...
95
                 */
96 3
                if (is_object($returnValue) && !$returnValue instanceof EntityInterface) {
97 1
                    $returnValue = $this->decorate($returnValue);
98
                }
99 5
            };
100
        }
101
102 5
        return [$prefixInterceptors, $suffixInterceptors];
103
    }
104
}
105