Laravel installation in sub-folder and horizon not working

1.5k views Asked by At

I have installed the Laravel in sub-folder and is trying to install the horizon. After routing to "test.com/sub-folder/horizon", all the design in broken and also the internal links are pointing to main domain instead of main-domain-without-subfolder.

After the search, it seems to be the known issue which is already reported in github issue

Has there is any work around to make horizon work when Laravel is installed in sub-folder?

3

There are 3 answers

3
Rafael Gálvez-Cañero On

I have a solution that only involves PHP.

The issue, as pointed out by @Isaiahiroko, is the basePath defined for Horizon's interface. That code is in Laravel\Horizon\Http\Controllers\HomeController::index(). The idea is this: we are going to pass to Laravel's service container our own implementation of that controller that will override the basePath definition passed to Horizon's interface.

Create a new controller with code like this:

<?php

namespace App\Http\Controllers;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\Http\Controllers\HomeController;

class HorizonHomeController extends HomeController
{
    /**
     * Overrides default horizon route to support subdirectory hosting.
     */
    public function index ()
    {
        // We use a plain request to check for the base url.
        $request = request();

        // Set up our base path. 
        $base_path = Str::substr($request->getBasePath(), 1);
        if (!empty($base_path)) {
            $base_path .= '/';
        }

        // Patch default horizon variables with our own base path.
        $variables = Horizon::scriptVariables();
        $variables['path'] = $base_path . config('horizon.path');

        // Render horizon's home view.
        return view('horizon::layout', [
            'assetsAreCurrent'       => Horizon::assetsAreCurrent(),
            'horizonScriptVariables' => $variables,
            'cssFile'                => Horizon::$useDarkTheme ? 'app-dark.css' : 'app.css',
            'isDownForMaintenance'   => App::isDownForMaintenance(),
        ]);
    }
}

What's left is telling Laravel's service container that when Horizon's HomeController is requested, it should provide our HorizonHomeController class. In your AppServiceProvider, at the end of the register() method, set this up:

// [...]
class AppServiceProvider extends ServiceProvider
{
  // [...]
  /**
   * Register any application services.
   *
   * @return void
   * @throws InvalidConfiguration
   */
  public function register()
  {
    // [...]
    
    // Horizon's subdirectory hack
    $this->app->bind(
      Laravel\Horizon\Http\Controllers\HomeController::class, 
      App\Http\Controllers\HorizonHomeController::class
    );
  }
  
  // [...]
}

After that, you should be able to browse to http(s)://<your-host>/<your-sub-dir>/horizon normally.

Considerations:

To me this feels cleaner that patching a compiled js, which also has the downside that needs to be re-applied every time Horizon is updated (this can be mitigated with a post-update script in composer, tho). Also, for additional points, this solution is only overriding the method that renders the view, but not the route, which means all of Horizon's authentication mechanisms (middlewares and gates) are working exactly as described in the documentation.

0
Ahsaan Yousuf On

As pointed out by @Isaiahiroko, you can achieve dynamic sub-directory handling (instead of hard coding your directory) with one line of code change in your public/vendor/horizon/app.js file

Search for window.Horizon.basePath="/"+window.Horizon.path and replace it with

window.Horizon.basePath=document.location.pathname.replace(new RegExp(window.Horizon.path+'.*$'), '')+window.Horizon.path
1
Isaiahiroko On

If you desperately need to do this, here is a hack:

  1. In public\vendor\horizon\app.js, search for window.Horizon.basePath
  2. replace window.Horizon.basePath="/"+window.Horizon.path; with window.Horizon.basePath="/[you sub-directoy]/"+window.Horizon.path;

It should work...until you run update one day and it mysteriously stop working.