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

Does the box support laravel? If so, can a box.json be provided? #1368

Open
zh7314 opened this issue May 14, 2024 · 5 comments
Open

Does the box support laravel? If so, can a box.json be provided? #1368

zh7314 opened this issue May 14, 2024 · 5 comments
Labels

Comments

@zh7314
Copy link

zh7314 commented May 14, 2024

Does the box support laravel? If so, can a box.json be provided? I have tried many times but have failed

@theofidry
Copy link
Member

I thought there was a doc entry for it, but looks like there is only a Symfony one.

To answer your question though, yes Laravel is supported. There should not be any framework that inherently cannot be supported. There is a few caveats of course, but it has more to do with PHARs than Box:

  • a PHAR is a readonly environment, that means if you have any write operation like a cache warmup, it needs to be done before shipping the code into the PHAR.
  • as per above, if you do write temporary files, you need to adjust your application to write somewhere on the disk and not in the PHAR.
  • it won't work for a webserver (PHARs do fine with it, but it's an old way with a very big and complicated API, I just removed all support for it when worked on Box because it was something I was not familiar with and not worth doing).

I think most of of what is written in the Symfony doc goes for Laravel, and as a matter of fact there is LaravelZero which is a more tailored distribution of Laravel for CLI applications and it uses Box for its compilation.

@zh7314
Copy link
Author

zh7314 commented May 15, 2024

Thank you. I tried the documentation about Symfony, but it still failed. I will try LaravelZero

@yoramdelangen
Copy link

yoramdelangen commented May 22, 2024

It is possible with Laravel, currently working on a project at work that uses Laravel within a PHAR for over 2 years now. There are some quirks and workarounds required! The most important one to overcome is realpath.

I 'patch' certain Laravel dependencies via a custom Application and inject/override specific files, try to leave as much as possible intact. So whenever we need to update libraries I hardly have to update patches what-so-ever.

@LuisAlejandro
Copy link

@yoramdelangen hi! I'm interested in such patches, do you have them available somewhere? Thanks

@yoramdelangen
Copy link

yoramdelangen commented Jan 16, 2025

@LuisAlejandro I cannot share you everything, but I can share snippets and talk you through it.. please note this is from Laravel <11.. Planning on upgrading the project soon to Laravel 11..

We have used this kind of structure since Laravel 8 and no issues during upgrades (yet).

1. Adjust storage folder location

You want everything from your storage folder to be stored outside of your PHAR file.
I created a simple function, that gives me the root_path from outside of the PHAR:

function root_path(?string $path = null): string
{
    static $root = null;

    if ($root === null) {
        $isWorker = $_ENV['APP_RUNNING_WORKER'] ?? false;
        $workdir = $_ENV['LARAVEL_WORKDIR'] ?? \Phar::running(false);

        // when running in worker mode, we should run from phar directory instead of cwd
        $base = $isWorker ? $workdir : getcwd();

        if (onWindows()) {
            if (\Phar::running(false)) {
                $base = dirname(\Phar::running(false));
            }
            $base = str_replace('\\', '/', $base);
        }

        $cwd = str_replace(['/public', '/my-phar.phar'], '', $base);
    }

    return str_replace('//', '/', $cwd.'/'.$path);
}

When Bootstrapping Laravel I adjusted certain paths like:

$rootPath = rtrim(root_path(), '/');
$app = new App\Application(
    dirname(__DIR__) // internal PHAR path
);
$app->useEnvironmentPath($rootPath); // environment file is outside of the PHAR
$app->useStoragePath(root_path('storage')); // make sure everything related to the storage is outside of my PHAR.

2. Create patches for parts/packages

I needed to create "patches" for the following packages/Laravel parts, mainly because they use the realpath function. What I did was creating a "patches" folder and created a "custom" namespace for each patch like:

  • PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider for using JWT. Patch namespace became Patch\PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider allowing me to extend the original service-provider and overwrite the methods I needed.
  • Illuminate\Foundation\Bootstrap\LoadConfiguration::getConfigurationFiles() became Patch\Illuminate\Foundation\Bootstrap\LoadConfiguration::getConfigurationFiles(). Finder was using realpath.
  • Illuminate\Database\Migrations\Migrator::getMigrationFiles and resolvePath uses realpath and/or needed some adjustments

There are a couple more vendor packages I needed to adjust but thats specific to my project, this will give you an idea.

3. Custom Application instance for applying the patches

In the Bootstrap part maybe you noticed I had a App\Application thats my custom Application.

use Illuminate\Foundation\Application as LaravelApplication;
use Patch\Illuminate\Database\Migrations\Migrator as PatchedMigrator;

class Application extends LaravelApplication
{
    // manually register the "orginal" namespace + class
    const PATCHES = [
        'Illuminate\Foundation\Bootstrap\LoadConfiguration',
        // .... others
    ];
    
    /**
     * Instantiate a concrete instance of the given type.
     *
     * @param  Closure|string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     * @throws \Illuminate\Contracts\Container\CircularDependencyException
     */
    public function build($concrete)
    {
        if (is_string($concrete) && in_array($concrete, static::PATCHES, true)) {
            $concrete = 'Patch\\'.$concrete;
        }

        return parent::build($concrete);
    }
   /**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        // patch fix on migrator, because its not a namespace+class
        // but a name, so hacky fix it
        if ($abstract === 'migrator') {
            $concrete = $this->patchMigrator();
        }

        // ⬇️⬇️⬇️⬇️⬇️ ORIGINAL ⬇️⬇️⬇️⬇️⬇️

       // COPIED OVER THE ORIGINAL CONTENTS OF THE `bind` method...
    }

    /**
     * Fix finder for database migrations, otherwise its using the `realpath` function
     * for lookup migration files.
     */
    private function patchMigrator(): Closure
    {
        return function ($app) {
            $repository = $app['migration.repository'];

            return new PatchedMigrator($repository, $app['db'], $app['files'], $app['events']);
        };
    }
}

This is not the entire file! just snippets of it.. with the most important changes necessary..

4. Custom Stub file

I need to adjust the stub file for the PHAR. Otherwise when running certain code it was outputted to the command-line instead of to the browser (e.g. phpinfo())

// custom stub, not entirely
<?php
// when requested on CLI...
if (\PHP_SAPI === 'cli') {
    Phar::mapPhar();

    include 'phar://'.__FILE__.'/artisan';
} else {
    $mimes = [
        'phps' => Phar::PHPS, // pass to highlight_file()
        // ....
        'php' => Phar::PHP, // parse as PHP
        'inc' => Phar::PHP, // parse as PHP
        // ... more mimes here for images, clips, styles etc...
        // these mimes are allowed to pass through to the PHAR as 
        // files in the PHAR
    ];

    // Important to pass all traffic directly to the public/index.php otherwise
    // all traffic is being handled as "command-line" commands
    $web = 'public/index.php';
    Phar::interceptFileFuncs();
    set_include_path('phar://'.__FILE__.\PATH_SEPARATOR.get_include_path());
    Phar::webPhar(null, $web, null, $mimes);

    $route = $web;
    $file = 'phar://'.__FILE__.'/public'.$_SERVER['REQUEST_URI'];

    // make sure assets will be loaded directly and only from the public folder
    if (is_dir($file) && is_file(str_replace('//', '/', $file.'/index.html'))) {
        $route = $file.str_replace('//', '/', $file.'/index.html');
    }
    include 'phar://'.__FILE__.'/'.$route;
}

__HALT_COMPILER();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants