Where do you set the public directory in Laravel 5?

Published 2 years ago by Marwelln

I have my public directory named public_html instead of public. So where do I change this setting?

I have a packages that uses $this->app['path.public'], but that package is trying to use public instead of what I want, public_html. So how do I tell Laravel 5 to use public_html?

Best Answer (As Selected By Marwelln)
DoubleD

You could open index.php (in your public directory) and change:

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__.'/../bootstrap/app.php';

// set the public path to this directory
$app->bind('path.public', function() {
    return __DIR__;
});

Now you dont need to change your public path when your public directory has changed.

Elixir has a hardcoded public path! Do not forget to set the changed public path in the Elixir config.

RemiC
RemiC
2 years ago (37,975 XP)

You can override the public path using ioc container :

App::bind('path.public', function() {
    return base_path().'/public_html';
});
Marwelln

Thanks mate. I added it to AppServiceProvider.php.

<?php namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider {

    ...

    public function register()
    {
        $this->app->bind('path.public', function() {
          return base_path().'/public_html';
        });
    }

}
DoubleD

You could open index.php (in your public directory) and change:

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__.'/../bootstrap/app.php';

// set the public path to this directory
$app->bind('path.public', function() {
    return __DIR__;
});

Now you dont need to change your public path when your public directory has changed.

Elixir has a hardcoded public path! Do not forget to set the changed public path in the Elixir config.

patrickbrouwers

I placed it inside the bootstrap/app.php file. I tried placing it inside a service provider, but then you can't use public_path() in a config file, which makes packages like pingpong modules or stylist not working properly because they keep publishing to /public

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__ . '/../')
);

$app->bind('path.public', function ()
{
    return base_path() . '/public_html';
});
elfamine

changing the public folder name seems like a bit of a hustle for me (I had to edit elixir version.js then I realized that elixir was still looking for my assets in the public folder instead of the renamed public folder)

I used a symlink :

ln -s public public_html

w1n78
w1n78
2 years ago (15,555 XP)

how would you set this based on APP_ENV? locally, the default public folder location applies but when i deploy to production, it's different and varies project to project since it's a shared environment/one server, multiple apps.

in laravel 4 i had to mess with index.php and bootstrap files coz i had a conditional statement that checks the value of APP_ENV. this was also tricky when running php artisan from the CLI, luckily there's a flag you can use to set the ENV during runtime.

it would be great if it can be set in the .env file. is this possible?

cbj4074

As a new Laravel user, I find it staggering that there is no simple, elegant, centralized, and environment-aware means by which to define both the "private" and the "public" filesystem paths that are referenced throughout the application.

While it may make sense to nest the public directory beneath the application root for source-control and packaging/distribution purposes, that is an unrealistic structure in a real-world deployment scenario.

The scenario that the OP describes is far more realistic, wherein the "public" directory is actually the web-server's "document root", and the rest of the application resides above and outside of the document root.

The default directory structure inspires less capable users to dump the entire application (including everything outside of the public directory) into the web-server's document root. This practice should be discouraged, as it exposes a broad attack-vector: the ability to access application resources via HTTP.

One might argue that a properly-configured server does not expose the application to unnecessary risk when Laravel is deployed as-is, but there is no guarantee as to the environment's viability. If PHP is upgraded and the process goes awry, thereby causing the web-server to present .php files as raw file downloads, an active attacker could conceivably "scrape" most or all source code. This type of mis-configuration has the potential to expose application details, ranging from filesystem paths to various credentials (cringe).

My research on this subject has yielded five possible approaches to using a more sensible "public" path (and I'm sure there are others):

1.)

Using a symbolic link (or junction point, on NTFS volumes)

This method is not portable. While it may be an acceptable solution for a solo developer, in a collaborative development environment, it's a non-option because it doesn't travel with the application source-code and is therefore cumbersome to implement consistently.

2.)

Using IoC container in /app/Providers/AppServiceProvider.php

This is not a bad approach, but as @patrickbrouwers notes, it suffers from a considerable flaw: the public_path() helper function remains unaffected in the context of configuration files, which causes the public path to be defined incorrectly in many third-party packages.

Also, if I were going to implement this approach, I would create a new Service Provider, rather than modify the included AppServiceProvider class (only because doing so reduces the likelihood of having to merge-in upstream changes).

3.)

Using IoC container in /bootstrap/app.php

This works well enough -- until environment-detection via the .env file is necessary. Any attempt to access $app->environment() at this stage yields, Uncaught exception 'ReflectionException' with message 'Class env does not exist'. Furthermore, in order to minimize the impact of upstream changes, my preference is to limit customization to as few files as possible, which brings me to the next method.

4.)

Using IoC container in /index.php

Given that /public/index.php already requires modifications to function out-of-the-box on many systems (the fact that realpath() is not used in the require and require_once statements causes the application to fail fatally in environments in which PHP enforces open_basedir restrictions, which do not allow nor resolve relative path notations, such as ../), my preference would be to make this change (to the public path definition) in the same file.

But, as with the above method, attempts to perform environment-detection cause a fatal error in this context, because the required resources have not yet been loaded.

5.)

Using custom IoC container in /app/Providers/*.php, coupled with overriding public_path() helper function

I settled on this method because it is the only method that solves the third-party package configuration problem (mentioned in method #2) and accounts for environment-detection.

The public_path() helper function (defined in /vendor/laravel/framework/src/Illuminate/Foundation/helpers.php) is wrapped in if ( ! function_exists('public_path')), so, if a function by the same name is defined before this instance is referenced, it becomes possible to override its functionality.

Given that I have already had to modify index.php (due to the open_basedir problems that I explained in #4), I elected to make this change in index.php, too.

//Defining this function here causes the helper function by the same name to be
//skipped, thereby allowing its functionality to be overridden. This is
//required to use a "non-standard" location for Laravel's "public" directory.

function public_path($path = '')
{
    return realpath(__DIR__);
}

Next, I created a custom Service Provider (instead of using AppServiceProvider.php, the reasons for which I explain in #2):

php artisan make:provider MyCustomServiceProvider

The file is created at /app/Providers/MyCustomServiceProvider.php. The register() method can then be populated with something like the following:

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        if (env('PUBLIC_PATH') !== NULL) {
            //An example that demonstrates setting Laravel's public path.
            
            $this->app['path.public'] = env('PUBLIC_PATH');
            
            //An example that demonstrates setting a third-party config value.
            
            $this->app['config']->set('cartalyst.themes.paths', array(env('PUBLIC_PATH') . DIRECTORY_SEPARATOR . 'themes'));
        }
        
        //An example that demonstrates environment-detection.
        
        if ($this->app->environment() === 'local') {
            
        }
        elseif ($this->app->environment() === 'development') {
            
        }
        elseif ($this->app->environment() === 'test') {
            
        }
        elseif ($this->app->environment() === 'production') {
            
        }
    }

At this point, everything seems to be working over HTTP. And while all of the artisan commands that I've tried have worked as expected, I have not exhausted all possible CLI scenarios.

I welcome any comments, improvements, or other feedback regarding this approach.

Thanks to everyone above for contributing to this solution!

mtvs_dev

You shouldn't call \App::bind(...) inside index.php because in this way it wouldn`t be executed when you run the application from the command line or in the case of the queue jobs.

ijpatricio

So that it works from both Http and Console, add to your bootstrap/app.php

$app->bind('path.public', function() {
    return base_path().'/public_html';
});
cbj4074

@mtvs_dev Good point about not using App::bind() in index.php. And this reason is in addition to the reason I describe in my first post within this thread (item 4).

@iboinas There are problems with the approach you describe above. Most notably, the problem that I address in item 3 above: this approach is incompatible with environment-detection via the .env file.

ferrolho

@cbj4074 I have tried to implement your solution but it does not work :/ It gives me the following error when I run php artisan serve:

[ErrorException]
chdir(): No such file or directory (errno 2)

Here's what I edited:

public/index.php: I added this:

function public_path($path = '')
{
    return realpath(__DIR__);
}

after this:

$app = require_once __DIR__ . '/../private/bootstrap/app.php';

private/app/Providers/PublicPathServiceProvider.php:

<?php namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class PublicPathServiceProvider extends ServiceProvider
{

    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        if (env('PUBLIC_PATH') !== NULL) {
            //An example that demonstrates setting Laravel's public path.
            $this->app['path.public'] = env('PUBLIC_PATH');

            // An example that demonstrates setting a third-party config value.
            //$this->app['config']
            //  ->set('cartalyst.themes.paths',
            //      array(env('PUBLIC_PATH') . DIRECTORY_SEPARATOR . 'themes'));
        } else {
            $this->app['path.public'] = base_path() . '/../public';
        }

        // Possible environment changes
        if ($this->app->environment() === 'local') {

        } elseif ($this->app->environment() === 'development') {

        } elseif ($this->app->environment() === 'test') {

        } elseif ($this->app->environment() === 'production') {

        }
    }

}
cbj4074

@ferrolho , I apologize for the long-delayed reply. I could have sworn that I had enabled notifications for this thread, but, apparently, I had not.

I don't use PHP's built-in HTTP server via Laravel, so I had not tried php artisan serve, but I just tried it on my development server and the HTTP server at least starts:

# php artisan serve
Laravel development server started on http://localhost:8000/

I receive a 500 error when I try to browse to http://localhost:8000, but that could be for so many different reasons. I have a particularly complex stack configuration (out of necessity), so I'm not at all surprised that PHP's built-in HTTP server doesn't work for me out-of-the-box. I probably need to pass it a path, at a minimum, but, unfortunately, I can't spare the time to troubleshoot it further.

I won't belabor the issues that Taylor (and others) raise in this discussion, but I urge you to consider using Homestead, plain-old VirtualBox, or something better suited to the task:

https://github.com/laravel/laravel/commit/80fb944e45801cec81b459f73892dbfc80c39de6

But let's try to work through your issue, nonetheless.

Firstly, this bit should be placed at the top of index.php, not the bottom:

function public_path($path = '')
{
    return realpath(__DIR__);
}

But in looking back at my notes regarding this subject, I appear to have gone a slightly different direction some time after my initial post in this thread.

I removed that function in favor of what seems to be a slightly more involved, although less error-prone, approach.

I've prepared a GitHub Gist of the changes that I typically make to index.php:

https://gist.github.com/cbj4074/9bb210706f8f2fdd61b0

All one should have to do is change lines 23 and 24 to suit his environment. The default values are intended to work with a "stock" Laravel configuration.

Regarding the Service Provider, your code looks okay to me.

You remembered to add the Service Provider to the 'providers' array in /config/app.php, correct?

If the revisions to your index.php don't solve the problem, I'd need additional details regarding the error that you receive:

[ErrorException]
chdir(): No such file or directory (errno 2)

I suspect that this is related to using PHP's built-in HTTP server without specifying a path to the document root. Because I don't use it, I'm not sure how php artisan serve sets the web-server's document root, but you might experiment with starting the server manually and see if you can get it to behave. Something like this:

php -S localhost:8000 -t public/

where public/ represents the correct path on your particular system. If you manage to get that to work, then we may have yet one more place within the Laravel source to override the public path definition.

Please do let us know what you find! Again, sorry for the late reply!

inyansuta

Now I created new project (Laravel 5.1) and just I rename "public" folder to "www" and everything works fine, nothing need be set ... How is this possible?

FrancoM

@inyansuta did you moved the application files outside the web root?

inyansuta

@FrancoM I did not move application files, application files are still beyond the standard root of the web ... As I say, I only renamed the folder public (to www), I have not changed anything, and everything works.

Sign In or create a forum account to participate in this discussion.