4

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.

4
  • Do you want to show another view if $news is empty? Commented Oct 26, 2014 at 15:12
  • @Marwelln We already have a longer conversation here but still no solution. Commented Oct 26, 2014 at 16:16
  • 1
    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. Commented Oct 26, 2014 at 16:21
  • It throws Call to undefined function view() error. If I change it to Response::view('layouts.master', ['content' => View::make('errors.missing')], 404); then the error will be Error in exception handler: Undefined variable: shared (View:master.blade.php) Commented Oct 26, 2014 at 16:44

7 Answers 7

10

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); }); 
Sign up to request clarification or add additional context in comments.

Comments

5

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

1 Comment

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.
1

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!

Comments

1

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.

Comments

1

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)

Comments

1

At the top of 404.blade.php you can extend your master layout @extends('layouts.master').

1 Comment

I follow this official way and I'm using the same logic in my project: laravel.com/docs/4.2/templates
0

OK, this is how you can achieve what you're after (amend as required).

App::error(function(Symfony\Component\HttpKernel\Exception\NotFoundHttpException $exception, $code) use ($path, $referer, $ip) { // Log the exception Log::error($exception); // This is a custom model that logs the 404 in the database (so I can manage redirects etc within my app) ErrorPage::create(['destination_page' => $path, 'referer' => $referer, 'ip' => $ip]); // Return a response for the master layout $layout = Response::view('layouts.frontend'); // Instantiate your error controller and run the required action/method, make sure to return a response from the action $view = App::make('Namespace\Controllers\Frontend\ErrorsController')->notFound(); // Merge data from both views from the responses into a single array $data = array_merge($layout->original->getData(), $view->original->getData()); // There appears to be a bug (at least in Laravel 4.2.11) where, // the response will always return a 200 HTTP status code, so // set the status code here. http_response_code(404); // Return your custom 404 error response view and pass the required data and status code. // Make sure your error view extends your master layout. return Response::view('404', $data, 404); }); 

So, essentially what we're doing here is returning a response for both the master layout and the custom 404/error view, then retrieving the data from the view objects of those responses, combining the data into a single array and then returning a response and passing our custom 404/error view, the data and the HTTP status code.

NOTE: There appears to be a bug (at least in Laravel 4.2.11) where the response will always return a 200 HTTP status code, regardless of what you pass to either view() or make(). With that said you need to manually set the response code using http_response_code(404).

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.