Skip to content
Open
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
6 changes: 3 additions & 3 deletions src/Http/Middleware/CP/HandleAuthenticatedInertiaRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private function alwaysProps()
return [
'version' => Statamic::version(),
'isPro' => Statamic::pro(),
'nav' => $this->nav(),
'nav' => fn () => $this->nav(),
'cmsName' => __(Statamic::pro() ? config('statamic.cp.custom_cms_name', 'Statamic') : 'Statamic'),
];
}
Expand All @@ -63,8 +63,8 @@ private function protectedProps()
return [
'supportUrl' => config('statamic.cp.support_url'),
'selectedSiteUrl' => Site::selected()->url(),
'licensing' => $this->licensing(),
'sessionExpiry' => $this->sessionExpiry(),
'licensing' => fn () => $this->licensing(),
'sessionExpiry' => fn () => $this->sessionExpiry(),
];
}

Expand Down
15 changes: 12 additions & 3 deletions src/Statamic.php
Original file line number Diff line number Diff line change
Expand Up @@ -512,13 +512,22 @@ public static function cpPerPage($perPage)

public static function nonInertiaPageData()
{
$props = Inertia::getShared();

return [
'url' => '/'.request()->path(),
'component' => 'NonInertiaPage',
'version' => inertia()->getVersion(),
'props' => $props,
'props' => static::resolveProps(Inertia::getShared()),
];
}

private static function resolveProps(array $props)
{
return collect($props)->map(function ($value) {
if (is_object($value) && is_callable($value)) {
$value = App::call($value);
}

return is_array($value) ? static::resolveProps($value) : $value;
})->all();
Comment on lines +523 to +531

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't quite match how Inertia itself resolves props. ResolvesCallables::resolveCallable() does:

return is_object($value) && is_callable($value) ? App::call($value) : $value;

Two differences:

  1. No container injection. A shared closure with a type-hinted dependency resolves fine on an Inertia page, but fatals on a blade page:
    ArgumentCountError: Too few arguments to function {closure}(), 0 passed in src/Statamic.php on line 527 and exactly 1 expected
    
  2. Invokable objects aren't resolved. Inertia::share(['foo' => new SomeInvokable]) resolves on an Inertia page; here the object passes straight through to json_encode() and comes out as {}.

Our own three closures take no arguments, so core is unaffected either way. But Inertia::share() is reachable by addons, and this PR is what first puts callables into the shared props at all, so this seems like the moment to make the two paths agree:

private static function resolveProps(array $props)
{
    return collect($props)->map(function ($value) {
        if (is_object($value) && is_callable($value)) {
            $value = App::call($value);
        }

        return is_array($value) ? static::resolveProps($value) : $value;
    })->all();
}

Illuminate\Support\Facades\App is already imported in this file, and the Closure import stays since it's used elsewhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ea4546a. I reproduced both cases first. The type-hinted closure threw the same ArgumentCountError you quoted, at Statamic.php:527. The invokable came back as [] in the JSON. resolveProps() now does the is_object() && is_callable() check and calls App::call(), so both paths behave the same.

I also added a test that shares a closure with a Request dependency and an invokable object, and asserts both resolve outside Inertia.

}
}
103 changes: 103 additions & 0 deletions tests/Http/Middleware/HandleAuthenticatedInertiaRequestsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace Tests\Http\Middleware;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\CP\Nav;
use Statamic\Facades\User;
use Statamic\Statamic;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

class HandleAuthenticatedInertiaRequestsTest extends TestCase
{
use PreventSavingStacheItemsToDisk;

protected $shouldPreventNavBeingBuilt = false;

protected function resolveApplicationConfiguration($app)
{
parent::resolveApplicationConfiguration($app);

Statamic::pushCpRoutes(function () {
Route::get('json-response-test', fn () => ['foo' => 'bar']);

Route::get('non-inertia-page-data-test', fn () => Statamic::nonInertiaPageData());
});
}

#[Test]
public function it_doesnt_build_the_nav_for_responses_that_arent_inertia_pages()
{
$built = false;

Nav::extend(function () use (&$built) {
$built = true;
});

$this
->actingAs(User::make()->makeSuper()->save())
->get('/cp/json-response-test')
->assertOk();

$this->assertFalse($built);
}

#[Test]
public function it_builds_the_nav_for_inertia_pages()
{
$built = false;

Nav::extend(function () use (&$built) {
$built = true;
});

$this
->actingAs(User::make()->makeSuper()->save())
->get('/cp/dashboard')
->assertOk();

$this->assertTrue($built);
}

#[Test]
public function it_resolves_the_nav_for_pages_rendered_outside_of_inertia()
{
$data = $this
->actingAs(User::make()->makeSuper()->save())
->get('/cp/non-inertia-page-data-test')
->assertOk()
->json();

$this->assertNotEmpty($data['props']['_statamic']['nav']);
}

#[Test]
public function it_resolves_shared_callables_the_same_way_inertia_does()
{
Inertia::share([
'closure_with_dependency' => fn (Request $request) => $request->path(),
'invokable' => new SharedInvokable,
]);

$data = $this
->actingAs(User::make()->makeSuper()->save())
->get('/cp/non-inertia-page-data-test')
->assertOk()
->json();

$this->assertEquals('cp/non-inertia-page-data-test', $data['props']['closure_with_dependency']);
$this->assertEquals('invoked', $data['props']['invokable']);
}
}

class SharedInvokable
{
public function __invoke()
{
return 'invoked';
}
}
Loading