Tag: Laravel

  • 📂

    How I deploy a Laravel project to a Kubernetes Cluster

    WIP: post not yet finalized.

    This is an overview of how I would setup a Kubernetes cluster, along with how I would set up my projects to deploy to that cluster.

    This is a descriptive post and contains nothing technical in the setting up of this infrastructure.

    That will come in future posts.

    Services / Websites I use

    Digital Ocean

    Within Digital Ocean, I use their managed Kubernetes, Managed database, DNS, S3-compatible spaces with CDN and Container registry.

    Github

    Github is what I use for my origin repository for all IaC code, and project code. I also use the actions CI features for automated tests and deployments.

    Terraform

    I use Terraform for creating my infrastructure, along with Terraform cloud for hosting my Terraform state files.

    Setting up the infrastructure

    I firstly set up my infrastructure in Digital Ocean and Github using Terraform.

    This infrastructure includes these resources in Digital Ocean: Kubernetes Cluster, Spaces bucket and Managed MySQL database. As well as two Action secrets in Github for: Digital Ocean Access Token and the Digital Ocean Registry Endpoint.

    After the initial infrastructure is setup — the Kubernetes cluster specifically, I then use Helm to install the nginx-ingress-controller into the cluster.

    Setting up a Laravel project

    I use Laravel Sail for local development.

    For deployments I write a separate Dockerfile which builds off of a php-fpm container.

    Any environment variables I need, I add them as a Kubernetes secret via the kubectl command from my local machine.

    Kubernetes deployment file

    All the things that my kubernetes cluster needs to know how to deploy my Laravel project are in a deployment.yml file in the project itself.

    This file is used by the Github action responsible for deploying the project.

    Github action workflows

    I add two workflow files for the project inside the ./.github/workflows/ directory. These are:

    ci.yml

    This file runs the full test suite, along with pint and larastan.

    deploy.yml

    This file is triggered only on the main branch, after the Tests (ci) action has completed successfully.

    It will build the container image and tag it with the current git sha.

    Following that, it will install doctl and authenticate with my Digital Ocean account using the action secret for the secret token I added during the initial Terraform stage.

    Then it pushes that image to my Digital Ocean container registry.

    The next step does a find and replace to the project’s deployment.yml file. I’ve included a snippet of that file below:

    YAML
          containers:
          - name: davidpeachcouk
            image: <strong><IMAGE></strong>
            ports:
            - containerPort: 9000

    It replaces that <IMAGE> placeholder with the full path to the newly-created image. It uses the other Github secret that was added in the Terraform stage: the Digital Ocean Registry Endpoint.

    Finally it sets up access to the Kubernetes cluster using the authenticated doctl command, before running the deployment.yml file with the kubectl command. After which, it just does a check to see that the deployment was a success.


  • 📂

    Don’t stop building

    I really enjoy building scripts for my own workflow.

    I wish I had the skills to build things in the real world, but until then I’ll keep building stuff in the digital space only.

    Although I love working with PHP and Laravel, it is Bash that has re-ignited a passion in me to just build stuff without thinking its got to work towards being some kind of “profitable” side project.

    Don’t. Stop. Building.


  • 📂 ,

    Given, When, Then — how I approach Test-driven development in Laravel

    Laravel is an incredible PHP framework and the best starting point for pretty much any web-based application (if writing it in PHP, that is).

    Along with it’s many amazing features, comes a beautiful framework from which to test what you are building.

    For the longest time I cowered at the idea of writing automated tests for what I built. It was a way of working that was brought in by a previous workplace of mine and my brain fought against it for ages.

    Since that time a few years ago I slowly came to like the idea of testing. Then over the past year or so I have grown to love it.

    I have met some people that are incredibly talented developers, but for me made the prospect of automated testing both confusing and intimidating.

    That was until I came across Adam Wathan’s excellent Test Driven Laravel course. He made testing immediately approachable and broke it down into three distinct phases (per test): “Given”, “When” and “Then”. Also known as “Arrange”, “Act” and “Assert”. I forget which phrase he used, but either way the idea is like this.

    “Given” this environment

    The first step is to set up the “world” in which the test should happen.

    One example would be if you were building an API that would return PlayStation game data to you. In order to return games, there must be games there to return.

    In Laravel we have factories that we can create for quickly creating test entries for our models. Here is an example of a Game model that uses its factory to create a game for us:

    $game = Game::factory()->create([
        'title' => 'The Last of Us part 2',
        'developer' => 'Naughty Dog',
    ]);

    “When” this thing I want to test happens

    Here you would do the thing that you are testing.

    Maybe that is sending some data to an API endpoint in your application. Or perhaps you are testing a single utility class can do a specific action, so you call the method on that class.

    Here, let’s continue the idea of return games from an api call. We’ll use the $game variable from the previous example and access its ID to build our GET endpoint:

    $response = $this->json('get', '/api/games/' . $game->id,);

    Here the $response variable gets the response from the json get call, allowing you to later make assertions against it.

    “Then” I should see this particular outcome

    In this last step you would make assertions against what has happened. This could be checking if a record exists in a database with specific values, or asserting that an email got sent.

    Basically anything you need to make sure happened, or didn’t happen, for you to be sure you are getting your desired functionality.

    Let’s finish our game example by asserting that we got json back with the expected data. We do this by calling the appropriate method off of the $response variable from the previous example.

    $response->assertJson([
        'title' => 'The Last of Us part 2',
        'developer' => 'Naughty Dog',
    ]);

    The full example test code

    $game = Game::factory()->create([
        'title' => 'The Last of Us part 2',
        'developer' => 'Naughty Dog',
    ]);
    $response = $this->json('get', '/api/games/' . $game->id,);
    $response->assertJson([
        'title' => 'The Last of Us part 2',
        'developer' => 'Naughty Dog',
    ]);

    Much more to explore

    There is so much to automated testing and I’m still relatively new to it all myself.

    You can “fake” other things in your application in order to not run live things in tests. For example when testing emails are sent you don’t really want to be actually sending emails when you run your tests. Therefore you would “fake” the functionality of sending the mail.

    I hope that this post has been an easy-to-follow intro to how I myself approach testing.

    I have found that even as my tests have gotten more complex in certain situations, I still always stick to the same structural idea:

    1. Given this is the world my code lives in.
    2. When I perform this particular action.
    3. Then I should see this specific outcome.


  • 📂 ,

    Preview Laravel’s migrations with the pretend flag

    Here is the command to preview your Laravel migrations without running them:

    cd /your/project/root
    php artisan migrate --pretend

    Laravel’s migrations give us the power to easily version control our database schema creations and updates.

    In a recent task at work, I needed to find out why a particular migration was failing.

    This is when I discovered the simple but super-useful flag --pretend, which will show you the queries that Laravel will run against your database without actually running those migrations.


  • 📂

    Migrating my website to Statamic

    I love Laravel.

    I also really like WordPress, for what it is. So when it came to originally putting my personal site together I just wanted to get a simple WordPress site together.

    I have attempted to build my own website and blog in Laravel from scratch multiple times over the years. I even stuck with a build for a while but ultimately went back to WordPress.

    My issue was only down to the fact that I wanted to write more in my own time and found I spent most my time tinkering.

    But I really love Laravel.

    So imagine my joy when I came across Statamic. Statamic is a CMS package that can be installed into a Laravel site and just works seamlessly alongside you Laravel code.

    I am in the process of rebuilding my personal site and will be getting it live as soon as I can.

    I think I will migrate my current site to a new domain, something like “davidpeach.me”, and then use the 4042302 technique to ensure my old posts are still found as I migrate the posts over.

    I’m really looking forward to getting creative with Statamic and then layering on all of the excellent Laravel features as a way to learn as much, and refresh my mind, about my favourite framework.


  • 📂

    If I start rebuilding my website in Laravel yet again, I really need to see it through and commit to it. If the last couple of weeks have taught me anything it’s that I love working with Laravel.

    Let’s see how this goes.


  • 📂

    How I would set up Laravel with Docker

    This is a quick brain dump for myself to remember how I set up Laravel with Docker. Hopefully it can help others out also.

    I tried to avoid Docker for the longest time due to the ease of just running php artisan serve. However, when you have some dependancies that your site will rely on, Docker can be helpful — especially when having multiple developers — in getting up and running with the whole codebase easier.

    This post assumes you have setup a basic Laravel project on a Linux computer, and have both Docker and Docker Compose installed locally.

    What will this project use?

    This is only a basic example to get up and running with the following dependancies. You can add more items to your docker-compose.yml file as you need to.

    Note: whatever you choose to name each extra service in your docker-compose.yml file, use its key as the reference point in your .env file.

    • The main site codebase
    • A MySQL database
    • an NGINX webserver
    • PHP

    docker-compose.yml

    Have a file in the project root, named `docker-compose.yml

    version: "3.3"
    
    services:
      mysql:
        image: mysql:8.0
        restart: on-failure
        env_file:
          - .env
        environment:
          MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
          MYSQL_DATABASE: ${MYSQL_DATABASE}
      nginx:
        image: nginx:1.15.3-alpine
        restart: on-failure
        volumes:
          - './public/:/usr/src/app'
          - './docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro'
        ports:
          - 80:80
        env_file:
          - .env
        depends_on:
          - php
      php:
        build:
          context: .
          dockerfile: './docker/php/Dockerfile'
        restart: on-failure
        env_file:
          - .env
        user: ${LOCAL_USER}

    Dockerfile

    Have a Dockerfile located here: ./docker/php/Dockerfile. I keep it in a separate folder for tidiness.

    # ./docker/php/Dockerfile
    FROM php:7.2-fpm
    
    RUN docker-php-ext-install pdo_mysql
    
    RUN pecl install apcu-5.1.8
    RUN docker-php-ext-enable apcu
    
    RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
        && php -r "if (hash_file('SHA384', 'composer-setup.php') === '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
        && php composer-setup.php --filename=composer \
        && php -r "unlink('composer-setup.php');" \
        && mv composer /usr/local/bin/composer
    
    WORKDIR /usr/src/app
    
    COPY ./ /usr/src/app
    
    RUN PATH=$PATH:/usr/src/app/vendor/bin:bin
    

    default.conf

    Have a default.conf file for the project’s nginx container saved here: ./docker/nginx/default.conf

    # ./docker/nginx/default.conf
    server {
     server_name ~.*;
    
     location / {
         root /usr/src/app;
    
         try_files $uri /index.php$is_args$args;
     }
    
     location ~ ^/index\.php(/|$) {
         client_max_body_size 50m;
    
         fastcgi_pass php:9000;
         fastcgi_buffers 16 16k;
         fastcgi_buffer_size 32k;
         include fastcgi_params;
         fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
     }
    
     error_log /dev/stderr debug;
     access_log /dev/stdout;
    }

    Add the necessary variables to your .env file

    There are some variables used in the docker-compose.yml file that need to be added to the .env file. These could be added directly, but this makes it more straightforward for other developers to customise their own setup.

    MYSQL_ROOT_PASSWORD=root
    MYSQL_DATABASE=example
    LOCAL_USER=1000:1000
    

    The MYSQL_ROOT_PASSWORD and MYSQL_DATABASE are self-explanatory, but theLOCAL_USER variable refers to the user id and group id of the currently logged in person on the host machine. This normally defaults to 1000 for both user and group.

    If your user and/or group ids happen to be different, just alter the variable value.

    Note: find out your own ids by opening your terminal and typing id followed by enter. You should see something like the following:

    uid=1000(david) gid=1000(david) groups=1000(david),4(adm),27(sudo),1001(rvm)

    uid and gid are the numbers you need, for user and group respectively.

    Run it

    Run the following two commands separately then once they are finished head to http:localhost to view the running code.

    Note: This setup uses port 80 so you may need to disable any local nginx / apache that may be running currently.

    docker-compose build
    docker-compose up -d

    Any mistakes or issues, just email me.

    Thanks for reading.


  • 📂

    How to easily set a custom redirect in Laravel form requests

    In Laravel you can create custom request classes where you can house the validation for any given route. If that validation then fails, Laravel’s default action is to redirect the visitor back to the previous page. This is commonly used for when a form is submitted incorrectly – The visitor will be redirected back to said form to correct the errors. Sometimes, however, you may wish to redirect the visitor to a different location altogether.

    TL;DR (Too long; didn’t read)

    At the top of your custom request class, add one of the following protected properties and give it your required value. I have given example values to demonstrate:

    protected $redirect = '/custom-page'; // Any URL or path
    protected $redirectRoute = 'pages.custom-page'; // The named route of the page
    protected $redirectAction = 'PagesController@customPage'; // The controller action to use.
    

    This will then redirect your visitor to that location should they fail any of the validation checks within your custom form request class.

    Explaination

    When you create a request class through the Laravel artisan command, it will create one that extends the base Laravel class Illuminate\Foundation\Http\FormRequest. Within this class the three protected properties listed above are initialised from line 33, but not set to a value.

    Then further down the page of the base class, on line 127 at the time of writing, there is a protected method called getRedirectUrl. This method performs a series of checks for whether or not any of the three redirect properties have actually been set. The first one it finds to be set by you, in the order given above, is the one that will be used as the custom redirect location.

    Here is that getRedirectUrl method for your convenience:

    /**
    * Get the URL to redirect to on a validation error.
    *
    * @return string
    */
    protected function getRedirectUrl()
    {
        $url = $this->redirector->getUrlGenerator();
    
        if ($this->redirect) {
            return $url->to($this->redirect);
        } elseif ($this->redirectRoute) {
            return $url->route($this->redirectRoute);
        } elseif ($this->redirectAction) {
            return $url->action($this->redirectAction);
        }
    
        return $url->previous();
    }
    
    

    Do you have any extra tips to add to this? Let me know in the comments below.

    Thanks.


  • 📂

    Digging into laravel is fun. Just made my first little generator tool. Laravel makes everything so bloody easy!


  • 📂

    Laravel Blade push and stack

    Laravel’s blade view compiler is second to none. I’ve used a couple of different templating engines and blade is by far my favourite.

    Including Partials

    The way in which we include partials of views within our main views is as follows:
    @include(‘partials.my-first-partial’)
    It will inject that partial’s content in the specified place.

    Defining Sections

    Within our views, we define “sections” with the following syntax:

    @section(‘section_name’)
    
        The section’s content within here
    
    @stop

    And we can define as many sections as we need for our project.

    When the same section is used in multiple places within one compilation

    Imagine we have master template file as such:

    // layouts.main.blade.php
    <!doctype html>
    
    ...
    @yield(‘partials.form’)
    ...
    
    @yield(‘custom_scripts’)
    
    

    Let’s suppose we have the following layout template that extends our main layout one and is including three partials. This example is a form template including its various inputs from separate partials. For my own website I have a different form for each of my post types and so I have the inputs in separate partials for easy reuse.

    // partials.form.blade.php
    @extends(‘layouts.main’)
    
    <form>@include(‘parials.form-title’)
    @include(‘parials.form-content’)
    @include(‘parials.form-tags’)</form>
    

    Let’s next suppose that in a couple of those partial input views you need to inject some custom scripting. This is a slightly contrived example, but it will illustrate the point.

    // partials.form-content.blade.php
    <textarea class="content" name="content"></textarea>
    
    @section(‘custom_scripts’)
    // dummy javascript as example
    $(‘.content’).doSomething();
    @stop
    
    // partials.form-tags.blade.php
    <select class="tags" name="tags">
    <option value="tagone">Tag One</option>
    <option value="tagtwo">Tag Two</option>
    <option value="tagthree">Tag Three</option>
    </select>
    
    @section(‘custom_scripts’)
    $(‘.tags’).doSomethingElse()
    @stop
    

    Now, when the form page gets compiled, only the first occurrence of the ‘custom_scripts’ section will be included.

    So what if you needed to be able to define this section in chunks across partials?

    Introducing Blade’s Push & Stack directives

    To give this functionality, Laravel does in fact have two little-known directives called ‘push’ and ‘stack’.

    They allow you to ‘stack up’ items across partials with the ‘push’ directive, which can then be echoed out together with the ‘stack’ directive.

    Here’s the above form example but with ‘push’ and ‘stack’ used in place of ‘section’ and ‘yield’.

    // layouts.main.blade.php
    <!doctype html>
    
    ...
    @yield(‘partials.form’)
    ...
    
    @stack(‘custom_scripts’)
    
    
    // partials.form-content.blade.php
    <textarea class="content" name="content"></textarea>
    
    @push(‘custom_scripts’)
    // dummy javascript as example
    $(‘.content’).doSomething();
    @endpush
    
    // partials.form-tags.blade.php
    <select class="tags" name="tags">
    <option value="tagone">Tag One</option>
    <option value="tagtwo">Tag Two</option>
    <option value="tagthree">Tag Three</option>
    </select>
    
    @push(‘custom_scripts’)
    $(‘.tags’).doSomethingElse()
    @endpush
    

    This will now compile all uses of the @push(‘custom_scripts’) and echo them out as one wherever you call @stack(‘custom_scripts’)

    When I was shown this technique by a mate at work, it blew my mind.

    Have fun.