Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.x] Fix resolving shared dot props #636

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 73 additions & 52 deletions src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,83 +119,67 @@ public function resolveProperties(Request $request, array $props): array
});
}

$props = $this->resolveArrayableProperties($props, $request);
// Dot-notated props should be resolved last to ensure that
// they are correctly merged with callable props.
uksort($props, function ($key) {
return str_contains($key, '.');
});

if($isPartial && $request->hasHeader(Header::PARTIAL_ONLY)) {
$props = $this->resolveOnly($request, $props);
}

if($isPartial && $request->hasHeader(Header::PARTIAL_EXCEPT)) {
$props = $this->resolveExcept($request, $props);
}
$only = $this->getOnly($request, $isPartial);
$except = $this->getExcept($request, $isPartial);

$props = $this->resolvePropertyInstances($props, $request);
$props = $this->resolvePropertyInstances($props, $request, $only, $except);

return $props;
}

/**
* Resolve all arrayables properties into an array.
* Get the `only` partial props.
*/
public function resolveArrayableProperties(array $props, Request $request, bool $unpackDotProps = true): array
public function getOnly(Request $request, bool $isPartial): array
{
foreach ($props as $key => $value) {
if ($value instanceof Arrayable) {
$value = $value->toArray();
}

if (is_array($value)) {
$value = $this->resolveArrayableProperties($value, $request, false);
}

if ($unpackDotProps && str_contains($key, '.')) {
Arr::set($props, $key, $value);
unset($props[$key]);
} else {
$props[$key] = $value;
}
if(! $isPartial) {
return [];
}

return $props;
}

/**
* Resolve the `only` partial request props.
*/
public function resolveOnly(Request $request, array $props): array
{
$only = array_merge(
return array_merge(
array_filter(explode(',', $request->header(Header::PARTIAL_ONLY, ''))),
$this->persisted
);

$value = [];

foreach($only as $key) {
Arr::set($value, $key, data_get($props, $key));
}

return $value;
}

/**
* Resolve the `except` partial request props.
* Get the `except` partial props.
*/
public function resolveExcept(Request $request, array $props): array
public function getExcept(Request $request, bool $isPartial): array
{
$except = array_filter(explode(',', $request->header(Header::PARTIAL_EXCEPT, '')));

Arr::forget($props, $except);
if(! $isPartial) {
return [];
}

return $props;
return array_filter(explode(',', $request->header(Header::PARTIAL_EXCEPT, '')));
}

/**
* Resolve all necessary class instances in the given props.
*/
public function resolvePropertyInstances(array $props, Request $request): array
public function resolvePropertyInstances(array $props, Request $request, array $only, array $except, bool $unpackDotProps = true, string $parentKey = ''): array
{
foreach ($props as $key => $value) {
$prop = $parentKey ? implode('.', [$parentKey, $key]) : $key;

if($only && ! $this->isPropIncluded($only, $prop)) {
unset($props[$key]);

continue;
}

if($except && $this->isPropExcluded($except, $prop)) {
unset($props[$key]);

continue;
}

if ($value instanceof Closure) {
$value = App::call($value);
}
Expand All @@ -212,13 +196,50 @@ public function resolvePropertyInstances(array $props, Request $request): array
$value = $value->toResponse($request)->getData(true);
}

if ($value instanceof Arrayable) {
$value = $value->toArray();
}

if (is_array($value)) {
$value = $this->resolvePropertyInstances($value, $request);
$value = $this->resolvePropertyInstances($value, $request, $only, $except, false, $prop);
}

$props[$key] = $value;
if ($unpackDotProps && str_contains($key, '.')) {
Arr::set($props, $key, $value);
unset($props[$key]);
} else {
$props[$key] = $value;
}
}

return $props;
}

/**
* Determine whether a prop should be included in the partial response.
*/
public function isPropIncluded(array $only, string $prop): bool
{
foreach($only as $key) {
if(Str::startsWith($key, $prop) || Str::startsWith($prop, $key)) {
return true;
}
}

return false;
}

/**
* Determine whether a prop should be excluded from the partial response.
*/
public function isPropExcluded(array $except, string $prop): bool
{
foreach($except as $key) {
if(Str::startsWith($prop, $key)) {
return true;
}
}

return false;
}
}
71 changes: 71 additions & 0 deletions tests/MiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,77 @@ public function test_validation_errors_are_scoped_to_error_bag_header(): void
$this->withoutExceptionHandling()->get('/', ['X-Inertia-Error-Bag' => 'example']);
}

public function test_middleware_can_share_props_with_dot_notation(): void
{
$this->prepareMockEndpoint(null, [
'auth.permissions.is_admin' => true,
'user.verified' => true,
]);

$response = $this->get('/', ['X-Inertia' => 'true']);

$response->assertJson([
'props' => [
'user' => [
'name' => 'Jonathan',
'verified' => true,
],
'auth' => [
'permissions' => [
'is_admin' => true,
],
],
],
]);
}

public function test_include_shared_props_in_partial_response(): void
{
$this->prepareMockEndpoint(null, [
'auth.permissions.is_admin' => true,
'user.verified' => true,
]);

$response = $this->get('/', [
'X-Inertia' => 'true',
'X-Inertia-Partial-Component' => 'User/Edit',
'X-Inertia-Partial-Data' => 'auth',
]);

$response->assertJson([
'props' => [
'auth' => [
'permissions' => [
'is_admin' => true,
],
],
],
]);
}

public function test_exclude_shared_props_from_partial_response(): void
{
$this->prepareMockEndpoint(null, [
'auth.permissions.is_admin' => true,
'user.verified' => true,
]);

$response = $this->get('/', [
'X-Inertia' => 'true',
'X-Inertia-Partial-Component' => 'User/Edit',
'X-Inertia-Partial-Data' => 'user',
'X-Inertia-Partial-Except' => 'user.verified',
]);

$response->assertJson([
'props' => [
'user' => [
'name' => 'Jonathan',
],
],
]);
}

public function test_middleware_can_change_the_root_view_via_a_property(): void
{
$this->prepareMockEndpoint(null, [], new class() extends Middleware {
Expand Down
Loading
Loading