laravel / nova-issues

556 stars 34 forks source link

[11.x] nova still has a dependency on spatie/once package #6278

Closed ibrunotome closed 8 months ago

ibrunotome commented 8 months ago

Description:

Followed upgrade guide , but trying to access any route after the upgrade give me this error:

Error: Class "Spatie\Once\Cache" not found in /var/www/vendor/laravel/nova/src/NovaCoreServiceProvider.php:102
Stack trace:
#0 /var/www/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(458): Laravel\Nova\NovaCoreServiceProvider->Laravel\Nova\{closure}(Object(Laravel\Octane\Events\RequestReceived))
#1 /var/www/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(286): Illuminate\Events\Dispatcher->Illuminate\Events\{closure}('Laravel\\Octane\\...', Array)
#2 /var/www/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php(266): Illuminate\Events\Dispatcher->invokeListeners('Laravel\\Octane\\...', Array, false)
#3 /var/www/vendor/laravel/octane/src/DispatchesEvents.php(18): Illuminate\Events\Dispatcher->dispatch('Laravel\\Octane\\...')
#4 /var/www/vendor/laravel/octane/src/ApplicationGateway.php(30): Laravel\Octane\ApplicationGateway->dispatchEvent(Object(Illuminate\Foundation\Application), Object(Laravel\Octane\Events\RequestReceived))
#5 /var/www/vendor/laravel/octane/src/Worker.php(84): Laravel\Octane\ApplicationGateway->handle(Object(Illuminate\Http\Request))
#6 /var/www/vendor/laravel/octane/bin/swoole-server(120): Laravel\Octane\Worker->handle(Object(Illuminate\Http\Request), Object(Laravel\Octane\RequestContext))
#7 [internal function]: {closure}(Object(Swoole\Http\Request), Object(Swoole\Http\Response))
#8 /var/www/vendor/laravel/octane/bin/swoole-server(170): Swoole\Server->start()
#9 {main}

These are my dependencies:

"require": {
  "php": "^8.3",
  "ext-bcmath": "*",
  "firebase/php-jwt": "^6.9",
  "guzzlehttp/guzzle": "^7.2",
  "laravel/cashier": "^15.1",
  "laravel/framework": "^v11.0",
  "laravel/horizon": "^5.20",
  "laravel/nova": "^4.33",
  "laravel/octane": "^2.0",
  "laravel/sanctum": "^4.0",
  "laravel/telescope": "^5.0",
  "laravel/tinker": "^2.8",
  "league/flysystem-aws-s3-v3": "^3.0",
  "outl1ne/nova-settings": "^5.2",
  "owen-it/laravel-auditing": "^13.5",
  "spatie/laravel-sluggable": "^3.5",
  "tpetry/laravel-postgresql-enhanced": "^0.37"
},
"require-dev": {
  "barryvdh/laravel-ide-helper": "^3.0",
  "fakerphp/faker": "^1.9.1",
  "laravel/pail": "^1.0",
  "laravel/pint": "^1.13",
  "mockery/mockery": "^1.4.4",
  "nunomaduro/collision": "^8.1",
  "pestphp/pest": "^2.0",
  "pestphp/pest-plugin-laravel": "^2.0",
  "spatie/laravel-ignition": "^2.3.1"
},

The docs says to remove spatie/once dependency but nova still using it:

  "require": {
    "php": "^7.3|^8.0",
    "brick/money": "^0.5.0|^0.6.0|^0.7.0|^0.8.0",
    "doctrine/dbal": "^2.13.3|^3.1.2|^4.0",
    "illuminate/support": "^8.83.4|^9.3.1|^10.0|^11.0",
    "inertiajs/inertia-laravel": "^0.4.5|^0.5.2|^0.6.0|^1.0",
    "laravel/ui": "^3.3|^4.0",
    "nesbot/carbon": "^2.53.1|^3.0",
    "rap2hpoutre/fast-excel": "^3.2|^4.1|^5.0",
    "spatie/once": "^1.1|^2.0|^3.0",
    "symfony/console": "^5.4|^6.0|^7.0",
    "symfony/finder": "^5.4|^6.0|^7.0",
    "symfony/polyfill-intl-icu": "^1.22.1",
    "symfony/process": "^5.4|^6.0|^7.0",
    "ext-json": "*"
  },
  "require-dev": {
    "larastan/larastan": "^1.0.1|^2.5.1",
    "laravel/nova-dusk-suite": "8.4.x-dev|9.4.x-dev|10.4.x-dev|11.4.x-dev",
    "laravel/pint": "^1.6",
    "laravel/scout": "^9.8|^10.0",
    "mockery/mockery": "^1.4.4",
    "orchestra/testbench-dusk": "^6.44|^7.40|^8.22|^9.0",
    "phpunit/phpunit": "^9.6|^10.5",
    "predis/predis": "^1.1.9|^2.0.2"
  },

Detailed steps to reproduce the issue on a fresh Nova installation:

Not tested on a fresh laravel/nova installation, just upgraded from 10.x to 11.x

codebarista commented 8 months ago

This also happens here with a clean new installation in conjunction with laravel/octane. Steps to reproduce:

  1. Install Laravel 11
    laravel new novatest
  2. Install Nova 4
    composer require laravel/nova
  3. Serve Laravel and navigate to http://127.0.0.1:8000/nova (ok)
    php artisan serve
  4. Install Octane (for the sake of simplicity with FrankenPHP)
    composer require laravel/octane
    php artisan octane:install 
  5. Start Octane and navigate to http://127.0.0.1:8000/nova (fail)

    php artisan octane:start

    Response:

    Error: Class "Spatie\Once\Cache" not found in vendor/laravel/nova/src/NovaCoreServiceProvider.php:102
    $event->listen(RequestReceived::class, function ($event) {
    Nova::flushState();
    /** @phpstan-ignore-next-line */
    Cache::getInstance()->flush(); // <--- Line 102 Spatie\Once\Cache
    
    $event->sandbox->forgetInstance(ImpersonatesUsers::class);
    });

    In vendor/composer the package spatie/once is listed as installed. However, the folder does not exist in vendor/spatie.

codebarista commented 8 months ago

Admittedly, a hacky workaround, but this is how I was able to run the Laravel 11 update with Octane and Nova for now:

  1. Create the file app/Overrides/Cache.php
  2. Paste the following cache emulation:
    
    <?php

namespace Spatie\Once;

use Illuminate\Support\Once;

class Cache { public static function getInstance(): static { return new static; }

public function flush(): void
{
    Once::flush();
}

}

3. Fake the namespace `Spatie\Once` in composer.json
```json
"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Database\\Factories\\": "database/factories/",
        "Database\\Seeders\\": "database/seeders/",
        "Spatie\\Once\\": "app/Overrides"
    }
},

Edit: actually clear the new once cache.

martio commented 8 months ago

The issue is with the duplication of the "once" function in Laravel 11. You should replace Cache::getInstance()->flush(); with Once::flush();.

https://github.com/laravel/framework/pull/49744/files

The workaround is to copy the provider and correct this line of code:

composer.json

    "extra": {
        "laravel": {
            "dont-discover": [
                "laravel/nova"
            ]
        }
    },

app/Providers/NovaCoreServiceProvider.php

 <?php

namespace App\Providers;

use Illuminate\Auth\Events\Attempting;
use Illuminate\Auth\Events\Logout;
use Illuminate\Container\Container;
use Illuminate\Contracts\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Events\RequestHandled;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Once;
use Illuminate\Support\ServiceProvider;
use Laravel\Nova\Auth\Adapters\SessionImpersonator;
use Laravel\Nova\Contracts\ImpersonatesUsers;
use Laravel\Nova\Contracts\QueryBuilder;
use Laravel\Nova\Events\ServingNova;
use Laravel\Nova\Http\Middleware\ServeNova;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Listeners\BootNova;
use Laravel\Nova\Query\Builder;
use Laravel\Octane\Events\RequestReceived;
use Laravel\Nova\Nova;
use Laravel\Nova\NovaServiceProvider as BaseNovaServiceProvider;

/**
 * The primary purpose of this service provider is to push the ServeNova
 * middleware onto the middleware stack so we only need to register a
 * minimum number of resources for all other incoming app requests.
 */
class NovaCoreServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any package services.
     *
     * @return void
     */
    public function boot()
    {
        Nova::booted(BootNova::class);

        if ($this->app->runningInConsole()) {
            $this->app->register(BaseNovaServiceProvider::class);
        }

        if (! $this->app->configurationIsCached()) {
            $this->mergeConfigFrom(__DIR__.'/../../config/nova.php', 'nova');
        }

        Route::middlewareGroup('nova', config('nova.middleware', []));
        Route::middlewareGroup('nova:api', config('nova.api_middleware', []));

        $this->app->make(HttpKernel::class)
                    ->pushMiddleware(ServeNova::class);

        $this->app->afterResolving(NovaRequest::class, function ($request, $app) {
            if (! $app->bound(NovaRequest::class)) {
                $app->instance(NovaRequest::class, $request);
            }
        });

        $this->registerEvents();
        $this->registerResources();
        $this->registerJsonVariables();
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        if (! defined('NOVA_PATH')) {
            define('NOVA_PATH', realpath(__DIR__.'/../../'));
        }

        $this->app->singleton(ImpersonatesUsers::class, SessionImpersonator::class);

        $this->app->bind(QueryBuilder::class, function ($app, $parameters) {
            return new Builder(...$parameters);
        });
    }

    /**
     * Register the package events.
     *
     * @return void
     */
    protected function registerEvents()
    {
        tap($this->app['events'], function ($event) {
            $event->listen(Attempting::class, function () {
                app(ImpersonatesUsers::class)->flushImpersonationData(request());
            });

            $event->listen(Logout::class, function () {
                app(ImpersonatesUsers::class)->flushImpersonationData(request());
            });

            $event->listen(RequestReceived::class, function ($event) {
                Nova::flushState();
                Once::flush();

                $event->sandbox->forgetInstance(ImpersonatesUsers::class);
            });

            $event->listen(RequestHandled::class, function ($event) {
                Container::getInstance()->forgetInstance(NovaRequest::class);
            });
        });
    }

    /**
     * Register the package resources such as routes, templates, etc.
     *
     * @return void
     */
    protected function registerResources()
    {
        $this->loadViewsFrom(__DIR__.'/../../resources/views', 'nova');
        $this->loadTranslationsFrom(__DIR__.'/../../resources/lang', 'nova');

        if (Nova::runsMigrations()) {
            $this->loadMigrationsFrom(__DIR__.'/../../database/migrations');
        }

        $this->registerRoutes();
    }

    /**
     * Register the package routes.
     *
     * @return void
     */
    protected function registerRoutes()
    {
        Route::group($this->routeConfiguration(), function () {
            $this->loadRoutesFrom(__DIR__.'/../../routes/api.php');
        });
    }

    /**
     * Get the Nova route group configuration array.
     *
     * @return array{domain: string|null, as: string, prefix: string, middleware: string}
     */
    protected function routeConfiguration()
    {
        return [
            'domain' => config('nova.domain', null),
            'as' => 'nova.api.',
            'prefix' => 'nova-api',
            'middleware' => 'nova:api',
            'excluded_middleware' => [SubstituteBindings::class],
        ];
    }

    /**
     * Register the Nova JSON variables.
     *
     * @return void
     */
    protected function registerJsonVariables()
    {
        Nova::serving(function (ServingNova $event) {
            // Load the default Nova translations.
            Nova::translations(
                lang_path('vendor/nova/'.app()->getLocale().'.json')
            );

            Nova::provideToScript([
                'appName' => Nova::name() ?? config('app.name', 'Laravel Nova'),
                'timezone' => config('app.timezone', 'UTC'),
                'translations' => function () {
                    return Nova::allTranslations();
                },
                'userTimezone' => function ($request) {
                    return Nova::resolveUserTimezone($request);
                },
                'pagination' => config('nova.pagination', 'links'),
                'locale' => config('app.locale', 'en'),
                'algoliaAppId' => config('services.algolia.appId'),
                'algoliaApiKey' => config('services.algolia.apiKey'),
                'version' => Nova::version(),
            ]);
        });
    }
}

bootstrap/providers.php

 <?php

return [
    App\Providers\NovaCoreServiceProvider::class,
    App\Providers\NovaServiceProvider::class,
];
github-actions[bot] commented 8 months ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.