One place for hosting & domains

      Handlers

      Creating A Laravel 404 Page Using Custom Exception Handlers

      Introduction

      PHP Exceptions are thrown when an unprecedented event or error occurs. As a rule of thumb, an exception should not be used to control the application logic such as if-statements and should be a subclass of the Exception class.

      Being unprecedented, an exception can be thrown at any point or time of our application.

      Laravel provides a convenient exception handler class that checks for all exceptions thrown in a Laravel application and gives relevant responses. This is made possible by the fact that all Exceptions used in Laravel extend the Exception class.

      One main advantage of having all exceptions caught by a single class is that we are able to create custom exception handlers that return different response messages depending on the exception.

      In this tutorial, we will look at how to create a custom exception handler in Laravel 5.2 and how to return a 404 page depending on the Exception.

      In Laravel 5.2, all errors and exceptions, both custom and default, are handled by the Handler class in app/Exceptions/Handler.php with the help of two methods.

      The report method enables you to log raised exceptions or parse them to error logging engines such as bugsnag or sentry which we will not delve into in this tutorial.

      The render method responds with an error message raised by an exception. It generates an HTTP response from the exception and sends it back to the browser.

          
          public function render($request, Exception $e)
          {
              return parent::render($request, $e);
          }
      

      We can however override the default error handling with our own custom exception handler.

      
      public function render($request, Exception $e)
      {
          if ($e instanceof CustomException) {
              return response()->view('errors.custom', [], 500);
          }
      
          return parent::render($request, $e);
      }
      

      Under the hood, Laravel does its own handling checks to determine the best possible response for an exception. Taking a look at the parent class (Illuminate\Foundation\Exceptions\Handler), the render method generates a different response depending on the thrown Exception.

          
          public function render($request, Exception $e)
          {
              if ($e instanceof HttpResponseException) {
                  return $e->getResponse();
              } elseif ($e instanceof ModelNotFoundException) {
                  $e = new NotFoundHttpException($e->getMessage(), $e);
              } elseif ($e instanceof AuthenticationException) {
                  return $this->unauthenticated($request, $e);
              } elseif ($e instanceof AuthorizationException) {
                  $e = new HttpException(403, $e->getMessage());
              } elseif ($e instanceof ValidationException && $e->getResponse()) {
                  return $e->getResponse();
              }
      
              if ($this->isHttpException($e)) {
                  return $this->toIlluminateResponse($this->renderHttpException($e), $e);
              } else {
                  return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
              }
          }
      

      In this section, we will create an inbuilt Laravel error by intentionally raising an exception.

      To do this, we will try to fetch records that do not exist from a model using the firstOrFail() Eloquent method.

      Go ahead and set up a simple SQLite database. Luckily, Laravel ships with a User model and a corresponding users table. Simply do the following.

      1. Create a new Laravel project.
      2. Update your .env file to have DB_CONNECTION to be sqlite and the only database parameter.
      3. Create a database.sqlite file in the database directory. This is the default SQLite database as configured in config/database.php
      4. Run php artisan migrate on the route of your Laravel project. This will set up a users table in the database.

      We will then add a route and a controller to get the first user in our users table who just so happens not to exist.

      app/Http/routes.php

      Route::get('/user', [
          'uses' => 'SampleController@findUser',
          'as' => 'user'
      ]);
      

      App/Http/Controllers/SampleController.php

           
          public function findUser()
          {
              $user = User::firstOrFail();
              return $user->toArray();
          }
      

      Running this on the browser will return a ModelNotFoundException error response.

      NotFoundHttpException

      ModelNotFoundException

      With this exception, we can now add a custom handler that returns our own error message.

      We will modify the render method in app/Exceptions/Handler.php to return a JSON response for an ajax request or a view for a normal request if the exception is one of ModelNotFoundException or NotFoundHttpException.

      If it is neither of the two, we will let Laravel handle the exception.

         
          public function render($request, Exception $e)
          {
              
              if ($e instanceof ModelNotFoundException) {
                  
                  if ($request->ajax()) {
                      return response()->json(['error' => 'Not Found'], 404);
                  }
      
                  
                  return response()->view('errors.missing', [], 404);
              }
      
              return parent::render($request, $e);
          }
      

      Add a 404.blade.php file in resources/view/errors to contain our user feedback.

      <!DOCTYPE html>
      <html>
      <head>
          <title>User not found.</title>
      </head>
      <body>
          <p>You broke the balance of the internet</p>
      </body>
      </html>
      

      If we now refresh the page, we have the following message on our view with a 404 status code.

      Custom exception handling with 404 status code feedback

      NotFoundHttpException

      When a user visits an undefined route such as /foo/bar/randomstr1ng, a NotFoundHttpException exception, which comes as part of the Symfony package, is thrown.

      To handle this exception, we will add a second condition in the render method we modified earlier and return a message from resources/view/errors/missing.blade.php

      
          public function render($request, Exception $e)
          {
              
              
              if ($e instanceof ModelNotFoundException or $e instanceof NotFoundHttpException) {
                  
                  if ($request->ajax()) {
                      return response()->json(['error' => 'Not Found'], 404);
                  }
      
                  
                  return response()->view('errors.missing', [], 404);
              }
      
              return parent::render($request, $e);
          }
      

      Just like we did in the previous section, Laravel 5.2 makes it all too easy to create custom error pages based on the exception that was thrown.

      We can also simply generate a 404 error page response by calling the abort method which takes an optional response message.

      abort(404, 'The resource you are looking for could not be found');
      

      This will check for a corresponding resources/view/errors/404.blade.php and serve an HTTP response with the 404 status code back to the browser. The same applies to 401 and 500 error status codes.

      Depending on an application’s environment, you may want to show varying levels of error details. You can set the APP_DEBUG value in config/app.php to either true or false by changing it in your .env file.

      In most cases, you may not want your users in production to see detailed error messages. It is therefore good practice to set APP_DEBUG value to false while in a production environment.

      How To Define and Use Handlers in Ansible Playbooks



      Part of the Series:
      How To Write Ansible Playbooks

      Ansible is a modern configuration management tool that doesn’t require the use of an agent software on remote nodes, using only SSH and Python to communicate and execute commands on managed servers. This series will walk you through the main Ansible features that you can use to write playbooks for server automation. At the end, we’ll see a practical example of how to create a playbook to automate setting up a remote Nginx web server and deploy a static HTML website to it.

      In a nutshell, handlers are special tasks that only get executed when triggered via the notify directive. Handlers are executed at the end of the play, once all tasks are finished.

      In Ansible, handlers are typically used to start, reload, restart, and stop services. If your playbook involves changing configuration files, there is a high chance that you’ll need to restart a service so that the changes take effect. In this case, you’ll need to define a handler for that service and include the notify directive in any tasks that require that service handler.

      In a previous section of this series, you’ve seen how to use a template to replace the default Nginx page with a custom HTML landing page. In practice, when setting up your Nginx web server, you’re most likely going to include new server block files in your sites-available directory, create symbolic links, or change settings that require a server reload or restart.

      Considering such a scenario, this is how a handler to restart the Nginx service would look like:

      ...
        handlers:
          - name: Restart Nginx
            service:
              name: nginx
              state: restarted     
      

      To trigger this handler, you’ll need to include a notify directive in any task that requires a restart on the Nginx server.

      The following playbook replaces the default document root in Nginx’s configuration file using the built-in Ansible module replace. This module looks for patterns in a file based on a regular expression defined by regexp, and then replaces any matches found with the content defined by replace. The task then sends a notification to the Restart Nginx handler for a restart as soon as possible. What that means is, it doesn’t matter how many times you trigger the restart, it will only happen when all tasks are already finished executing and the handlers start running. Additionally, when no matches are found, no changes are made to the system, and for that reason the handler is not triggered.

      Create a new file called playbook-12.yml in your ansible-practice directory:

      • nano ~/ansible-practice/playbook-12.yml

      Add the following lines to the new playbook file:

      ansible-practice/playbook-12.yml

      ---
      - hosts: all
        become: yes
        vars:
          page_title: My Second Landing Page
          page_description: This is my second landing page description.
          doc_root: /var/www/mypage
      
        tasks:
          - name: Install Nginx
            apt:
              name: nginx
              state: latest
      
          - name: Make sure new doc root exists
            file:
              path: "{{ doc_root }}"
              state: directory
              mode: '0755'
      
          - name: Apply Page Template
            template:
              src: files/landing-page.html.j2
              dest: "{{ doc_root }}/index.html"
      
          - name: Replace document root on default Nginx configuration
            replace:
              path: /etc/nginx/sites-available/default
              regexp: '(s+)root /var/www/html;(s+.*)?$'
              replace: g<1>root {{ doc_root }};g<2>
            notify: Restart Nginx
      
          - name: Allow all access to tcp port 80
            ufw:
              rule: allow
              port: '80'
              proto: tcp
      
        handlers:
          - name: Restart Nginx
            service:
              name: nginx
              state: restarted
      

      Save and close the file when you’re done.

      One important thing to keep in mind when using handlers is that they are only triggered when the task that defines the notify trigger causes a change in the server. Taking this playbook into account, the first time it runs the replace task it will change the Nginx configuration file and thus the restart will run. In subsequent executions, however, since the string to be replaced is not present in the file anymore, the task won’t cause any changes and won’t trigger the handler execution.

      Remember to provide the -K option if you run this playbook, since it requires sudo permissions:

      • ansible-playbook -i inventory playbook-12.yml -u sammy -K

      Output

      BECOME password: PLAY [all] ********************************************************************************************** TASK [Gathering Facts] ********************************************************************************** ok: [203.0.113.10] TASK [Install Nginx] ************************************************************************************ ok: [203.0.113.10] TASK [Make sure new doc root exists] ******************************************************************** changed: [203.0.113.10] TASK [Apply Page Template] ****************************************************************************** changed: [203.0.113.10] TASK [Replace document root on default Nginx configuration] ********************************************* changed: [203.0.113.10] TASK [Allow all access to tcp port 80] ****************************************************************** ok: [203.0.113.10] RUNNING HANDLER [Restart Nginx] ************************************************************************* changed: [203.0.113.10] PLAY RECAP ********************************************************************************************** 203.0.113.10 : ok=7 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      If you look at the output, you’ll see the “Restart Nginx” handler being executed just before the end of the play. If you go to your browser and access the server’s IP address now, you’ll see the following page:

      Screenshot showing the new landing page after update

      In the next and final part of this series, we’ll connect all the dots and put together a playbook that automates setting up a remote Nginx server to host a static HTML website.



      Source link