• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NeoIsRecursive / inertia-tempest / 13986084906

21 Mar 2025 06:35AM UTC coverage: 96.61% (+1.3%) from 95.27%
13986084906

Pull #2

github

web-flow
Merge ab00ae0a5 into bf5c3f470
Pull Request #2: Add defer props support

92 of 93 new or added lines in 1 file covered. (98.92%)

2 existing lines in 2 files now uncovered.

171 of 177 relevant lines covered (96.61%)

11.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.99
/src/Http/InertiaResponse.php
1
<?php
2

3
namespace NeoIsRecursive\Inertia\Http;
4

5
use Closure;
6
use NeoIsRecursive\Inertia\Contracts\MergeableProp;
7
use NeoIsRecursive\Inertia\Props\AlwaysProp;
8
use NeoIsRecursive\Inertia\Props\DeferredProp;
9
use NeoIsRecursive\Inertia\Props\LazyProp;
10
use NeoIsRecursive\Inertia\Support\Header;
11
use NeoIsRecursive\Inertia\Views\InertiaBaseView;
12
use Tempest\Router\IsResponse;
13
use Tempest\Router\Request;
14
use Tempest\Router\Response;
15
use Tempest\Support\Arr\ImmutableArray;
16

17
use function Tempest\invoke;
18
use function Tempest\Support\arr;
19

20
final class InertiaResponse implements Response
21
{
22
    use IsResponse;
23

24
    public function __construct(
25
        readonly Request $request,
26
        readonly string $page,
27
        readonly array $props,
28
        readonly string $rootView,
29
        readonly string $version,
30
    ) {
31

32
        $deferredProps = self::resolvePropKeysThatShouldDefer(
18✔
33
            props: $props,
18✔
34
            request: $request,
18✔
35
            component: $page
18✔
36
        );
18✔
37

38
        $mergeProps = self::resolvePropKeysThatShouldMerge(
18✔
39
            props: $props,
18✔
40
            request: $request
18✔
41
        );
18✔
42

43
        // Build page data immutably
44
        $pageData = array_merge(
18✔
45
            [
18✔
46
                'component'  => $page,
18✔
47
                'props'      => self::composeProps(
18✔
48
                    props: $this->props,
18✔
49
                    request: $this->request,
18✔
50
                    component: $page
18✔
51
                ),
18✔
52
                'url'        => $request->uri,
18✔
53
                'version'    => $version,
18✔
54
            ],
18✔
55
            count($deferredProps) ? ['deferredProps' => $deferredProps] : [],
18✔
56
            count($mergeProps) ? ['mergeProps' => $mergeProps] : []
18✔
57
        );
18✔
58

59
        $isInertia = ($request->headers->offsetExists(Header::INERTIA)
18✔
60
            && $request->headers[Header::INERTIA] === 'true');
18✔
61

62
        $this->body = $isInertia
18✔
63
            ? (function () use ($pageData) {
12✔
64
                // side effect to set Inertia header
65
                $this->headers[Header::INERTIA] = 'true';
12✔
66

67
                return $pageData;
12✔
68
            })()
12✔
69
            : new InertiaBaseView(
6✔
70
                view: $rootView,
6✔
71
                pageData: $pageData
6✔
72
            );
6✔
73
    }
74

75
    public static function isPartial(Request $request, string $component): bool
76
    {
77
        return ($request->headers[Header::PARTIAL_COMPONENT] ?? null) === $component;
18✔
78
    }
79

80
    /**
81
     * @pure
82
     * Composes the various prop transformations into one functional pipeline.
83
     */
84
    private static function composeProps(array $props, Request $request, string $component): array
85
    {
86
        $always = static::resolveAlwaysProps($props);
18✔
87
        $partial = static::resolvePartialProps($request, $component, $props);
18✔
88

89
        return static::evaluateProps(
18✔
90
            array_merge($always, $partial),
18✔
91
            $request,
18✔
92
            true
18✔
93
        );
18✔
94
    }
95

96
    /**
97
     * @pure
98
     * function to extract AlwaysProp instances.
99
     */
100
    private static function resolveAlwaysProps(array $props): array
101
    {
102
        return array_filter($props, static fn($prop) => $prop instanceof AlwaysProp);
18✔
103
    }
104

105
    /**
106
     * @pure
107
     * function to extract Partial props based on request headers.
108
     */
109
    private static function resolvePartialProps(Request $request, string $component, array $props): array
110
    {
111
        $headers = $request->headers;
18✔
112

113
        if (!static::isPartial($request, $component)) {
18✔
114
            return array_filter($props, static fn($prop) => !($prop instanceof LazyProp || $prop instanceof DeferredProp));
14✔
115
        }
116

117
        $only = array_filter(explode(',', $headers[Header::PARTIAL_ONLY] ?? ''));
4✔
118
        $except = array_filter(explode(',', $headers[Header::PARTIAL_EXCEPT] ?? ''));
4✔
119

120
        $filtered = $only
4✔
121
            ? array_intersect_key($props, array_flip($only))
3✔
122
            : $props;
1✔
123

124
        return array_filter(
4✔
125
            $filtered,
4✔
126
            static fn($key) => !in_array($key, $except, true),
4✔
127
            ARRAY_FILTER_USE_KEY
4✔
128
        );
4✔
129
    }
130

131
    private static function resolvePropKeysThatShouldDefer(array $props, Request $request, string $component): array
132
    {
133

134
        if (static::isPartial($request, $component)) {
18✔
135
            return [];
4✔
136
        }
137

138
        return arr($props)
14✔
139
            ->filter(function ($prop) {
14✔
140
                return $prop instanceof DeferredProp;
14✔
141
            })
14✔
142
            ->map(fn(DeferredProp $prop, string $key) => [
14✔
143
                'group' => $prop->group,
14✔
144
                'key'   => $key,
14✔
145
            ])
14✔
146
            ->groupBy(fn(array $prop) => $prop['group'])
14✔
147
            ->map(fn(array $group) => arr($group)->pluck('key')->toArray())
14✔
148
            ->toArray();
14✔
149
    }
150

151
    private static function resolvePropKeysThatShouldMerge(array $props, Request $request): array
152
    {
153
        $resetProps = arr(explode(',', $request->headers[Header::RESET] ?? ''));
18✔
154
        return arr($props)
18✔
155
            ->filter(fn($prop) => $prop instanceof MergeableProp && $prop->shouldMerge)
18✔
156
            ->filter(fn($_, $key) => !$resetProps->contains($key))
18✔
157
            ->keys()
18✔
158
            ->toArray();
18✔
159
    }
160

161
    /**
162
     * @pure
163
     * Evaluates props recursively.
164
     */
165
    private static function evaluateProps(array $props, Request $request, bool $unpackDotProps = true): array
166
    {
167
        return arr($props)
18✔
168
            ->map(function ($value, string|int $key) use ($request) {
18✔
169
                $evaluated = $value instanceof Closure ? invoke($value) : $value;
18✔
170
                $evaluated = ($evaluated instanceof LazyProp || $evaluated instanceof AlwaysProp)
18✔
171
                    ? $evaluated()
4✔
172
                    : $evaluated;
17✔
173
                $evaluated = $evaluated instanceof ImmutableArray
18✔
NEW
174
                    ? $evaluated->toArray()
×
175
                    : $evaluated;
18✔
176
                $evaluated = is_array($evaluated)
18✔
177
                    ? self::evaluateProps($evaluated, $request, false)
15✔
178
                    : $evaluated;
17✔
179

180
                return [$key, $evaluated];
18✔
181
            })
18✔
182
            ->reduce(
18✔
183
                function (array $acc, array $item) use ($unpackDotProps) {
18✔
184
                    [$key, $value] = $item;
18✔
185
                    if ($unpackDotProps && str_contains($key, '.')) {
18✔
186
                        return arr($acc)->set($key, $value)->toArray();
4✔
187
                    }
188
                    $acc[$key] = $value;
18✔
189
                    return $acc;
18✔
190
                },
18✔
191
                []
18✔
192
            );
18✔
193
    }
194
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc