Repository: inertiajs/inertia-laravel (php)

Issue:
adding to HandleInertiaRequests->share array no longer works in version 1.2.0
I have this code block that use to work in version 1.0.0. It is no longer working in version 1.2.0. It use to return the auth.user along with permissions. Now it appears that permissions is just overwriting the entire array and that is all that is returned.

//HandleInertiaRequests.php
```
public function share(Request $request): array
{
      return array_merge(parent::share($request), [
            'auth.user.permissions' => fn () => $request->user()->getAllPermissions(),
      ]);
}
```
Partial reload behaviour change in 1.1.0 causes lazy attributes not to be reloaded correctly
### Version:

- `@inertiajs/react` version: 1.1.0

### Describe the problem:

I updated Inertia a few minutes ago and found the following issue.
**Before the Update to 1.1.0:**
When having a list of objects 'foos', with each object having a lazy attribute like 'bar', reloading the list object foos, will result in the bar object being loaded in correctly.
```php
return Inertia::render('TestView', [
    'foos' => [
        [
            'bar' => Inertia::lazy(function () {
                return 'Test';
            })
        ],
        ...
    ]
])
```

```tsx
reload({
    only: [
        'foos'
    ],
});
```

**Current behaviour (after 1.1.0):**
When I now try to do the same, bar is not being loaded into the foo object. I think the partial reload changed in this specific version are faulty for that behavior.

Pull request:
Revert dot-notation support in partial reloads
Resolves #633
Resolves #640
Closes #634
Closes #636

This PR reverts the changes made in #620 which add dot-notation support to partial reloads.

While this would be a nice feature to eventually have, this particular implementation introduced some breaking changes. While we tried hard to resolve them in #634 and #636, we haven't been able to crack it yet, so I've decided to revert these changes for now.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab6fa08..584ff88 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v1.2.0...1.x)
 
 - Add "always" props using new `Inertia::always()` wrapper ([#627](https://github.com/inertiajs/inertia-laravel/pull/627))
+- Revert dot-notation support in partial reloads ([#641](https://github.com/inertiajs/inertia-laravel/pull/641))
 
 ## [v1.2.0](https://github.com/inertiajs/inertia-laravel/compare/v1.1.0...v1.2.0) - 2024-05-17
 
diff --git a/src/Response.php b/src/Response.php
index 45ce7a4..50baf00 100644
--- a/src/Response.php
+++ b/src/Response.php
@@ -86,7 +86,9 @@ class Response implements Responsable
      */
     public function toResponse($request)
     {
-        $props = $this->resolveProperties($request, $this->props);
+        $props = $this->resolvePartialProps($request, $this->props);
+        $props = $this->resolveAlwaysProps($props);
+        $props = $this->evaluateProps($props, $request);
 
         $page = [
             'component' => $this->component,
@@ -103,92 +105,35 @@ class Response implements Responsable
     }
 
     /**
-     * Resolve the properites for the response.
+     * Resolve the `only` and `except` partial request props.
      */
-    public function resolveProperties(Request $request, array $props): array
+    public function resolvePartialProps(Request $request, array $props): array
     {
         $isPartial = $request->header(Header::PARTIAL_COMPONENT) === $this->component;
 
         if (! $isPartial) {
-            $props = array_filter($this->props, static function ($prop) {
+            return array_filter($props, static function ($prop) {
                 return ! ($prop instanceof LazyProp);
             });
         }
 
-        $props = $this->resolveArrayableProperties($props, $request);
-
-        if ($isPartial && $request->hasHeader(Header::PARTIAL_ONLY)) {
-            $props = $this->resolveOnly($request, $props);
-        }
-
-        if ($isPartial && $request->hasHeader(Header::PARTIAL_EXCEPT)) {
-            $props = $this->resolveExcept($request, $props);
-        }
-
-        $props = $this->resolveAlways($props);
-
-        $props = $this->resolvePropertyInstances($props, $request);
-
-        return $props;
-    }
-
-    /**
-     * Resolve all arrayables properties into an array.
-     */
-    public function resolveArrayableProperties(array $props, Request $request, bool $unpackDotProps = true): 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;
-            }
-        }
-
-        return $props;
-    }
-
-    /**
-     * Resolve the `only` partial request props.
-     */
-    public function resolveOnly(Request $request, array $props): array
-    {
         $only = array_filter(explode(',', $request->header(Header::PARTIAL_ONLY, '')));
+        $except = array_filter(explode(',', $request->header(Header::PARTIAL_EXCEPT, '')));
 
-        $value = [];
+        $props = $only ? Arr::only($props, $only) : $props;
 
-        foreach ($only as $key) {
-            Arr::set($value, $key, data_get($props, $key));
+        if ($except) {
+            Arr::forget($props, $except);
         }
 
-        return $value;
-    }
-
-    /**
-     * Resolve the `except` partial request props.
-     */
-    public function resolveExcept(Request $request, array $props): array
-    {
-        $except = array_filter(explode(',', $request->header(Header::PARTIAL_EXCEPT, '')));
-
-        Arr::forget($props, $except);
-
         return $props;
     }
 
     /**
-     * Resolve `always` properties that should always be included on all visits, regardless of "only" or "except" requests.
+     * Resolve `always` properties that should always be included on all visits,
+     * regardless of "only" or "except" requests.
      */
-    public function resolveAlways(array $props): array
+    public function resolveAlwaysProps(array $props): array
     {
         $always = array_filter($this->props, static function ($prop) {
             return $prop instanceof AlwaysProp;
@@ -203,7 +148,7 @@ class Response implements Responsable
     /**
      * Resolve all necessary class instances in the given props.
      */
-    public function resolvePropertyInstances(array $props, Request $request): array
+    public function evaluateProps(array $props, Request $request, bool $unpackDotProps = true): array
     {
         foreach ($props as $key => $value) {
             if ($value instanceof Closure) {
@@ -226,11 +171,20 @@ class Response implements Responsable
                 $value = $value->toResponse($request)->getData(true);
             }
 
+            if ($value instanceof Arrayable) {
+                $value = $value->toArray();
+            }
+
             if (is_array($value)) {
-                $value = $this->resolvePropertyInstances($value, $request);
+                $value = $this->evaluateProps($value, $request, false);
             }
 
-            $props[$key] = $value;
+            if ($unpackDotProps && str_contains($key, '.')) {
+                Arr::set($props, $key, $value);
+                unset($props[$key]);
+            } else {
+                $props[$key] = $value;
+            }
         }
 
         return $props;


Test patch:
diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php
index d0a9ae3..ef692c0 100644
--- a/tests/ResponseTest.php
+++ b/tests/ResponseTest.php
@@ -279,72 +279,6 @@ class ResponseTest extends TestCase
         $this->assertSame('123', $page->version);
     }
 
-    public function test_nested_partial_props(): void
-    {
-        $request = Request::create('/user/123', 'GET');
-        $request->headers->add(['X-Inertia' => 'true']);
-        $request->headers->add(['X-Inertia-Partial-Component' => 'User/Edit']);
-        $request->headers->add(['X-Inertia-Partial-Data' => 'auth.user,auth.refresh_token']);
-
-        $props = [
-            'auth' => [
-                'user' => new LazyProp(function () {
-                    return [
-                        'name' => 'Jonathan Reinink',
-                        'email' => 'jonathan@example.com',
-                    ];
-                }),
-                'refresh_token' => 'value',
-                'token' => 'value',
-            ],
-            'shared' => [
-                'flash' => 'value',
-            ],
-        ];
-
-        $response = new Response('User/Edit', $props);
-        $response = $response->toResponse($request);
-        $page = $response->getData();
-
-        $this->assertFalse(isset($page->props->shared));
-        $this->assertFalse(isset($page->props->auth->token));
-        $this->assertSame('Jonathan Reinink', $page->props->auth->user->name);
-        $this->assertSame('jonathan@example.com', $page->props->auth->user->email);
-        $this->assertSame('value', $page->props->auth->refresh_token);
-    }
-
-    public function test_exclude_nested_props_from_partial_response(): void
-    {
-        $request = Request::create('/user/123', 'GET');
-        $request->headers->add(['X-Inertia' => 'true']);
-        $request->headers->add(['X-Inertia-Partial-Component' => 'User/Edit']);
-        $request->headers->add(['X-Inertia-Partial-Data' => 'auth']);
-        $request->headers->add(['X-Inertia-Partial-Except' => 'auth.user']);
-
-        $props = [
-            'auth' => [
-                'user' => new LazyProp(function () {
-                    return [
-                        'name' => 'Jonathan Reinink',
-                        'email' => 'jonathan@example.com',
-                    ];
-                }),
-                'refresh_token' => 'value',
-            ],
-            'shared' => [
-                'flash' => 'value',
-            ],
-        ];
-
-        $response = new Response('User/Edit', $props);
-        $response = $response->toResponse($request);
-        $page = $response->getData();
-
-        $this->assertFalse(isset($page->props->auth->user));
-        $this->assertFalse(isset($page->props->shared));
-        $this->assertSame('value', $page->props->auth->refresh_token);
-    }
-
     public function test_lazy_props_are_not_included_by_default(): void
     {
         $request = Request::create('/users', 'GET');
@@ -523,4 +457,85 @@ class ResponseTest extends TestCase
 
         $this->assertSame('/subpath/product/123', $page->url);
     }
+
+    public function test_prop_as_basic_array(): void
+    {
+        $request = Request::create('/years', 'GET');
+
+        $response = new Response('Years', ['years' => [2022, 2023, 2024]], 'app', '123');
+        $response = $response->toResponse($request);
+        $view = $response->getOriginalContent();
+        $page = $view->getData()['page'];
+
+        $this->assertSame([2022, 2023, 2024], $page['props']['years']);
+    }
+
+    public function test_dot_notation_props_are_merged_with_shared_props(): void
+    {
+        $request = Request::create('/test', 'GET');
+
+        $response = new Response('Test', [
+            'auth' => ['user' => ['name' => 'Jonathan']],
+            'auth.user.is_super' => true,
+        ], 'app', '123');
+        $response = $response->toResponse($request);
+        $view = $response->getOriginalContent();
+        $page = $view->getData()['page'];
+
+        $this->assertSame([
+            'auth' => [
+                'user' => [
+                    'name' => 'Jonathan',
+                    'is_super' => true,
+                ],
+            ],
+        ], $page['props']);
+    }
+
+    public function test_dot_notation_props_are_merged_with_lazy_shared_props(): void
+    {
+        $request = Request::create('/test', 'GET');
+
+        $response = new Response('Test', [
+            'auth' => function () {
+                return ['user' => ['name' => 'Jonathan']];
+            },
+            'auth.user.is_super' => true,
+        ], 'app', '123');
+
+        $response = $response->toResponse($request);
+        $view = $response->getOriginalContent();
+        $page = $view->getData()['page'];
+
+        $this->assertSame([
+            'auth' => [
+                'user' => [
+                    'name' => 'Jonathan',
+                    'is_super' => true,
+                ],
+            ],
+        ], $page['props']);
+    }
+
+    public function test_dot_notation_props_are_merged_with_other_dot_notation_props(): void
+    {
+        $request = Request::create('/test', 'GET');
+
+        $response = new Response('Test', [
+            'auth.user' => ['name' => 'Jonathan'],
+            'auth.user.is_super' => true,
+        ], 'app', '123');
+        $response = $response->toResponse($request);
+        $view = $response->getOriginalContent();
+        $page = $view->getData()['page'];
+
+        $this->assertSame([
+            'auth' => [
+                'user' => [
+                    'name' => 'Jonathan',
+                    'is_super' => true,
+                ],
+            ],
+        ], $page['props']);
+    }
 }