Issues (158)

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
14
use function array_shift;
15
use function assert;
16
use function is_array;
17
use function is_string;
18
use function uri_template;
19
20
/** @psalm-import-type Query from Types */
21
final readonly class EmbedInterceptor implements EmbedInterceptorInterface
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 21 at column 6
Loading history...
22
{
23
    private const SELF_LINK = '_self';
24
25
    private ResourceInterface $resource;
26
27
    public function __construct(
28
        ResourceInterface $resource,
29
    ) {
30
        $this->resource = clone $resource;
31
    }
32
33
    /**
34
     * {@inheritDoc}
35
     *
36
     * @throws EmbedException
37
     */
38
    #[Override]
39
    public function invoke(MethodInvocation $invocation)
40
    {
41
        $ro = $invocation->getThis();
42
        assert($ro instanceof ResourceObject);
43
        $query = $this->getArgsByInvocation($invocation);
44
        $embeds = $invocation->getMethod()->getAnnotations();
45
        $this->embedResource($embeds, $ro, $query);
46
47
        return $invocation->proceed();
48
    }
49
50
    /**
51
     * @param array<Embed|object> $embeds
52
     * @param Query               $query
53
     *
54
     * @throws EmbedException
55
     *
56
     * @psalm-suppress NoInterfaceProperties
57
     * @psalm-suppress MixedMethodCall
58
     */
59
    private function embedResource(array $embeds, ResourceObject $ro, array $query): void
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
                /** @var Request $request */ // phpcs:ignore SlevomatCodingStandard.PHP.RequireExplicitAssertion.RequiredExplicitAssertion
70
                $request = $this->resource->get->uri($uri); // @phpstan-ignore-line
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