Custom 404 in Laravel 4.2 with Layout

12,183

Solution 1

This is my approach. Just add the following code to /app/start/global.php file

App::missing(function($exception)
{
    $layout = \View::make('layouts.error');
    $layout->content = \View::make('views.errors.404');
    return Response::make($layout, 404);
});

Solution 2

You can do the trick by adding something like this on global.php and create the necessary error views.

App::error(function(Exception $exception, $code)
{
    $pathInfo = Request::getPathInfo();
    $message = $exception->getMessage() ?: 'Exception';
    Log::error("$code - $message @ $pathInfo\r\n$exception");

    if (Config::get('app.debug')) {
        return;
    }

    switch ($code)
    {
        case 403:
            return Response::view( 'error/403', compact('message'), 403);

        case 500:
            return Response::view('error/500', compact('message'), 500);

        default:
            return Response::view('error/404', compact('message'), $code);
    }
});

You can also check some laravel-starter-kit packages available around and check how they do stuffs like that. Here is my version of laravel-admin-template

Solution 3

I scratched my head on this one for a bit, here is the solution:

In app/start/global.php:

App::error(function(Exception $exception, $code) {
    if ($exception instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
      Log::error('NotFoundHttpException Route: ' . Request::url() );
    }

    Log::error($exception);

    // HTML output on staging and production only
    if (!Config::get('app.debug'))
        return App::make("ErrorsController")->callAction("error", ['code'=>$code]);
});

(NOTE: The above snippet displays these custom error pages only on environments where debug mode is true. Set debug mode on your various environments in the appropriate app.php file: http://laravel.com/docs/4.2/configuration#environment-configuration)

In app/controllers/ErrorsController.php:

protected $layout = "layouts.main";

/*
|--------------------------------------------------------------------------
| Errors Controller
|--------------------------------------------------------------------------
*/

public function error($code) {
    switch ($code) {
        case 404:
            $this->layout->content = View::make('errors.404');
        break;

        default:
            $this->layout->content = View::make('errors.500');
        break;
    }
}

}

(NOTE: protected $layout = "layouts.main"; refers to my master layout, which is named 'main'. Your master layout may be named something else, such as 'master'.)

Lastly, create app/views/errors/404.blade.php and app/views/errors/500.blade.php and place whatever HTML you want for the error pages in there. It will automatically be wrapped in layouts.main!

Missing pages and 500 internal errors will now automatically display custom error pages, with the layout. You can manually invoke an error page from any controller by calling: return App::make("ErrorsController")->callAction("error", ['code'=>404]); (replace 404 with whatever error code you want)

Solution 4

I can't tell you if this is the best approach or considered best practice but like you I was frustrated and found an alternative solution using the callAction method in Illuminate\Routing\Controller.

app/start/global.php

App::missing(function($exception)
{
    return App::make("ErrorController")->callAction("missing", []);
});

app/controllers/ErrorController.php

<?php

class ErrorController extends BaseController {

    protected $layout = 'layouts.master';

    public function missing()
    {
        $this->layout->content = View::make('errors.missing');
    }
}

Hope it helps!

Solution 5

I know I'm late to the party, but as this question remains unanswered and ranks relatively high in search results for the query, "Laravel 404 Error in exception handler". Only because this SO page remains a common problem and there is no solution marked I wanted to add more information and another possible solution for many users.

When you follow the methods offered here and elsewhere and use the app/start/global.php file to implement App:error() it should be noted that the base_controller is instantiated AFTER this file so any variables you might be passing into your normal view files (eg. $user) are not set. If any of those variables are referenced in the template you're extending, you get the error.

You can still extend your template if you revisit your view files and check if the variables are set using isset() and handle the false condition by setting defaults.

For example;

@yield('styles')
<style type="text/css">
body{
    background-color: {{ $user->settings->bg_color }};
}
</style>

The above will throw the error reported because there is no $user object at the time this is executed. The usual exception handler would provide more detail, but that's disabled out of necessity to actually show the 404 page so you get almost nothing to go on. However, if you use isset() or empty() you can accommodate the case.

<style type="text/css">
body{
    @if(isset($user))
        background-color: {{ $user->settings->bg_color }};
    @else
        background-color: #FFCCFF;
    @endif
}
</style>

This simple solution doesn't help if you've got a number of references in your top level layout or header file. You might need to change your @extends from your core layout, to a custom one. Eg. views/layouts/errors.blade.php.

Then in your 404.blade.php you would do something like this

@extends('layouts.errors')

And create views/layouts/errors.blade.php with new headers not dependent on $user (or whatever).

Hope that helps.

Share:
12,183

Related videos on Youtube

Sas Sam
Author by

Sas Sam

Updated on September 14, 2022

Comments

  • Sas Sam
    Sas Sam over 1 year

    My page uses global layout and there are many views with own controllers which are using this layout. The view called from controller action like this:

    class NewsController extends BaseController {
    
      protected $layout = 'layouts.master';
    
      public function index()
      {
        $news = News::getNewsAll();
    
        $this->layout->content = View::make('news.index', array(
            'news' => $news
        ));
      }
    }
    

    I would like to create a custom 404 page in the same way because I need the normal page layout for nested custom 404 design. Is it possible somehow? The issue is that I cannot set the HTTP status code to 404 from controller, so it's just a soft-404 yet. I know that the proper way would be send the Response::view('errors.404', array(), 404) from filter.php in App::missing() but I cannot set the layout there just the view which is not enough. Or am I wrong and it's possible somehow?

    Thanks!

    Update: I've created a Gist for this problem with the files what I use in the project. Maybe it helps more to understand my current state.

    • Marwelln
      Marwelln
      Response::view('layouts.errors', ['content' => View::make('errors.404')], 404) will use a layout file and have the $content variable set to the contents of errors.404.
  • Sas Sam
    Sas Sam over 9 years
    I follow this official way and I'm using the same logic in my project: laravel.com/docs/4.2/templates
  • Sas Sam
    Sas Sam over 9 years
    Thanks but your solution (like many others) is not using the layout for 404. It works but not like how I want. I have a layout (header, footer, etc. in layout for every view), stuff in BaseController and I want to throw the 404 page with 404 HTTP Status code from ErrorController like I showed above in my Gist example.