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

Inertia Middleware: A better home for user-provided defaults. #161

Merged
merged 21 commits into from
Oct 14, 2020

Conversation

claudiodekker
Copy link
Member

@claudiodekker claudiodekker commented Oct 9, 2020

This PR introduces a new way of setting up and sharing application defaults with Inertia.

Up until this point, we've always used (and recommended) the AppServiceProvider as the place to do this, but doing so has a few negative impacts, most important one being that Inertia gets bootstrapped even when it does not have to (e.g. when running inside the Console).

By using Middleware for this instead, we not only solve this problem, but it becomes possible to provide different defaults for each route (group), as you're free to (re)configure these middleware as you see fit!

To use:

  • Run php artisan inertia:middleware to generate a new HandleInertiaRequests middleware.
  • Add the \App\Http\Middleware\HandleInertiaRequests::class to your application's Middleware stack.

Example implementation: https://github.com/inertiajs/pingcrm/pull/88/files

@reinink
Copy link
Member

reinink commented Oct 10, 2020

Thanks so much for working on this @claudiodekker!

So, I don't really understand the need for the trait here. I think it would be nice to just use regular inheritance instead. I've hacked on this a little tonight, and got it working the way I was hoping. Please find both a proposed new Inertia adapter middleware below, and an example user-land implementation (based on Ping CRM).

Adapter: Inertia\Middleware

<?php

namespace Inertia;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Inertia\Inertia;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;

class Middleware
{
    public function handle($request, Closure $next)
    {
        Inertia::version($this->version($request));
        Inertia::share($this->share($request));

        $response = $next($request);
        $response = $this->checkVersion($request, $response);
        $response = $this->changeRedirectCode($request, $response);

        return $response;
    }

    public function checkVersion(Request $request, Response $response)
    {
        if ($request->header('X-Inertia') &&
            $request->method() === 'GET' &&
            $request->header('X-Inertia-Version', '') !== Inertia::getVersion()
        ) {
            if ($request->hasSession()) {
                $request->session()->reflash();
            }

            return new Response('', 409, ['X-Inertia-Location' => $request->fullUrl()]);
        }

        return $response;
    }

    public function changeRedirectCode(Request $request, Response $response)
    {
        if ($request->header('X-Inertia') &&
            $response instanceof RedirectResponse &&
            $response->getStatusCode() === 302 &&
            in_array($request->method(), ['PUT', 'PATCH', 'DELETE'])
        ) {
            $response->setStatusCode(303);
        }

        return $response;
    }

    public function version(Request $request)
    {
        return null;
    }

    public function share(Request $request)
    {
        return [
            'errors' => function () use ($request) {
                return $this->resolveValidationErrors($request);
            }
        ];
    }

    public function resolveValidationErrors(Request $request)
    {
        if (! $request->session()->has('errors')) {
            return (object) [];
        }

        return (object) Collection::make($request->session()->get('errors')->getBags())->map(function ($bag) {
            return (object) Collection::make($bag->messages())->map(function ($errors) {
                return $errors[0];
            })->toArray();
        })->pipe(function ($bags) {
            return $bags->has('default') ? $bags->get('default') : $bags->toArray();
        });
    }
}

User-land: App\Http\Middleware\Inertia

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Inertia\Middleware;

class Inertia extends Middleware
{
    public function version(Request $request)
    {
        return md5_file(public_path('mix-manifest.json'));
    }
    
    public function share(Request $request)
    {
        return array_merge(parent::share($request), [
            'auth' => function () use ($request) {
                return [
                    'user' => $request->user() ? [
                        'id' => $request->user()->id,
                        'first_name' => $request->user()->first_name,
                        'last_name' => $request->user()->last_name,
                        'email' => $request->user()->email,
                        'role' => $request->user()->role,
                        'account' => [
                            'id' => $request->user()->account->id,
                            'name' => $request->user()->account->name,
                        ],
                    ] : null,
                ];
            },
            'flash' => function () use ($request) {
                return [
                    'success' => $request->session()->get('success'),
                    'error' => $request->session()->get('error'),
                ];
            },
        ]);
    }
}

@claudiodekker
Copy link
Member Author

claudiodekker commented Oct 10, 2020

Hey @reinink 👋 ,

Yeah, that was actually the first thing I tried, but for some reason it wouldn't work? I got into the situation where it would call $this->share($request) on the 'base' Middleware, and wouldn't pick up on the overloaded/extended instance. (And I'm sure I created an instance of the 'extended' class, and not the base class..)

Anyway, I probably missed something, because I used what you suggested, and it worked just fine this time around 🤔
I've updated the PR to reflect those changes, as well as some other things. Pretty satisfied with the result!

src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
stubs/Middleware.blade.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
Co-authored-by: Jonathan Reinink <[email protected]>
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
claudiodekker and others added 3 commits October 10, 2020 14:40
Co-authored-by: Jonathan Reinink <[email protected]>
Co-authored-by: Jonathan Reinink <[email protected]>
Co-authored-by: Jonathan Reinink <[email protected]>
Co-authored-by: Jonathan Reinink <[email protected]>
src/Middleware.php Outdated Show resolved Hide resolved
src/Middleware.php Outdated Show resolved Hide resolved
tests/ControllerTest.php Outdated Show resolved Hide resolved
tests/MiddlewareTest.php Outdated Show resolved Hide resolved
This makes editor autocompletion more friendly, as before there would be two classes that would be called Inertia otherwise.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants