Issues (169)

src/EmbedInterceptor.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use BEAR\Resource\Annotation\Embed;
8
use BEAR\Resource\Exception\BadRequestException;
9
use BEAR\Resource\Exception\EmbedException;
10
use BEAR\Resource\Exception\LinkException;
11
use Override;
12
use Ray\Aop\MethodInvocation;
13
use Ray\Di\Di\Set;
14
use Ray\Di\ProviderInterface;
15
16
use function array_shift;
17
use function assert;
18
use function is_array;
19
use function is_string;
20
use function uri_template;
21
22
/** @psalm-import-type Query from Types */
23
final readonly class EmbedInterceptor implements EmbedInterceptorInterface
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 23 at column 6
Loading history...
24
{
25
    private const SELF_LINK = '_self';
26
27
    /** @param ProviderInterface<ResourceInterface> $resourceProvider */
28
    public function __construct(
29
        #[Set(ResourceInterface::class)]
30
        private ProviderInterface $resourceProvider,
31
    ) {
32
    }
33
34
    /**
35
     * {@inheritDoc}
36
     *
37
     * @throws EmbedException
38
     */
39
    #[Override]
40
    public function invoke(MethodInvocation $invocation)
41
    {
42
        $ro = $invocation->getThis();
43
        assert($ro instanceof ResourceObject);
44
        $query = $this->getArgsByInvocation($invocation);
45
        $embeds = $invocation->getMethod()->getAnnotations();
46
        $this->embedResource($embeds, $ro, $query);
47
48
        return $invocation->proceed();
49
    }
50
51
    /**
52
     * @param array<Embed|object> $embeds
53
     * @param Query               $query
54
     *
55
     * @throws EmbedException
56
     */
57
    private function embedResource(array $embeds, ResourceObject $ro, array $query): void
58
    {
59
        $resource = $this->resourceProvider->get();
60
61
        foreach ($embeds as $embed) {
62
            if (! $embed instanceof Embed) {
63
                continue;
64
            }
65
66
            try {
67
                $templateUri = $this->getFullUri($embed->src, $ro);
68
                $uri = uri_template($templateUri, $query);
69
                $request = $resource->createRequest(Request::GET, $uri);
70
                assert($request instanceof Request);
71
                $this->prepareBody($ro, $embed);
72
73
                if ($embed->rel === self::SELF_LINK) {
74
                    $this->linkSelf($request, $ro);
75
76
                    continue;
77
                }
78
79
                assert(is_array($ro->body));
80
81
                $ro->body[$embed->rel] = clone $request;
82
            } catch (BadRequestException $e) {
83
                // wrap ResourceNotFound or Uri exception
84
                throw new EmbedException($embed->src, 500, $e);
85
            }
86
        }
87
    }
88
89
    private function getFullUri(string $uri, ResourceObject $ro): string
90
    {
91
        if ($uri[0] === '/') {
92
            $uri = "{$ro->uri->scheme}://{$ro->uri->host}" . $uri;
93
        }
94
95
        return $uri;
96
    }
97
98
    public function prepareBody(ResourceObject $ro, Embed $embed): void
99
    {
100
        if ($ro->body === null) {
101
            $ro->body = [];
102
        }
103
104
        if (! is_array($ro->body)) {
105
            throw new LinkException($embed->rel); // @codeCoverageIgnore
106
        }
107
    }
108
109
    /**
110
     * @param MethodInvocation<object> $invocation
111
     *
112
     * @return Query
113
     */
114
    private function getArgsByInvocation(MethodInvocation $invocation): array
115
    {
116
        /** @var list<scalar> $args */
117
        $args = $invocation->getArguments()->getArrayCopy();
118
        $params = $invocation->getMethod()->getParameters();
119
        $namedParameters = [];
120
        foreach ($params as $param) {
121
            $namedParameters[$param->name] = array_shift($args);
122
        }
123
124
        return $namedParameters;
125
    }
126
127
    public function linkSelf(Request $request, ResourceObject $ro): void
128
    {
129
        $result = $request();
130
        assert(is_array($result->body));
131
        /** @var mixed $value */
132
        foreach ($result->body as $key => $value) {
133
            assert(is_string($key));
134
            /** @psalm-suppress MixedArrayAssignment */
135
            $ro->body[$key] = $value; // @phpstan-ignore-line
136
        }
137
138
        $ro->code = $result->code;
139
    }
140
}
141