How to use custom middleware with Laravel 404 exception - laravel-5

In my Laravel 5.2 app I have custom middleware called 'cart' that I use to keep track of the users cart contents across different routes.
It looks like this:
class CartMiddleware
{
public function handle($request, Closure $next)
{
$cart = new Cart();
$cart_total = $cart->total();
view()->composer(['layouts.main'], function ($view) use ($cart_total) {
$view->with('cart_total', $cart_total);
});
return $next($request);
}
}
Route::group(['middleware' => ['cart']], function () {
Route::get('cart', 'CartController#show');
});
When ever my app raises a 404 exception the 404.blade.php view cannot render because it is missing the $cart_total that is supplied by the 'cart' middleware.
Is there a way to assign this 'cart' middleware to my exception?
if ($e instanceof HttpException) {
if ($request->ajax()) {
return response()->json(['error' => 'Not Found'], 404);
}
return response()->view('errors.404', [], 404);
}
return parent::render($request, $e);

At Laravel 5.4 and probably some older ones you can modify the file app/exceptions/Handler.php and the function render like this:
if( is_a( $exception, \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class ) ) {
return redirect()->route( 'error_404' );
}
// ... old code in the function ...
in this way every 404 errors are redirected to certain real route that acts like other routes of site.
You may also submit any data from current request to show a reasonable error at the target.

Related

Laravel feature test using middleware

I'm trying to make a feature test for the start page of my Laravel application.
Route:
Route::domain('{subdominio}.'.env("APP_DOMAIN"))->middleware('mapNumserie')->group(function () {
Route::get('/', 'FarmaciaController#show')->middleware('modomtofarmacia')->name('inicio');
})
For some reasons, I use a middleware mapNumserie where I map the subdomain aganist a serial number $idfarmacia in order to use it after in controller:
public function handle($request, Closure $next){
try{
$route = $request->route();
$idfarmacia = \App\Farmacia::where('subdominio', $route->subdominio)->value('numserie');
$route->setParameter('idFarmacia', $idfarmacia);
$route->forgetParameter('subdominio');
}
catch (\Exception $e){
abort(404);
}
return $next($request);
}
In my ModoMtoFarmacia middleware I just check that an app instance is not in maintenance mode or disabled:
public function handle(Request $request, Closure $next){
$farmacia = \App\Farmacia::find($request->idFarmacia);
if($farmacia->inactiva){
abort(404);
}
if(!$farmacia->modomantenimiento){
return $next($request);
}
else{
return response()->view('errors.503')->setStatusCode(503);
}
}
In my controller symply get some data using this $idfarmacia and return a view:
public function show(Request $request, $idfarmacia) {
$data =
//Query...
if($data){
return view('inicio', ['data' => $data]);
}
else{
abort(404);
}
}
With a very simple test method I hope to validate a 200 response :
public function test_example(){
$response = $this->get('http://mysubdomain.domain.prj');
$response->assertStatus(200);
}
But when I run the test I get:
Expected response status code [200] but received 404.
The used URL works correctly in web environment, but the test fails. Where am I wrong?
Finally a found the problem in my phpunit.xml settings, which caused an error in the first query (also in any other), in this case the one found in the MapNumserie middleware:
$idfarmacia = \App\Farmacia::where('subdominio', $route->subdominio)->value('numserie');
I had to set up properly the values of DB_DATABASE and DB_CONNECTION

Only load route with existent parametre

Im doing a crud, i want to show item data using id, i have this in web.php:
Route::get('update/{id}', 'CrudController#update');
How can I deny that the user changes the id in the path to one that does not exist? That shows only those that exist and those that do not, that do not load?
In your update method, you can do the following:
public function update($id)
{
MyModel::findOrFail($id);
//...perform other actions
}
It will throw a 404 response if the requested $id is a non-existent one.
Then you can catch it if you want in the render() method of app\Exceptions\Handler.php:
use Illuminate\Database\Eloquent\ModelNotFoundException;
.
.
.
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException) {
if ($request->wantsJson()) {
return response()->json([
'data' => 'Resource not found'
], 404);
} else {
abort(404);
}
}
return parent::render($request, $exception);
}
Or, If you do not want to go through all the trouble of configuring it in the handler, you could also do:
public function update($id)
{
if (! $model = MyModel::find($id)) {
abort(404);
}
//...perform other actions with $model
}
The abort(404) method takes the user to the default Page not found page of laravel, which is an appropriate thing to do.

Different Response(JSON and webpage) in API and Website for Laravel 404 and 500?

I want to show the different response for API and website. In api response I want to show json response with 404 and 500 for type of exception mainly for routes.
If a user try to request a route and route not found I want to show a response in json response for API and webpage for website.
I know and try the code into app/Exceptions/Handler.php
public function render($request, Exception $exception)
{
if ($exception instanceof NotFoundHttpException) {
if ($request->expectsJson()) {
return response()->json(['error' => 'Not Found'], 404);
}
return response()->view('404', [], 404);
}
return parent::render($request, $exception);
}
https://laravel.com/docs/5.4/errors#http-exceptions
but failed can anybody help me how can I set different responses for error pages.
Expects JSON is about headers, i do not like that solution for API errors, you can access the API through a browser. My solution is most of the times to filter by the URL route, because it starts with "api/...", which can be done like so $request->is('api/*').
If you have your routes that are not prefixes with /api, then this will not work. Change the logic to fit with your own structure.
public function render($request, Exception $exception)
{
if ($exception instanceof NotFoundHttpException) {
if ($request->is('api/*')) {
return response()->json(['error' => 'Not Found'], 404);
}
return response()->view('404', [], 404);
}
return parent::render($request, $exception);
}
Just wanted to add an alternative to the above answers, which all seem to work as well.
After having the same problem and digging deeper, I took a slightly different approach:
your exception handle calls parent::render(...). If you look into that function, it will render a json response if your request indicates that it wantsJson() [see hints how that works here]
now, to turn all responses (including exceptions) to json I used the Jsonify Middleware idea from here, but applied it to the api MiddlewareGroup, which is by default assigned to RouteServiceProvider::mapApiRoutes()
Here is one way to implement it (very similar to referenced answer from above):
Create the file app/Http/Middleware/Jsonify.php
<?php
namespace App\Http\Middleware;
use Closure;
class Jsonify
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ( $request->is('api/*') ) {
$request->headers->set('Accept', 'application/json');
}
return $next($request);
}
}
Add the middleware reference to your $routeMiddleware table of your app/Http/Kernel.php file:
protected $routeMiddleware = [
...
'jsonify' => \App\Http\Middleware\Jsonify::class,
...
];
In that same Kernel file, add the jsonify name to the api group:
protected $middlewareGroups = [
...
'api' => [
'jsonify',
'throttle:60,1',
'bindings',
],
...
];
Result is that the middleware gets loaded for any requests that fall into the 'api' group. If the request url begins with api/ (which is slightly redundant I think) then the header gets added by the Jsonify Middleware. This will tell the ExceptionHandler::render() that we want a json output.
No need to hustle again for Laravel upgrade. You just need to define this method in the routes/api.php
Route::fallback(function(){
return response()->json(['message' => 'Not Found!'], 404);
});
I'm using Laravel 5.5.28, and am adding this in app/Exceptions/Handler.php
public function render($request, Exception $exception)
{
// Give detailed stacktrace error info if APP_DEBUG is true in the .env
if ($request->wantsJson()) {
// Return reasonable response if trying to, for instance, delete nonexistent resource id.
if ($exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
return response()->json(['data' => 'Resource not found'], 404);
}
if ($_ENV['APP_DEBUG'] == 'false') {
return response()->json(['error' => 'Unknown error'], 400);
}
}
return parent::render($request, $exception);
}
This expects that your API calls will be having a header with key Accept and value application/json.
Then a nonexistent web route returns the expected
Sorry, the page you are looking for could not be found
and a nonexistent API resource returns a JSON 404 payload.
Found the info here.
You could combine this with the answer looking for the instance of NotFoundHttpException to catch the 500. I imagine, however, that the stack trace would be preferred.
finally found this for 9.x
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
}
source: https://laravel.com/docs/9.x/errors#rendering-exceptions
try this.
public function render($request, Exception $exception)
{
if ($request->ajax()) {
return \Response::json([
'success' => false,
'message' => $exception->getMessage(),
], $exception->getCode());
} else {
return parent::render($request, $exception);
}
}

Redirect to login page on "page not found" - Laravel 5.4

I want to make so that if the user is not logged in and the page is not found (unexisting route), the user to be redirected to the login page instead to show the 404 page. If the user is logged in then the 404 page should be displayed. I'm using Laravel 5.4.
Use this in your Exceptions/Handler.php
public function render($request, Exception $e)
{
if ($e instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException){
return response(redirect(url('/')), 404);
}
return parent::render($request, $e);
}
You can listen for any exception in App\Exceptions\Handler#render:
if ($e instanceof NotFoundHttpException) {
if ( \Auth::guest() ) {
return redirect()->to('/login');
}
return response()->view('errors.404', [], 404);
}
Don't forget use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; at the top of your file.
EDIT
You have to enable the session for all routes so you can have the user data on not found routes. In your app/Http/Kernel.php add the following items to the $middleware array:
protected $middleware = [
....
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
The correct way to implement this would be as follows;
Route::group(['middleware' => ['auth']], function() {
// add your routes here;
});
Single route;
Route::get('profile', function () {
// Only authenticated users
})->middleware('auth');

Laravel 5.2 Authentication error: Accessing logged in page directly through URL

I am using Laravel 5.2 and I have Authenticate.php file in:
\vendor\laravel\framework\src\Illuminate\Auth\Middleware
I need to authenticate my user so that, the user can't access the logged in page by just writing it in URL. How to do it?
When I searched, the solutions were for the Authenticate file which was in Middleware folder inside Http, which was a little different.
Please tell me how to do it. I tried:
if(Auth::guard($guard) -> guest()){
if ($request -> ajax()){
return response['Unauthorized',401];
} else{
return redirect() -> guest('login');
}
}
return $next($request);
}
}
How to do it? please tell me options. Any suggestion is invited.
And, Thank you in advance... :)
The solutions you found while searching are correct, you should actually edit your middlewares inside app/Http/Middleware if you checked out the documentation here, the right way to do it is:
You have to update this middleware app/Http/Middleware/RedirectIfAuthenticated.php which has this function
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/admin');
}
return $next($request);
}
What you can do in your case, you can use the same if clause in your current handle function, so it looks like this:
if (Auth::guard($guard)->check()) {
return redirect('/admin');
}
if (Auth::guard($guard)->guest()) {
if ($request->ajax()) {
return response['Unauthorized',401];
} else {
return redirect()->guest('login');
}
}
return $next($request);
Put all the routes that only the logged in users can access into Auth Middleware group:
Route::group(['middleware' => ['auth']], function () {
// Routes here
});

Resources