# What is TALL Stack

**You can discuss pages on this site at** [**https://github.com/snapey/talltips/discussions**](https://github.com/snapey/talltips/discussions)

TALL stack describes applications with a preference for the following components;

* [Tailwind](https://tailwindcss.com/) - CSS Utility Framework
* [Alpine](https://github.com/alpinejs/alpine) - JS a lightweight declarative javascript framework
* [Laravel](https://laravel.com) - PHP based web application framework
* [Livewire](https://laravel-livewire.com/) - front end components that sync with back-end state without writing APIs

To quickly boilerplate an application for TALL stack, [check out the preset](https://github.com/laravel-frontend-presets/tall) maintained by @imliam

## TALL resources

For an *awesome* list of TALL resources, check  <https://github.com/blade-ui-kit/awesome-tall-stack>

{% embed url="<https://github.com/tanthammar/tall-forms>" %}

{% embed url="<https://github.com/MedicOneSystems/livewire-datatables>" %}


# Tailwind Resources

Collection of useful links and tutorials

{% embed url="<https://github.com/aniftyco/awesome-tailwindcss>" %}

{% embed url="<https://devdojo.com/tnylea/getting-started-with-tailwindcss>" %}

{% embed url="<https://devdojo.com/tnylea/creating-a-slider-with-tailwind-css>" %}

{% embed url="<https://heroicons.com/>" %}

{% embed url="<https://tailwind-gradient-designer.csspost.com/>" %}

### Newsletters

[Tailwind Weekly newsletter](https://www.getrevue.co/profile/tailwind-weekly)

### Blogs

### YouTube

[Tailwind CSS Tips, Tricks & Best Practices](https://youtu.be/nqNIy8HkEQ8) by Sam Selikoff

[Tailwind CSS Tutorials playlist](https://www.youtube.com/playlist?list=PLEhEHUEU3x5p8cxOJ27w20LffCknp935L) by Andre Madarang


# Swinging Bell Notification Icon

Create an animated notification bell with Tailwind

Using an SVG Bell icon and custom tailwind class, we can make a bell symbol that swings to draw the user's attention.

![](/files/-MErDILTOwdwUT9DARYo)

### Create the Icon

Not a requirement for this example (you could in-line the SVG) but here I have downloaded one of the Heroicons from <https://heroicons.dev/> and made it into a Laravel 7 component.

views/components/svg/bell.blade.php

```
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" {{ $attributes->merge(['class'=>'inline-block']) }}>
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
        d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9">
    </path>
</svg>
```

This uses most of the code copied from the heroicons site, but with the addition of merging any passed attributes.

### Create the blade content for the bell

```markup
@if($notifications->count() > 0)
    <span class="text-base tracking-tighter text-yellow-500">
        <x-svg.bell class="h-5 -mr-1 align-text-top animate-swing origin-top"/>
        <sup>{{ $notifications->count() }}</sup>
    </span>
@endif
```

### Create Tailwind Extension

In the code above, you will see a new class; `animate-swing`we add this using the tailwind.config.js file

```javascript
extend: {
      keyframes: {
        'swing': {
          '0%,100%' : { transform: 'rotate(15deg)' },
          '50%' : { transform: 'rotate(-15deg)' },
        }
      },
      animation: {
        'swing': 'swing 1s infinite'
      }
    }
```

The bell is rotated around a point that is in the centre top of the icon, starting at 15 degrees through 0 degrees to -15 degrees and back again over a 1 second duration.


# Styled Unordered Lists

use marker: to add colored discs

## The skinny on elegant lists:

<figure><img src="/files/Fl6cfahge7cF7aiZSou1" alt=""><figcaption></figcaption></figure>

To provided colored bullets, and indented list items use the following classes on the UL element;

```
<ul class="ml-4 space-y-4 list-disc marker:text-red-700">
```

`ml-4` to indent the (default) list-outside so that the bullets align with the left of the paragraph

`space-y-4` to add vertical spacing between all the `ul` children

`marker:text-red-700` sets the color of the disc element


# Alpine Resources

Useful Alpine resources

{% embed url="<https://github.com/alpinejs/awesome-alpine>" %}

{% embed url="<https://devdojo.com/tnylea/animations-with-alpine>" %}

{% embed url="<https://devdojo.com/tnylea/accessing-data-variables-from-alpinejs>" %}

{% embed url="<https://codecourse.com/watch/learn-alpine-js>" %}

### Laracasts

{% embed url="<https://laracasts.com/series/alpine-essentials>" %}


# Tabbed Content Using Alpine JS

Create tabbed page content with anchors to directly open any tab

Create the effect of tabbed page content, allowing any tab to be linked directly and validation errors returning to the same tab as the form.

![](/files/-MAkX-H8MBnw59ffFDuU)

## Create \<div> for each section of tabbed content

Selecting a tab will show this div's content and hide the others, creating the appearance of tabbed navigation;

```markup
<div x-show="tab == '#tab1'" x-cloak>
    <p>This is the content of Tab 1</p>
</div>

<div x-show="tab == '#tab2'" x-cloak>
    <p>This is the content of Tab 2</p>
</div>

<div x-show="tab == '#tab3'" x-cloak>
    <p>This is the content of Tab 3</p>
</div>
    
```

Name the tabs according to your use case.  Prefix the tab name with `#`, this will come in useful later

{% hint style="info" %}
Use x-cloak to prevent all the sections from appearing before Alpine starts
{% endhint %}

## Create the navigation

Create a set of clickable anchors that will tell alpine to change the value 'tab' so that the relevant Div is shown

```markup
<div class="flex flex-row justify-between">

    <a class="px-4 border-b-2 border-gray-900 hover:border-teal-300" 
      href="#" x-on:click.prevent="tab='#tab1'">Tab1</a>
      
    <a class="px-4 border-b-2 border-gray-900 hover:border-teal-300" 
      href="#" x-on:click.prevent="tab='#tab2'">Tab2</a>
      
    <a class="px-4 border-b-2 border-gray-900 hover:border-teal-300" 
      href="#" x-on:click.prevent="tab='#tab3'">Tab3</a>
      
</div>
```

## Create Alpine scope with x-data

Wrap the whole thing within a div which contains the `x-data` tag to initialise an instance of Alpine. Set the initial value of the tab to whatever tab you want to be displayed by default (assumed to be tab 1)

```markup
<div x-data="{ tab: '#tab1' }" class="">

    <!-- Links here -->
    
    <!-- Tab Content here -->
    
</div>

```

So the variable *tab* is set to '#tab1' .  When the Tab2 link is clicked, *tab* will be set to '#tab2' and since that section has x-show looking at the the boolean result of the comparison tab='#tab2' which will be true and the tab content will be shown.  All other tabs will evaluate false and not be shown.

{% hint style="info" %}
Add Tailwind's **transition** and **duration** classes to each tab to smooth the switching between tabs
{% endhint %}

## Use Hashtags to allow any tab to be opened directly

You have created a page with what appears to be tabbed content, however, you can only link to the initial page state (usually the first tab) and not to any of the additional tabs.  For example, the tabbed content could be part of a user's settings area.  One of the tabbed panels could present the option to change their password.  It would be nice to link directly to this tab instead of telling users to go to their settings then change to the correct tab.

We can improve this by setting the initial state of the tab with any hash that has been applied to the URL;

```markup
<div x-data="{ tab: window.location.hash ? window.location.hash : '#tab1' }">
```

The javascript property `window.location.hash` contains the # segment of the url, including the #

So, for instance, if the url is **mywebsite.com/settings#password** then the `window.location.hash` will contain '#password' .

By initialising our `tab` to the value of the hash, then that tab will be opened.  The ternary in the x-data statement allows a default to be specified. This is the reason we prefixed each of our tab names with #.

## Return to the same tab after Laravel validation error

If your tabbed panel contains a traditional form (not a Livewire form) then when a validation error occurs you will be redirected back to the page but with the first tab selected and not the tab that contains the form, thus the validation errors will not be visible.

The solution to this problem is to add the tab's hash to the form action;

```markup
<form class="" action="{{ route('student.password.change') }}#password" method="POST" >
```

Following a validation error, the user is redirected back to the page with the hash specified here added to the URL - thus selecting the appropriate tab.


# Checkbox component with SVG tick

Styling a checkbox by swapping out SVGs

The effect illustrated here, uses x-show to switch between two different SVGs, one being a circle and one being a circle with a check mark inside.

![](/files/-MAuUSHZ75M6eF0mGRAN)

```markup
@props(['label', 'name'])
<div class="flex items-center text-left" x-data="{checked: document.getElementById('{{ $name }}').checked}" >
    <div class="w-4/12"></div>
    <label class="w-8/12 pr-8 font-bold text-gray-100 cursor-pointer" for="{{ $name }}"
        x-on:click="checked = document.getElementById('{{ $name }}').checked" 
    >
        <x-svg.circle class="w-6" x-show="!checked"/>
        <x-svg.circle-check class="w-6" x-show="checked" x-cloak/>
        <input class="hidden mr-3" name="{{ $name }}" type="checkbox" id="{{ $name }}"/>
        <span>{!! $label !!}</span>
    </label>
</div>
```

Using `x-on:click` in the checkbox label updates the status of the `checked` Alpine attribute whenever the state of the checkbox changes.

The icons used were imported from Steve Schoger's heroicons and converted to Laravel 7 components. Check out <https://heroicons.dev/> for an easy way to find and copy the SVG you need.  The empty circle was created by taking one of the other circular icons and deleting one of the paths.&#x20;

The actual checkbox input is hidden, but remains in sync. The technique needs some [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) tags adding to ensure accessibility.

The component is included in the view with `name` and `label` props;

```markup
<x-inputs.checkbox name="terms" label="I Accept the Terms and Conditions <a class='text-teal-400 underline hover:text-teal-200' target='_blank' href='/terms-condition'>here</a>" class=""  />
```


# Dropdown animation

Dropdown menu effect using Tailwind and Alpine

![](/files/-MCLKFoLRR3GyucEtamB)

```markup
<div class="bg-gray-200 flex min-h-screen items-center ml-32">
  <div class="inline-block relative" x-data="{open: false}">
    <button @click="open = !open" class="focus:outline-none shadow cursor-pointer inline-block text-gray-700 hover:text-black flex border border-gray-400 rounded p-2 pl-3 pr-1 bg-gray-100" :class="{ 'shadow-none border-indigo-300': open}">
      @snapey
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" :class="{'rotate-180': open}" class="ml-1 transform duration-300 inline-block fill-current text-gray-500 w-6 h-6"><path fill-rule="evenodd" d="M15.3 10.3a1 1 0 011.4 1.4l-4 4a1 1 0 01-1.4 0l-4-4a1 1 0 011.4-1.4l3.3 3.29 3.3-3.3z"/></svg>
    </button>

    <ul x-show="open" class="bg-white absolute left-0 shadow w-40 rounded text-indigo-600 origin-top shadow-lg"
      x-transition:enter="transition ease-out duration-200"
      x-transition:enter-start="opacity-0 transform scale-y-50"
      x-transition:enter-end="opacity-100 transform scale-y-100"
      x-transition:leave="transition ease-in duration-300"
      x-transition:leave-end="opacity-0 transform scale-y-50"
    >
      <li><a href="#" class="py-1 px-3 block hover:bg-indigo-100">Profile</a></li>
      <li><a href="#" class="py-1 px-3 border-b block hover:bg-indigo-100">Billing</a></li>
      <li><a href="#" class="py-1 px-3 block hover:bg-indigo-100">Log out</a></li>
    </ul>
  </div>
</div>
```

Adapted from <https://www.jesper.dev/posts/creating-a-dropdown-with-alpinejs/>


# Create a Sliding Puzzle Captcha

An alternative to Google Recaptcha

SlidingCaptcha is a simple class that creates a sliding puzzle.  The user must align the pieces when submitting a form such as a registration or contact form.  It does not rely on any third party API and satisfies privacy concerns.&#x20;

{% hint style="info" %}
Updated for Intervention V3, which requires PHP 8+
{% endhint %}

![](/files/YEWCvWlzBvebt6hbPmd4)

The background to the puzzle is generated on-the-fly by Intervention image and passed to the view as inline images.

### SlidingCaptcha Service Class

Create a class called SlidingCaptcha.  Here I have created it in a `Services` folder

{% code title="SlidingCaptcha.php" %}

```php
<?php

namespace App\Services;

use Intervention\Image\Image;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
use Intervention\Image\Geometry\Factories\CircleFactory;

class SlidingCaptcha
{
    public $manager;

    public Image $top;

    public Image $bottom;

    public int $position;

    const CANVAS_HEIGHT = 200;

    const CANVAS_WIDTH = 4000;

    const CANVAS_BG = '#F0F0F0';

    public function __construct()
    {
        $this->manager = new ImageManager(new Driver());
        $this->generate();
    }

    private function generate()
    {
        $image = $this->createImage();

        $this->bottom = clone $image;
        $this->top = clone $image;

        $this->bottom->crop(2000, 50, 0, 50);

        $this->position = random_int(0, 160) * 10;  // ensures steps of 10

        $this->top->crop(400, 50, $this->position, 0);

        $this->position = 2000 - $this->position;

    }

    private function createImage()
    {
        $image = $this->manager->create(self::CANVAS_WIDTH, self::CANVAS_HEIGHT)->fill(self::CANVAS_BG);

        foreach (range(1, 50) as $x) {
            $image->drawCircle(
                random_int(0, self::CANVAS_WIDTH),   // x
                random_int(0, self::CANVAS_HEIGHT),  // y
                function (CircleFactory $circle) {
                    $circle->radius(random_int(20, (self::CANVAS_HEIGHT/2)-10)); // radius of circle in pixels
                    $circle->background($this->colours()); // background color
                    $circle->border('444444', 1); // border color & size
                });
        }

        $image->resize(self::CANVAS_WIDTH / 2, self::CANVAS_HEIGHT / 2);

        return $image;
    }

    private function colours()
    {
        return sprintf("rgba(%s, %s, %s, %s)",
            random_int(0, 255),  // range for R
            random_int(0, 255),  // range for G
            random_int(0, 255),  // range for B
            (rand(1, 8) / 10)    // range for opacity
        );
    }
}


```

{% endcode %}

Using the popular package Intervention Image (<https://image.intervention.io/v3>) a canvas is created which is twice the size we need.  I found making it the exact size it was too grainy.  The canvas is initially 4000px x 200px and contains 50 randomly spaced and coloured circles.

The image is then downsized to 2000px x 100px, and then split into two halves, top and bottom. Finally, the top image is cropped to 400px wide at a random position within the larger image.

### Using the Service Class

Call the Service in the controller that presents the form;

```php
        $sc = new SlidingCaptcha();

        session()->put('sc_position', $sc->position);

        return view('test')->withSlidingCaptcha($sc);
```

Here we pass the SlidingCaptcha object to the view.  It contains two objects for both parts of the puzzle, and the position within the full image where the top image was taken from.  This will be what the user needs to provide by sliding the puzzle.

### The blade view

The view is very simple, and uses Tailwindcss for styling and Alpinejs to allow the user to slide the puzzle.

```markup
<form action="{{ route('contact.create') }}" method="POST"> @csrf
    <!-- rest of your form here -->
    <div class="flex flex-col" x-data="{guess:400}" x-effect="$refs.bottom.style.backgroundPosition=guess+'px';">
        <div class="w-full mx-auto rounded-t-md" style="margin:0; height:50px; background-image:url('{{ $slidingCaptcha->top->toGif()->toDataUri() }}')"></div>
        <div class="w-full mx-auto shadow rounded-b-md" style="margin:0; height:50px; background-image:url('{{ $slidingCaptcha->bottom->toGif()->toDataUri() }}');" x-ref="bottom" ></div>
        <input type="range" name="guess" min="400" max="2000" step="10" x-model="guess" autocomplete="off" class="w-full mt-2 py-3 max-w-[400px]">
        @error('guess'){{ $message }}@enderror
    </div>
    <input type="submit" value="Send" class="px-4 py-2 rounded-lg bg-emerald-600 font-bold shadow-lg text-white mt-4 border"> 
</form>
```

The top half of the puzzle and the bottom half are stacked on top of each other and then a range input element provides the amount that the bottom image should be scrolled by.

Alpine links the value of the input slider with the position of the background so as the slider moves, so does the bottom image within its container.

When the two halves align, the user can try submitting the form

### Validating the input

When we created the SlidingCaptcha, we saved the `position` in session so that we can check it when the form is submitted with simple validation which can be added alongside your other validation rules.

```php
    public function create(Request $request)
    {
        $this->validate($request, [
            'guess' => ['required', Rule::in([session('sc_position')])],
        ],[
            'guess.in' => 'The puzzle must be aligned exactly'
        ]);
```

The guess from the form (the range input element) must match exactly, the position that was stored in session.  If it does then the user passed the Captcha challenge!


# Tabler Icons Component

Simple Laravel 7+ component to display one of the Tabler icon set

{% embed url="<https://github.com/tabler/tabler-icons>" %}

## The Component

{% code title="resources/views/components/tabler.blade.php" %}

```markup
<!-- tabler icon, pulls from public/tabler folder. Accepts:
    strokeWidth   defaults to "1"
    class         defaults to "inline-block relative h-2"
-->

<svg viewBox="0 0 24 24" stroke="currentColor" 
    stroke-width="{{ $strokeWidth ?? 1 }}" 
    class="inline-block relative h-2 {{ $class ?? '' }}" 
    {{ $attributes->except(['class','icon']) }} 
>
    
    <use xlink:href="/tabler/tabler-sprite-nostroke.svg#tabler-{{$icon}}" />

</svg>
```

{% endcode %}

Copy the component script into `tabler.blade.php` in the `resources/views/components` folder

## Install the icons

Copy the icon svg sprite file from Github tabler/tabler-icons

Locate the file `tabler-sprite-nostroke.svg` and copy it into the folder  `public/tabler/`

## Usage

Example usage:

Twitch icon with height 6 in the same colour as the parent element.

```markup
<x-tabler icon="brand-twitch" class="h-6" />
```

Bucket icon in blue-500 and pushed up to align with the bottom of the text. Default stoke width of 1 is increased to 2.

```markup
<x-tabler wire:click="hello" 
    icon="bucket" 
    class="bottom-1 h-6 text-blue-500" 
    strokeWidth="2" />
```

#### Notes:

Search for the **icon name** required at <https://tablericons.com/>

Always remember to close the component with the `/>`&#x20;

If new icons are added just download a fresh copy of the svg file from Github. The project is under active development and new icons are being added regularly.

The approach uses the sprite map file for the icon set which is rather large at 300K+ . You may prefer to download the individual icon files and use the example above to find the correct file. At the time of writing, only individual icon files with 2px stroke weight are provided.


# Password-less Login with Laravel 8+

Create a pass-phrase or 'magic-link' login system for Laravel 8 and Jetstream

#### ✅ Checked works with Laravel 10

## Introduction

Users don't manage passwords well. They forget them or choose easy to remember passwords then use that same password on every site they visit.  We then have to build features into our application to let them login when the password is forgotten or allow them to change the password at any time. We have to ensure we hold passwords securely even if our application is not that important because a user might be trusting us with their use-everywhere password.

Our applications are easier to manage with less support issues if we use a password-less login process.  Such schemes are popular on sites like Medium with their 'magic link' or Notion.so with their login code such as `jay-bawl-sack-lid`

There is a good discussion of these password-less login methods in this [article on medium](https://medium.com/@kelvinvanamstel/should-we-embrace-magic-links-and-leave-passwords-alone-c73db7007fc4).

So, how could we implement such a solution with a new Laravel 8 Jetstream project?

{% hint style="info" %}
This article is using Jetstream with Livewire  (this site is TALL stack focussed) but the principles should hold for Inertia also.&#x20;
{% endhint %}

## Prepare

I'm starting with a new Laravel 8 project, with Jetstream installed in Livewire flavour.

## Remove the requirement for passwords

Our first task is to remove passwords from the Login process.  To make this simple, we will give everyone a default password of 'password'.  It won't be used, but it prevents us having to make too many changes to the Fortify+Jetstream code.

### &#x20;Remove password fields from login and register forms

{% code title="resources/views/auth/login.blade.php" %}

```markup
            <div>
                <x-jet-label value="Email" />
                <x-jet-input class="block w-full mt-1" type="email" name="email" :value="old('email')" required autofocus />
            </div>
{{-- 
            <div class="mt-4">
                <x-jet-label value="Password" />
                <x-jet-input class="block w-full mt-1" type="password" name="password" required autocomplete="current-password" />
            </div>
--}}
            <input type="hidden" value="password" name="password" />

```

{% endcode %}

{% hint style="info" %}
*Note the additional line for creating a hidden password field with the value 'password'*
{% endhint %}

{% code title="resources/views/auth/register.blade.php" %}

```markup
            </div>
{{-- 
            <div class="mt-4">
                <x-jet-label value="Password" />
                <x-jet-input class="block w-full mt-1" type="password" name="password" required autocomplete="new-password" />
            </div>

            <div class="mt-4">
                <x-jet-label value="Confirm Password" />
                <x-jet-input class="block w-full mt-1" type="password" name="password_confirmation" required autocomplete="new-password" />
            </div>
--}}

            <div class="flex items-center justify-end mt-4">
```

{% endcode %}

### Create user with default password

Fortify actions are available in the app/Actions/Fortify folder. We can adjust the CreateNewUser.php file to use our default password.

{% code title="app/Actions/Fortify/CreateNewUser.php" %}

```php
public function create(array $input)
{
    Validator::make($input, [
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
         // 'password' => $this->passwordRules(),
    ])->validate();

    return User::create([
        'name' => $input['name'],
        'email' => $input['email'],
        'password' => Hash::make('password'),    //make($input['password']),
    ]);
}
```

{% endcode %}

Here, the password field is commented out of the validation, and the password added to the user record is just the hash of the string 'password'.

**Testing:**  We should now be able to register a user, and login without any password.  *We have built a very insecure application at this point!*

![](/files/-MHH7Idj1Qgv9GV4dK0g)

*Article sponsored by* [*SixTokens.com*](https://sixtokens.com)

## Create a source of pass-phrases

For this solution a passphrase is a combination of 3 or 4 words separated by hyphens. The words are sourced from a list[ published by the EFF](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases) and are chosen because they are short and easy to spell.

Rather than publish the code and word list here, you can access it via <https://github.com/snapey/passphrase>

* Create a folder within app called `Utility`
* Place **PassPhrase.php** and **wordlist.txt** in this folder

## Put pass-phrase into session when user logs in

When the user registers, Laravel will fire a `Registered` event and when the user is logged in either by the login form or the *remember* function, then a `Login` event is fired.  We are going to listen for these events and place our randomly generated pass-phrase into session. Ultimately, the user will only be allowed access to our application if they can provide the same code that we have in session.

### Create a listener

`php artisan make:listener RequirePassPhrase`

This uses our utility class to create a pass-phrase and store it along with an expiry timestamp.

{% code title="app/Listeners/RequirePassPhrase.php" %}

```php
<?php

namespace App\Listeners;

use App\Utility\PassPhrase;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;

class RequirePassPhrase
{
    protected $generator;
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct(PassPhrase $generator)
    {
        $this->generator = $generator;
    }

    /**
     * Handle the event.
     *
     * @param  object  $event
     * @return void
     */
    public function handle($event)
    {
        // don't need to interrupt the process if the user 
        // logged in with remember token
        if(auth()->viaRemember()) {
            return;
        }

        $passphrase = $this->generator->passPhrase(3);

        Session::put('passphrase', $passphrase);
        Session::put('passphrase_expiry', now()->addMinutes(15)->timestamp);

    }
}

```

{% endcode %}

With this; `$this->generator->passPhrase(3);` we create a phrase with three words.  Set this according to your preferences.  The EFF article mentioned earlier explains;&#x20;

> for *k* words chosen from a list of length *n*, there are *nk* possible passphrases of this type. It will take an adversary about *nk*/2 guesses on average to crack this passphrase.

Our wordlist is approximately 4100 words, so 3 words is  4100x4100x4100/2 = 34,400,000,000 guesses so don't go overboard with the number of words in the passphrase.

### Bind our Listener to Events

Listening for events is configured in the app\Providers\EventServiceProvider.  We add our listener for the two events.  We can remove the email verification listener as if the user can receive the passcode then they verified the email at the same time.

{% code title="app/Providers/EventServiceProvider.php" %}

```php
    protected $listen = [
        \Illuminate\Auth\Events\Registered::class => [
            \App\Listeners\RequirePassPhrase::class,
        ],
        \Illuminate\Auth\Events\Login::class => [
            \App\Listeners\RequirePassPhrase::class,
        ],
    ];

```

{% endcode %}

## Send the pass-phrase to our user

Having created the code and put it in session we need to send this to the user.  Notifications are the easiest to implement here, and you could, for instance, choose to send the code to the user by one of the other notification methods such as SMS text.

### Create the notification

`php artisan make:notification AdvisePassPhrase`

Adjust your new notification (use your own words as required)

{% code title="app/Notifications/AdvisePassPhrase.php" %}

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class AdvisePassPhrase extends Notification
{
    use Queueable;

    public $passphrase;

    public function __construct(string $passphrase)
    {
        $this->passphrase = $passphrase;
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->subject('Your login code for ' . config('app.name'))
            ->line('Here is your login PassPhrase which is valid for the next 15 minutes')
            ->line($this->passphrase)
            // ->action('Notification Action', url('/'))
            ->line('Thank you for using our application!');
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

```

{% endcode %}

### Call the Notification and provide the pass-phrase

In our earlier Listener, add a line to send the notification;

{% code title="app/Listeners/RequirePassPhrase.php" %}

```php
use App\Notifications\AdvisePassPhrase;


        Session::put('passphrase', $passphrase);
        Session::put('passphrase_expiry', now()->addMinutes(15)->timestamp);

        $event->user->notify(new AdvisePassPhrase($passphrase));
```

{% endcode %}

Line 7 is added to the earlier file

### Testing

Provided we have configured a mail service such as mailtrap, when we register or login, a mail should be received containing our passphrase.

## Accept and validate the pass-phrase

### Create Controller

`php artisan make:controller PassPhraseController`

{% code title="app/Http/Controllers/PassPhraseController.php" %}

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Contracts\LoginResponse;

class PassPhraseController extends Controller
{
    public function show()
    {
        return view('auth.passphrase');
    }

    public function store(Request $request)
    {

        if (Session::get('passphrase_expiry') < now()->timestamp ){
            Auth::logout();
            $this->clearSession($request);
            return redirect()->route('login')->withErrors(['email' =>['Your Passphrase has expired. Please login again']]);
        }

        if (strToLower($request->passphrase) != Session::get('passphrase')) {
            throw ValidationException::withMessages([
                'passphrase' => ['Sorry, that is not the correct passphrase. Please check your email for the latest message.'],
            ]);
        }

        $this->clearSession($request);

        return app(LoginResponse::class);
    }

    public function clearSession($request)
    {
        $request->session()->forget('passphrase');
        $request->session()->forget('passphrase_expiry');
    }
}

```

{% endcode %}

Call this from your routes file

{% code title="routes/web.php" %}

```php
use App\Http\Controllers\PassPhraseController;

//

Route::get('/login/confirm',[PassPhraseController::class,'show'])->name('login.confirm');
Route::post('/login/confirm',[PassPhraseController::class,'store'])->name('login.confirmation');
```

{% endcode %}

### Create a form for the capture of the pass-phrase

The easiest route with a new application is to just copy the Login view and edit a few of the fields;

{% code title="resources/views/auth/passphrase.blade.php" %}

```php
<x-guest-layout>
    <x-authentication-card>
        <x-slot name="logo">
            <x-authentication-card-logo />
        </x-slot>

        <x-validation-errors class="mb-4" />

        @if (session('status'))
            <div class="mb-4 font-medium text-sm text-green-600">
                {{ session('status') }}
            </div>
        @endif

        <form method="POST" action="{{ route('login.confirmation') }}">
            @csrf

            <div>
                <x-label value="Pass-phrase" for="passphrase" />
                <x-input class="block w-full mt-1" type="text" name="passphrase" :value="old('passphrase')" required autofocus />
            </div>
            
            <div class="flex items-center justify-end mt-4">
                <x-button class="ml-4">
                     Confirm
                </x-button>
            </div>
        </form>
    </x-authentication-card>
</x-guest-layout>

```

{% endcode %}

### Testing

* Visit the route `/login/confirm` and check you can see the form.
* Entering an invalid code should show the message that the code is incorrect
* Entering a valid code should direct to the home route
* Waiting 15 minutes and entering a code should report that the code has expired

## Add middleware to block access until pass-phrase accepted

We can create a middleware that checks the user's session.  If it contains a `passphase` key then the user is in the middle of logging in and should not be permitted to access the application.  We need to except the login routes from the middleware so that the user can access the login process.

### Make Middleware

`php artisan make:middleware PassPhraseGuard`

{% code title="app/Http/Middleware/PassPhraseGuard.php" %}

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class PassPhraseGuard
{

    // if the user's session contains a passphrase then we need to direct the user to the 
    // passphrase confirm route instead.
    // need to allow the user through to any login routes

    public function handle(Request $request, Closure $next)
    {
        $passphrase = $request->session()->get('passphrase', null);

        if (is_null($passphrase)) {
            return $next($request);
        }

        // passphrase set, still valid?

        if ($request->session()->get('passphrase_expiry') < now()->timestamp) {

            $request->session()->forget('passphrase');
            $request->session()->forget('passphrase_expiry');

            Auth::logout();

            return redirect('/');
        }

        if ($request->route()->named('login.*')) {
            return $next($request);
        }

        return redirect()->route('login.confirm');
    }
}

```

{% endcode %}

### Include the Middleware as a global route middleware

Include the new middleware in your web middleware stack

{% code title="app/Http/Kernel.php" lineNumbers="true" %}

```php
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Laravel\Jetstream\Http\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            \App\Http\Middleware\PassPhraseGuard::class,
        ],
```

{% endcode %}

*We added Line 10*

### Testing

* Once logged in, access to all pages should be blocked, directing the user to the confirm pass-phrase page.
* Landing on the site after 15 minutes should return to the guest mode
* Remember me should work as normal

## Cleaning up

### Remove references to passwords

In the config/fortify.php file, turn off the ability to reset and change passwords by commenting out the `resetPasswords` and `updatePasswords` features.

{% code title="config/fortify.php" %}

```php

    'features' => [
        Features::registration(),
        // Features::resetPasswords(),
        // Features::emailVerification(),
        Features::updateProfileInformation(),
        // Features::updatePasswords(),
        Features::twoFactorAuthentication(),
    ],
```

{% endcode %}

## Conclusion

In this article we created a password-less login process that uses a **pass-phrase** technique. [ In the second part of this article we will add a **magic-link** alternative](/laravel/password-less-login-with-magic-link-in-laravel-8).

## Feedback

If you have any suggestions how this article can be improved, **contribute to discussion at** [**https://github.com/snapey/talltips/discussions**](https://github.com/snapey/talltips/discussions)


# Password-less Login with Magic Link in Laravel 8

Provide users with a secure URL that automatically logs them in

This article follows on from code we built in [Password-less Login with Laravel 8](/laravel/passwordless-login). Be sure to read that first as it requires much of what we scaffolded there.

## Send the user an email containing a secure url

In the AdvisePassPhrase notification we are also going to add back the button that we commented out earlier and attach our secure URL to the button.

{% code title="app/Notifications/AdvisePassPhrase.php" %}

```php
use Illuminate\Support\Facades\URL;

//

        public function toMail($notifiable)
    {
        return (new MailMessage)
            ->line('Here is your login PassPhrase which is valid for the next 15 minutes')
            ->line($this->passphrase)
            ->line('Or click the button to access the site (opens in a new window)')
            ->action('Confirm', URL::temporarySignedRoute(
                'login.magiclink',
                now()->addMinutes(15),
                ['user' => $notifiable->id, 'code' => $this->passphrase]
            ))
            ->line('Thank you for using our application!');
    }
```

{% endcode %}

* Line 11 we create our button labelled 'Confirm' and pass it a temporary signed route.
* Line 12 is the named route that we will create in a moment where we check the signed route.
* Line 13 we set the expiry time as 15 minutes in the future.&#x20;
* Line 14, we are passing and securing the user ID and the passphrase

## Create a controller for the secure endpoint

`php artisan make:controller MagicLinkController`

This controller only needs one function. Its responsibility is to check the URL is still secure and has not been tampered with and then clear the session passphrase keys.

{% code title="app/Http/Controllers/MagicLinkController.php" %}

```php
use App\Models\User;
use Illuminate\Support\Facades\Auth;

//

    public function confirm(Request $request, User $user)
    {
        if (!$request->hasValidSignature() || Auth::guest()) {
            abort(401);
        }
        
        if ($request->code != $request->session()->get('passphrase')) {
            abort(401);
        }

        $request->session()->forget('passphrase');
        $request->session()->forget('passphrase_expiry');

        return app(LoginResponse::class);
    }
```

{% endcode %}

If the signature is valid and the user is still logged in then clear the passphrase from session and perform the default LoginResponse.

If the Link has already been used then show an error.

{% hint style="info" %}
This process will not work if the user completes the login form on one device but then clicks the link in a different device. Since they are not logged in on that device this URL will have no effect. You might want to add this as a warning to the email.
{% endhint %}

## &#x20;Add the route

```php
Route::get('/login/magic/{user}',[MagicLinkController::class,'confirm'])->name('login.magiclink');
```

Make sure the route name starts `login.` so that it can bypass the protection we placed in our earlier middleware.

## Testing

When we login and provide the email address, an email is sent containing a secure link.  Clicking this link within 15 minutes should remove the passphrase we are using as a guard in middleware.

Any longer than 15 minutes and they will see an error.

## Feedback

If you have any suggestions how this article can be improved, DM the author on Twitter [@snapey](https://twitter.com/snapey)


# Laravel Resources

### Within the Laravel.com domain

[Laravel Meetup](https://meetup.laravel.com/)

[Laravel secret blog](https://blog.laravel.com/)

[Laravel Snippet](https://blog.laravel.com/snippets)

[Jetstream](https://github.com/alexeymezenin/laravel-best-practices)

### Artisan

Crib Sheet by James Brookes <https://artisan.page/>

### Best Practices

{% embed url="<https://github.com/alexeymezenin/laravel-best-practices>" %}


# Laravel Breeze Login Conditional Redirect

When you want to redirect a user after login according to their role, and using Laravel Breeze

#### ✅ Checked works with Laravel 10

When using Breeze and having users with different roles (eg customer / administrator), you might want to redirect the user once they have authenticated.

With Breeze this is fairly simple since the authentication process is performed in the user's App and can be easily modified.

Locate the file app/Http/Controllers/Auth/AuthenticatedSessionController.php

Replace the last line of the `store()` method with your redirect logic. For example;

```php
    public function store(LoginRequest $request): RedirectResponse
    {
        $request->authenticate();

        $request->session()->regenerate();

        //return redirect()->intended(RouteServiceProvider::HOME);

        return redirect()->intended(
            auth()->user()->is_admin ? route('admin.dashboard') : route('dashboard')
        );
    }
```

In the example a ternary is used, testing the is\_admin flag on the logged in user.  Be sure to retain the \`intended()\` function since this serves a valuable purpose.

### Intended route

The intended route is the name given to the place the user was trying to reach when they had to login.  Imagine the situation; the user is logged in and looking at your application's dashboard or similar.  They go away for a few hours then return and click on a menu item.

Since they are no longer logged in because their session expired, they can't go straight to the link and are redirected to the login page.  The route that they were trying to reach is stored in session as the `intended` route.  After logging in, if the session contains an intended route then they should be sent there.

The `intended()` function takes one parameter and this is a fallback route for when intended is not set.

When modifying the behaviour after login, its important to honor the intended route.

### Fortify + Jetstream

Performing the same redirect on Jetstream is a little more involved.  Check the following TallTips article: [Jetstream Login Conditional Redirect](/laravel/laravel-8-conditional-login-redirects)


# Jetstream Login Conditional Redirect

How to provide different redirects at login when using Laravel Fortify and Jetstream

#### ✅ Checked works with Laravel 10

Laravel 8 introduces [Fortify](https://github.com/laravel/fortify), a new back-end package for providing user authentication services. This represents a big departure from the **controller with traits** approach used in previous versions and has caused some concern that the authentication process is no longer customisable.

Of course, the maturity of the framework, and previous experiences would be unlikely to produce a framework where you could not override default behaviour.

Suppose we need to redirect the user as they are logging in based on some user attribute. With Fortify, how might this be possible?

The hooks that we require are bound into the container during the booting of the `Laravel\Fortify\FortifyServiceProvider`. Within our own code we can re-bind a different class where we will place our business logic.

### Create our own Login Response Class

1. Create a folder under app\Http called `Responses`
2. Create a file `LoginResponse.php`

```php
<?php

namespace App\Http\Responses;

use Illuminate\Support\Facades\Auth;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;

class LoginResponse implements LoginResponseContract
{

    public function toResponse($request)
    {
        
        // below is the existing response
        // replace this with your own code
        // the user can be located with Auth facade
        
        return $request->wantsJson()
                    ? response()->json(['two_factor' => false])
                    : redirect()->intended(config('fortify.home'));
    }

}
```

For Example

```php
<?php

namespace App\Http\Responses;

use Illuminate\Support\Facades\Auth;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;

class LoginResponse implements LoginResponseContract
{

    public function toResponse($request)
    {
        return $request->wantsJson()
                    ? response()->json(['two_factor' => false])
                    : redirect()->intended(
                        auth()->user()->is_admin ? route('admin.dashboard') : route('dashboard')
                    );
    }

}
```

### Make Laravel use our new Response Class

This new class now replaces the Singleton previously registered by Fortify.

Edit the `JetstreamServiceProvider` in your `app\Providers` folder;

In the boot method, add reference to your new response class. When login completes (and the user is actually Authenticated) then your new response will be called.

```php
    public function boot()
    {
        $this->configurePermissions();

        Jetstream::deleteUsersUsing(DeleteUser::class);

        // register new LoginResponse
        $this->app->singleton(
            \Laravel\Fortify\Contracts\LoginResponse::class,
            \App\Http\Responses\LoginResponse::class
        );
    }
```

### Two Factor Authentication

If you use 2FA with Jetstream, you will also need to catch the TwoFactorLoginResponse.  Use the same approach;

```php

        // register new TwofactorLoginResponse
        $this->app->singleton(
            \Laravel\Fortify\Contracts\TwoFactorLoginResponse::class,
            \App\Http\Responses\LoginResponse::class
        );
```

You can return the same response, or create an additional response if you want different behaviour for users that login using 2FA.


# Simplify Laravel CRUD Controllers

Reusing the same form for create and update

This is the pattern I use for simple CRUD operations. It makes use of Route Model Binding to inject the model being created, and model properties to determine if the model is in the process of being created or updated.

The forms also make use of the `old()` helper to pull in previous form value from the model, or from previously submitted form.

Note that the form elements here are styled with tailwind utility classes. If you are not into Tailwind, look past that as its not relevant to this article.

The example is CRUD for something called a Template. In this particular application, its just a form with a bunch of input fields.

## The Controller&#x20;

It starts with simplifying the controller;

```php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\TemplateForm;
use App\Template;
use Illuminate\Http\Request;

class TemplateController extends Controller
{

    public function create()
    {
        return $this->edit(new Template());
    }

    public function store(TemplateForm $request)
    {
        return $this->update($request, new Template());
    }

    public function edit(Template $template)
    {
        return view('template.edit')->withTemplate($template);
    }

    public function update(TemplateForm $request, Template $template)
    {
        $request->persist($template);

        return redirect(route('templates.index'));
    }

}
```

When creating a record, we create a new model and pass it into the edit function. The edit is responsible for returning the view. Because we pass a new model into edit, we don’t need to worry about how we use the model in the view (more later).

When the data is returned from the form, if it is a new model then again, we create a new instance of the Template model and pass it to the Update method. The Update does not care if the model is new or an existing one looked up by Route Model binding. All it needs to do is to pass the model back to the Form Request and ask it to persist the model with the form data.

## Sharing the form

The same form is shared for both edit and update functions. This is possible because either way, an instance of our model is passed to the form.

*I have abbreviated the form because its not relevant to the discussion, but you will see validation and persist for items that are not visible below.*

```php
<div class="w-full p-6 flex">
    @if($template->exists)
        <form class="flex flex-col w-full" method="POST" action="{{ route('templates.update',$template) }}">
            @method('put')
    @else
        <form class="flex flex-col w-full" method="POST" action="{{ route('templates.store') }}">
    @endif
            @csrf
            <div class="flex w-full">
                {{-- form input element --}}
                <div class="flex flex-wrap mb-6 w-1/3">
                    <label for="name" class="block text-gray-700 text-sm font-bold mb-2">Template Name:</label>

                    <input id="name" type="text" required name="name"
                        value="{{ old('name', $template->name) }}"
                        class="text-base font-mono shadow appearance-none border rounded 
                            w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline 
                            @error('name') border-red-500 @enderror">
                    @error('name')
                    <p class="text-red-500 text-xs italic mt-4">{{ $message }}</p>
                    @enderror
                </div>

                {{-- form input element --}}
                <div class="flex flex-wrap mb-6 w-2/3 ml-4">
                    <label for="description" class="block text-gray-700 text-sm font-bold mb-2">Description:</label>

                    <input id="description" type="text" required name="description" value="{{ old('description', $template->description) }}"
                        class="text-base font-mono shadow appearance-none border rounded w-full 
                        py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline 
                        @error('description') border-red-500 @enderror">
                        
                    @error('description')
                    <p class="text-red-500 text-xs italic mt-4">{{ $message }}</p>
                    @enderror
                </div>
            </div>

            // irrelevant form elements removed.....

            <button class="positive-button" type="submit">Save </button>
        <form>
</div>
```

It is not possible to use the same form tag for both update and create because we need to pass the model ID for an update and make it a PUT request rather than a POST request. In line 2 we are checking if our model actually exists in the database so that we know which case it is. After this @if @else section, the rest of the form does not care if the model is new or not.

The form inputs themselves use the [`old()`](https://laravel.com/docs/6.x/requests#old-input)helper to insert the previous value, the value from validation or an empty value. for instance `value="{{ old('name', $template->name) }}"` . It helps a lot if you name the form field the same as the model attribute.

`old()` takes two parameters, the first is the field name that was submitted previously (in the case of validation failures, the second parameter is the default value. In our case, for a model that is being edited, the previous value is inserted. If it is a new model then NULL is returned and no errors are produced. First time around the value of the form field will be empty.

If you want a default value for the field then the null coalesce operator ?? can be used. For instance; `value="{{ old('type', $template->type ?? 'banana') }}"`. If the model is new then the default value of ‘banana’ will be inserted into the form.

## Form Request

I appreciate that this will be controversial, but i&#x74;*'*&#x73; what I do, and is optional. You can still go ahead and store the updated model in the controller, or use repository pattern or whatever. I prefer to use the [form request class](https://laravel.com/docs/7.x/validation#form-request-validation) to save the form data also.

```php
<?php

namespace App\Http\Requests;

use App\Template;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Storage;

class TemplateForm extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|max:100',
            'description' => 'required|max:200',
            'subject' => 'max:200',
        ];
    }

    public function persist(Template $template)
    {
        $template->name = $this->name;
        $template->description = $this->description;
        $template->type = $this->type;
        $template->subject = $this->subject;
        $template->email_template = $this->email_template;
        $template->sms_template = $this->sms_template;

        $template->save();
    }
}
```

So, yes `rules()` is standard, but I have added a `persist()` method. This expects to be passed a model instance, to which it saves the form data.

The model instance was passed from the update function of the controller with `$request->persist($template);` If we are creating a model then an empty model was passed from the store method, and if we are updating an existing model then this was injected by [Route Model Binding](https://laravel.com/docs/6.x/routing#route-model-binding). In the form request class we just pop the values into the model and save it.

## Conclusion

Unfortunately, too many people think that if you want to bind model data to a form then you must use the Laravel Collective Form components :zany\_face: . This is not the case. Understanding the [old()](https://laravel.com/docs/7.x/helpers#method-old) helper is fundamental to building simple crud operations , and passing an **instance of a new model** to your form means that you can share the same form with create and update operations.


# CSRF and expired login forms

How to handle a page with a login form that will expire

If your homepage contains a login form, or a modal with login, then when the session ends (by default, after 2 hours) then the csrf token is no longer valid and the user sees a page expired warning after they have filled out their login details.

We can work around this with a simple addition to the `<head>` of the main layout template.

```markup
<meta http-equiv="refresh" content="{{ config('session.lifetime') * 60 }}">
```

This simple line will [refresh](https://en.wikipedia.org/wiki/Meta_refresh) the page when it gets to the end of the session. The refreshed page will have a new session and a new csrf token. This way, your login form is always valid.

If the user interacts with the site  and loads other pages then this refresh will never happen since the timeout is reset each time the page is loaded.

For logged in users, after the session lifetime the page will refresh and they will be returned to the same page, however they will no longer be authorised so will be redirected however the auth middleware is configured.

There is a very small chance that the user goes away and comes back after 1 hour 59 minutes and starts to fill out the login form, part of the way through the page refreshes.  This would be a very unlikely coincidence and the user will be no worse off than if the form was stale and failed after they pressed login.

{% hint style="info" %}
Note that this reload will cause some small additional view count in your analytics&#x20;
{% endhint %}

<figure><img src="/files/bSseCP4mKTLLj7Mz7XWS" alt=""><figcaption><p>Affiliate Link</p></figcaption></figure>

Support the talltips site by purchasing Ash Allen's excellent book [via this link](https://ashallen.lemonsqueezy.com/?aff=1O08w)


# CSRF and expired logout forms

Prevent users seeing a 419 Page Expired when logging out

Best practice for logging out is that the function should be driven from a form and use a csrf token to prevent someone logging you out in a [CSRF](https://owasp.org/www-community/attacks/csrf) attack.  Forcibly logging users out may cause them to disclose login credentials whilst they are being monitored.

A problem with the CSRF token in the logout form is that it can become stale in at least the following three scenarios

1. User has more than one tab open and logs out in one of them. The logout action on other tabs will now produce 419 Page Expired error
2. User has a single tab open but leaves the site and does not return during the session duration. The CSRF token is then stale and pressing logout gives 419 error.
3. The user uses the option to close all sessions on other devices. Logout on a terminated session will generate the error.

### Laravel 11+ solution

Laravel 11 removes the Http/Middleware folder and the option to add in extra logic.  The solution is to replace the ValidateCsrfMiddleware with a class that performs the same function, but adds the logout route to the except array so that when a guest user logs out, the token is not checked.

Add the following file into a new app/Http/Middleware folder

{% code title="CheckCsrf.php" %}

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
use Illuminate\Support\Facades\Auth;

class CheckCsrf extends ValidateCsrfToken
{

    protected $except = [
        // other routes that need excepting
        'stripe/*',
    ];

    public function handle($request, Closure $next): Response
    {
        if($request->route()->named('logout')) {

            if (!Auth::check() || Auth::guard()->viaRemember()) {

                $this->except[] = 'logout';
                
            }   
        }

        return parent::handle($request, $next);
    }
}
```

{% endcode %}

Then add the following into the bootstrap/app.php file

{% code title="" %}

```php
->withMiddleware(function (Middleware $middleware) {
        $middleware->web(replace: [
            Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class =>
            App\Http\Middleware\CheckCsrf::class
        ]);
    })
```

{% endcode %}

Here the framework supplied middleware is swapped for our version

### Laravel 8 to 10 solution

A solution to the problem is relatively simple, and requires a small addition to the VerifyCsrfToken middleware;

{% code title="app/Http/Middleware/VerifyCsrfToken.php" %}

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
use Illuminate\Support\Facades\Auth;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        //
    ];
    
    public function handle($request, Closure $next)
    {
        if($request->route()->named('logout')) {

            if (!Auth::check() || Auth::guard()->viaRemember()) {

                $this->except[] = route('logout');
                
            }   

        }

        return parent::handle($request, $next);
    }
}

```

{% endcode %}

Normally this file contains just an `$except` array of routes that should be ignored from csrf.

### How does this help?

In this code we override the handle method and perform three checks.&#x20;

1. is the route logout
2. is the user a guest (ie, not using an authenticated session), or,
3. did the user just get logged back in via the 'Remember Me' cookie

If this is the case then we add `'logout'` to the except array.  We then pass control to the core VerifyCsrfMiddleware which recognises the presence of the logout route in the array, and bypasses the check. The form data is correctly posted and we are redirected using the LogoutResponse.

The user sees no error page.

By checking in this way, we ensure that valid logout requests by authenticated users are still protected by CSRF Token and that those with expired sessions do not see the 419 error.

<figure><img src="/files/bSseCP4mKTLLj7Mz7XWS" alt=""><figcaption><p>Affiliate Link</p></figcaption></figure>

Support the talltips site by purchasing Ash Allen's excellent book [via this link](https://ashallen.lemonsqueezy.com/?aff=1O08w)


# Add your own logo to Laravel Mail

Amending the template is easy to add your logo to the mail header

The mail template will already automatically add your application name to the mail template, but if you want to display your own logo?

### Publish the email stubs&#x20;

Run the command `php artisan vendor:publish --tag=laravel-mail`

This will copy the email layout stubs to the folder /resources/views/vendor/mail/html

### Edit the header.blade.php file.

To start with the template looks like;

```html
<tr>
<td class="header">
<a href="{{ $url }}" style="display: inline-block;">
@if (trim($slot) === 'Laravel')
<img src="https://laravel.com/img/notification-logo.png" class="logo" alt="Laravel Logo">
@else
{{ $slot }}
@endif
</a>
</td>
</tr>

```

This is the part that will show the laravel logo if the app name is laravel, but we can strip that out and add our own image.

```html
<tr>
<td class="header">
<a href="{{ $url }}" style="display: inline-block;">
<img src="{{ asset('/my-app-logo-192x192.png') }}" class="logo" alt="{{ $slot }}">
</a>
</td>
</tr>
```

The above links to your image.  Instead you can *embed* the image

### Embedding your image instead of linking

Instead of linking to an image on your site, you may prefer to embed the image. See the following article regarding the pros and cons of different ways of adding images to your emails;

{% embed url="<https://sendgrid.com/blog/embedding-images-emails-facts/>" %}

Instead of linking to the image, you can base64 encode it;

```html
<tr>
<td class="header">
<a href="{{ $url }}" style="display: inline-block;">
<img src="data:image/png;base64,{{ base64_encode(file_get_contents(public_path('my-app-logo-192x192.png'))) }}" class="logo" alt="{{ $slot }}">
</a>
</td>
</tr>
```


# Specify a different mail theme for Notifications

Where to place a custom mail theme in Laravel

{% hint style="info" %}
This issue was resolved in Laravel 8.5 <https://github.com/laravel/framework/issues/34391>
{% endhint %}

Views can use something called *Hints* to tell the framework where to look for the view file. The theme always uses a hint of `Mail::` which corresponds to the `resources/views/vendor/mail/html` folder. What if we want to locate our theme somewhere else?

We can define our own hint and bind it into the system in the `AppServiceProvider` with;

{% code title="app/Providers/AppServiceProvider.php" %}

```php
    public function boot()
    {
        $this->loadViewsFrom(resource_path('views/mail'), 'appmail');
    }

```

{% endcode %}

With this, we are saying that any view file that is hinted with `appmail::` is found in the `views/mail` folder.

Then, in the Notification, add the hint in the theme function;

```php
  public function toMail($notifiable)
  {
    return (new MailMessage)
      ->theme('appmail::mycorp')
      ->subject('Password Reset')
      ->line('Your password reset code is specified below')
      
    // etc
```


# Show custom page when email verification link expired

Using Laravel 10 with Breeze + Blade starter kit

{% hint style="info" %}
For Breeze + Livewire + Alpine starter kit, see the section at the end
{% endhint %}

This is how you might change the default breeze installation to show a custom page when the signature in an account verification email has expired;

find the below in routes\auth.php

{% code title="routes\auth.web" %}

```php
    Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
                ->middleware(['signed', 'throttle:6,1'])
                ->name('verification.verify');
```

{% endcode %}

remove the `signed` middleware

```php
    Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
                ->middleware(['throttle:6,1'])
                ->name('verification.verify');
```

In the VerifyEmailController, add the following before the existing code;

```php
        if (! $request->hasValidSignature()) {
            return redirect()->route('invalidSignature');
        }
```

This moves the signature checking from the middleware into the controller so that we can take control of the response. Here we redirect the user to a new view of our own design.

Duplicate the file resources/views/auth/verify-email.blade into verify-invalid.blade.php

Change it as below (only the message in the first div changes);

```html
<x-guest-layout>
    <div class="mb-4 text-sm text-gray-600">
        {{ __('Sorry but that verification link is no longer valid, click below to request a new one.') }}
    </div>

    @if (session('status') == 'verification-link-sent')
        <div class="mb-4 text-sm font-medium text-green-600">
            {{ __('A new verification link has been sent to the email address you provided during registration.') }}
        </div>
    @endif

    <div class="flex items-center justify-between mt-4">
        <form method="POST" action="{{ route('verification.send') }}">
            @csrf

            <div>
                <x-primary-button>
                    {{ __('Resend Verification Email') }}
                </x-primary-button>
            </div>
        </form>

        <form method="POST" action="{{ route('logout') }}">
            @csrf

            <button type="submit" class="text-sm text-gray-600 underline rounded-md hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                {{ __('Log Out') }}
            </button>
        </form>
    </div>
</x-guest-layout>
```

Create a new route in auth.php

```php
    Route::view('verify-invalid', 'auth.verify-invalid')
                ->name('invalidSignature');
```

This adds a new route that can show our new page.

### For Breeze with Livewire+Alpine

The main difference to the above process is that the view to copy is located in resources/views/livewire/pages/auth/verify-email.blade.php

{% hint style="info" %}
Remember that the default behaviour is that the user must be logged in when checking the verification email since the routes are wrapped in auth middleware
{% endhint %}


# Using a mutator to save currency

When user enters Pounds & Pence or Dollars & Cents

When working with currencies, it's good practice to store the value in the lowest denomination (eg cents) and then revert to normal currency format when displaying the value.  This is to avoid the use of floating point numbers and rounding issues when calculating VAT percentages.

If you capture the value from the user, then they will present you will something like 12.75 when you need to save 1275 in the database.

Eloquent accessors and mutators can be used for this conversion in each direction.  The example below uses Pounds, but the principle is the same for Dollars or Euro.

In the eloquent model

```php
    public function setPoundsAttribute($value)
    {
        $this->each = strval($value*100);
    }

    public function getPoundsAttribute()
    {
        return number_format($this->each/100,2);
    }
```

`each` is the column storing the pence value.

Now we can save the value to the model in pounds format eg `$item->pounds = '12.75'`

{% hint style="info" %}
Be sure to add the mutated field (pounds) to your `$fillable` attribute if you are using mass assignment protection.
{% endhint %}

{% hint style="warning" %}
This question when posed on Laracasts produced quite a few answers and alternative ways, the main issue being to avoid floating point conversion issues with certain values, eg 19.99

<https://laracasts.com/discuss/channels/general-discussion/int-conversion-issue>
{% endhint %}


# Using Spatie Valuestore to hold frequently accessed settings

Domain specific settings

This was a reply posted to the [Laracasts forum](https://laracasts.com/discuss/channels/tips/code-review-idea-to-solve-problem).  I know I will use this in the future, so documented here also as a reminder to myself as much as anything.

Install Valuestore `composer require spatie/valuestore`

In AppServiceProvider register method;

```php
 public function register()
 {
   $this->app->singleton('valuestore', function () {
     return \Spatie\Valuestore\Valuestore::make(storage_path('app/settings.json'));
   });

   $values = $this->app->valuestore->all();

   $this->app->bind('settings', function () use($values) {
     return $values;
   });
 }
```

What we have in the app container is a singleton that points to the `valuestore` class.  When you use that, you are directly interacting with the settings stored in the file.

When you use the `settings` bound to the app container, you are using a cached version of the values as they were at the start of the request cycle (as an associated array).

So instead of writing the current Euro rate to a database row, put it in the valuestore instead;

```php
app('valuestore')->put('EUR', $rate)
```

and in your model when you want to apply this;

```php
public function getPriceEUR()
{
  return intval($this->usd_price / app('settings')['EUR'] * 100);}
}
```

By using `settings` and not `valuestore` the file will only be accessed once and not for each iterated product in a collection.

Of course you now have a place where you can store other currencies or any other application settings using the full features of the [Valuestore](https://github.com/spatie/valuestore) package.<br>

{% hint style="info" %}
Be careful with decimals when converting . See <https://laracasts.com/discuss/channels/general-discussion/int-conversion-issue>
{% endhint %}


# Using the old() helper

Populate a form with user's previous entry, or default data

When we are presenting the user with a dropdown, from which they can choose, for example, a different user, you have to send your form all the possible choices. You can either send the whole model, or just `->pluck('name',$id')`

Then you loop over all the contacts setting the select value and name;

```php
<select name="contact">
@foreach($contacts as $contact)

	<option value="{{$contact->id}}">{{ $contact->name }}</option>

@endforeach
</select>
```

### Selecting the previous value

At each iteration of the loop, you want to check if the current contact (the one you are on in the loop) matches the id of the one in the model. We can do this by comparing the current ID with that already stored in the database.

```php
<select name="contact">
@foreach($contacts as $contact)
	<option value="{{$contact->id}}" 
			{{ $contact->id == $order->contact_id ? 'selected' : ''}}>
		  {{ $contact->name }}
   </option>

@endforeach
</select>
```

The ternary here compares the current contact with the contact on the order.

{% hint style="info" %}
This is the same as writing

```php
@if($contact->id == $order->contact_id) selected @endif
```

{% endhint %}

The above selects the current user on the order, but it does not remember the selection if you change it and then have a validation error.

This is where the old() helper comes in.

```php
<select name="contact">
@foreach($contacts as $contact)

	<option value="{{$contact->id}}" 
		{{ $contact->id == old('contact',$order->contact_id) ? 'selected' : ''}}>
		{{ $contact->name }}
        </option>

@endforeach
</select>
```

(assuming the select option is named 'contact')

Now, the current option is compared to whatever the old function returns. If it is not set (this is the first time the form is displayed, then the database value from the order is returned. If old data exists then this is being displayed as a result of a validation error, so it returns the value the person selected last time they posted the form.

{% hint style="info" %}
Syntax  `old( previous , default )`
{% endhint %}


# Alternatives to using Eloquent Accessor

Using computed columns in MySQL

Article illustrating how it is possible to use computed columns in our database tables to create additional model fields other than by using accessors.

{% embed url="<https://pineco.de/using-computed-columns-in-laravel/>" %}

Particularly useful where the model is exported via API or as an array and needs additional derived data.


# UpdateOrCreate may not update timestamp

When the record is identical, the updated\_at timestamp is not changed

Consider the following code which is part of a spreadsheet import of a product catalogue.

```php
    Item::updateOrCreate(
        ['code' => $row[0]],
        [
            'description'=> $row[1],
            'category'=> $row[2],
            'uom' => $row[3],
            'case_quantity' => $row[4],
            'each' => intval(strval($row[5]*100)),
            'generic' => false
    ]);
```

Using the Eloquent `model:updateOrCreate()`, if the spreadsheet contains any new items then a new record is added to the product catalogue.

If the Item already exists, but for instance, the price has changed then the existing record is found and updated.

However if the row in the spreadsheet is IDENTICAL to the record already in the database, then Eloquent knows that the record does not have any changes (is not dirty) and skips the write process. In this scenario, the `updated_at` field retains the previous value.

This might be ok for your use case, but in one project, the updated\_at column was being used as part of a scope to show only *current* products to the user.

The solution is to add `updated_at` to the data to be written to force a new value, and to ensure that updated\_at is in the `$fillable` array or unguarded.

```php
    Item::updateOrCreate(
        ['code' => $row[0]],
        [
            'description'=> $row[1],
            'category'=> $row[2],
            'uom' => $row[3],
            'case_quantity' => $row[4],
            'each' => intval(strval($row[5]*100)),
            'generic' => false,
            'updated_at' => now(),
    ]);
```


# Use of lockForUpdate()

Locking the database between read and update of a database record

Suppose we have an application where many users or API transactions are creating invoices. Each of these invoices must have a sequential number and not be impacted by multiple updates happening on the database at the same time.

For the sake of this example, we have decided not to use the auto incrementing primary key of the database table.  Perhaps our application is multi-tenant and each tenant or project in our system has their own invoice sequence.

The naive approach is to simply read the current invoice number, increment it and save it back to the database.  The problem with this approach is that as we are reading the current invoice number, another user's request could also be reading the same value.  Then we both increment the value and write back to the database.  We now have two invoices in our system with the same value.

The better approach is to use a transaction closure and Eloquent's `lockForUpdate()`

In the case of MySQL, this adds `SELECT ... FOR UPDATE` to the original query meaning that the record is locked until the transaction is completed.  The simplest way to complete the transaction is to use the closure approach;

```php
$invoice = DB::transaction(function () use ($tenant) {
    $inv = DB::table('tenants')
        ->where('id', $tenant)
        ->lockForUpdate()
        ->first('next_invoice')
		    ->next_invoice;

    DB::table('tenants')
        ->where('id', $tenant)
        ->update(['next_invoice' => ++$inv]);

    return $inv;
});
```

This wraps the select and update in a transaction and applies the lockForUpdate to the select. It then writes back the incremented value to the database and returns the number just taken to the caller for use in the invoice generation.


# Using S3

Various tips for working with S3 storage from your Laravel application

See also;

{% content-ref url="/pages/-MFj4sSi7ftYHwb714Q6" %}
[Livewire File Uploads Using S3](/livewire/livewire-file-uploads-using-s3)
{% endcontent-ref %}

## Content in folders

Segregate your S3 content according to your environment. Adding the following to `config/filesystems.php`

```php
        'pdfs' => [
            'driver' => 's3',
            'root' => env('APP_NAME') . '/pdfs',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => 'mybucket',
            'visibility' => 'public',

        ],
```

In the above, PDF files will be stored for public consumption within a disk prefixed with the environment name. The key to this is the `root` attribute. This provides a path to prefix to all files that are created in the pdfs disk.

Write files to the pdf folder after specifying the disk;

```php
Storage::disk('pdfs')->put('hello.txt','hello');
```

The `->url()` command can be used to get the public url for the created file;

```php
>>> Storage::disk('pdfs')->url('hello.txt');
=> "https://mybucket.s3.eu-west-1.amazonaws.com/MYAPP-DEV/pdfs/hello.txt"
```

Be sure to have different app names between Dev, Staging and Live to separate your output.


# Super Simple User Based Feature Flags

Through a simple addition to the user model we can enable application features for specific users only

When developing a new feature, it can be very useful to be able to get this feature in the hands of trusted users whilst all other users see nothing different.  This type of functionality can be termed a **Feature Switch** (or feature toggle). The method implemented here is easy to implement and robust in use.

There are only three components to this implementation

1. A config section that indicates if the feature is globally available
2. A function on the User model which tells us if the user has that feature available to them
3. Use of the function in views and classes to guide the workflow or enable view sections

Of course this approach may not suit all, and attention has to be paid to the effect of database changes for the feature which may not be available to all users.

### The config section

You could have a dedicated config file for feature switches, but in this case we use the app.php file and add a new section to it;

{% code title="config/app.php" %}

```php
    /*
    | Features
    | NU = Nudges are enabled
    */
    'features' => [
        'SE',  // Search is enabled
        'FL',  // Flashcards are enabled
    ],
      
```

{% endcode %}

Features that are globally available (available to all users) are added to the features section.  The comment tells us what other features are available but not yet implemented. &#x20;

You will start out with this features array empty, and instead give the flag to the test users. After you are happy with the new feature and want to roll it out to all users, add the appropriate flag to the features array.

### The User model

The addition of a simple function `hasFeature` will tell us if the feature is turned on for this user or turned on for everyone.

{% code title="User.php" %}

```php
    public function hasFeature($code)
    {
        // ignores user setting if the feature code is globally enabled.
        if(collect(config('app.features' ?? [] ))->contains($code)){
            return true;
        }

        return !! collect(explode(',',$this->features))->contains($code);
    }
```

{% endcode %}

If the global config contains the passed flag then the feature is available, irrespective of what the User model holds.  If the value is not in config, then check if the User model contains this value.

A simple `string` column is added to the users table containing `features` in this field place a comma separated  list of features available to this user, ie 'FL,NU'.

### Restricting the use of the feature

In views, a simple `@if` directive may be used. For example;

```php
@if(Auth::user()->hasFeature('SE'))
    <a href="{{ route('search') }}" class="px-2 py-2 text-sm font-bold text-center">SEARCH</a>
@endif
```

In classes and controllers, since the function simply returns true or false, we can use if, case statements, ternary statements etc

```php
if($user->hasFeature('NU')){
  // something to do with feature
}
```

### Cleaning up afterwards

Once you have proven your new feature and it has been rolled out globally for a period of time, it is best-practice to plan to remove the feature switch from your code now that it is no longer required.


# Installing a Specific Version of Laravel

Use Composer to setup a project in an earlier version of Laravel

Specify the version required in a Composer create-project command

`composer create-project laravel/laravel myproject "7.*"`


# Versioning your Laravel Project

Create a string which pulls information from your Git history

![A possible format of a version string showing in the footer](/files/-MLD5mR9K6b-FaqtAZ_f)

Using Git for version control, we can tag releases and then pull that tag information into our project so that it can be displayed to users.  This can be helpful where you have multiple deployments of the same code base and need to be sure which version a site is running.

### Create a version.php config file

{% code title="/config/version.php" %}

```php
<?php

    $tag  = exec('git describe --tags --abbrev=0');
    
    if(empty($tag)) {
        $tag = '-.-.-';
    }

    $hash = trim(exec('git log --pretty="%h" -n1 HEAD'));
    $date = Carbon\Carbon::parse(trim(exec('git log -n1 --pretty=%ci HEAD')));

return [

    'tag' => $tag,
    'date' => $date,
    'hash' => $hash,
    'string' => sprintf('%s-%s (%s)',$tag, $hash, $date->format('d/m/y H:i')),
    
];

```

{% endcode %}

The above gives our config a `version` element that we can then include within views, emails, support logs etc.

The config elements are accessed as `{{config('version.string')}}` for example.  Caching config avoids the exec functions being called on every request for the version information.

### Tagging releases

Using the github Desktop client, right click the commit and add the tag.

![](/files/-MLD7tE-AjYwfsad06KS)

Using github on the web;&#x20;

![](/files/-MLD9AMLwqAItdRVG0np)

Using Bitbucket.  In the Commits view, click on the latest commit and select tag from the sidebar;

![](/files/-MLDAVyMttOmqX3TjQZl)


# CSS Cache Busting with your Git Commit SHA

When not using MIX and its versions

Now that Tailwind CSS has JIT mode, I have stopped configuring and using laravel mix.

A useful feature of mix is cache busting - the ability to force your users to reload css and js files from the server instead of using their cached versions

A simple way to add cache busting to your css loads is to use your Git commit sha hash as a parameter on the css line.

This article describes how you can get the Git sha hash in your application; <https://talltips.novate.co.uk/laravel/versioning-your-laravel-project>

Add the sha to your css load;

```markup
<link rel="stylesheet" href="/css/app.css?{{ config('version.hash') }}">
```

This generates a line in your browser like;

```markup
<link rel="stylesheet" href="/css/app.css?f7509a3">
```

where \`f7509a3\` is the sha hash of your code and will change on each commit.


# Adding column to Database Notifications table

When you need additional scope on notifications

Suppose your user can belong to many teams, and they have the ability to switch teams, you, like me, may want to allow the user to have unread notifications only for the team that they are currently in.

A similar situation may exist for multi-tenant applications where the same user can be a member of different tenants.

You may find that you need an additional column on the notifications table that you can then later filter with a global scope.

This article: <https://www.ystash.com/blog/extra-columns-with-laravel-database-notifications/> suggests creating a new database notification channel.

The approach presented here is simpler as it only involves listening to the Notification Eloquent model **creating** event using an [observer](https://laravel.com/docs/8.x/eloquent#observers).

### Add your additional column to the notifications table

This is fairly straightforward, just a regular migration targeting the notifications table

```php
    public function up()
    {
        Schema::table('notifications', function (Blueprint $table) {
            $table->foreignId('organisation_id');
        });
    }
```

### Create an observer

If you don't already have it, create app\Observers table

Create new file NotificationObserver.php

{% code title="app\Observers\NotificationObserver.php" %}

```php
<?php

namespace App\Observers;

class NotificationObserver
{
    public function creating($model)
    {
        // here set the column data for your new column
    }

}

```

{% endcode %}

### Add Observer to AppServiceProvider

In the boot() method of Providers/AppServiceProvider

```php
    public function boot()
    {
        DatabaseNotification::observe(NotificationObserver::class);
    }
```

Thats it! when your database notification is created, your additional data will be added


# Find nearby locations using the Haversine formula in Eloquent query

Uses the haversine formula to find all DB records with a location within a specified radius

## Haversine

The **haversine formula** determines the [great-circle distance](https://en.wikipedia.org/wiki/Great-circle_distance) between two points on a sphere given their [longitudes](https://en.wikipedia.org/wiki/Longitude) and [latitudes](https://en.wikipedia.org/wiki/Latitude). Important in navigation, it is a special case of a more general formula in spherical trigonometry, the **law of haversines**, that relates the sides and angles of spherical triangles. (Wikipedia)

### Managing coordinates

Personally, I have always found it easier to handle and store coordinates as strings. Very rarely is it required to perform any arithmetic operations on the values.

{% hint style="info" %}
If your requirements are more complex, and you have mysql 8, consider Spatial queries instead. <https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html>
{% endhint %}

### Eloquent or DB Query Builder

Add the following to an existing query builder instance, and then not forgetting to call `get()` or `paginate()`

The query assumes that your table contains columns called `latitude` and `longitude`. You may need to adapt these to suit your use case.

```sql
// the centre of your search
$latitude = '56.32124';
$longitude = '-1.342934';

// search radius
$distance = 5;  //(miles - see note)

$query->selectRaw('(3959 * acos (
       cos ( radians(?) )
       * cos( radians( latitude ) )
       * cos( radians( longitude ) - radians(?) )
       + sin ( radians(?) )
       * sin( radians( latitude )))) AS distance',[
           $latitude,
           $longitude,
           $latitude
  ]);
            
  $query->havingRaw('distance <= ? OR 0', [$distance]);
```

{% hint style="info" %}
&#x20;The above assumes that you have distance in miles.  If distance is in KM then replace 3959 with 6371.
{% endhint %}

References:

* <https://stackoverflow.com/questions/574691/mysql-great-circle-distance-haversine-formula>
* <https://en.wikipedia.org/wiki/Haversine_formula>
*

{% hint style="info" %}
**You can discuss pages on this site at** [**https://github.com/snapey/talltips/discussions**](https://github.com/snapey/talltips/discussions)
{% endhint %}


# Using Queues on Shared Hosting with Laravel

When all you have is CRON

Sometimes, shared hosting must be used that does not permit the installation of supervisor to run the queue worker.

A common example of this are servers deployed with Cpanel access only.

One approach for non-time sensitive queue work (such as sending emails) is to add a task to the scheduler that starts the queue worker every minute;

```php
$schedule->command('queue:work --stop-when-empty')
             ->everyMinute()
             ->withoutOverlapping();
```

Adding this line to the scheduler in app\Console\Kernel.php and then setting up a cron job to run the scheduler, ensures that the queue gets serviced every minute.

For Cpanel servers, the Cron statement might look like;

```
* * * * * /usr/local/bin/php /home/{account_name}/live/artisan schedule:run
```

where `{account_name}` is the user account that cpanel is running under and `live` is the folder of the laravel application


# Create Guaranteed Unique Invoice Number in Laravel

Not as simple as it may sound.  How to avoid problems from race conditions.

An interesting question was posed on Laracasts Discussions which seems straightforward, but comes with a number of hidden issues;

> I need help in generating invoice number
>
> I need this format. For example in the financial year 01-04-2022 to 31-03-2023 I have generate number like this R2022/2023-0001 and so on.
>
> In the next year 01-04-2023 to 31-03-2024 it should be like this R2023/2024-0001

## Problems Considered

* How do we determine the current financial year?
* How do we reset the count in each financial year?
* How do we increment the invoice number and make it atomic so that another user cannot get the same number

## Determine the current financial year

This is relatively straightforward.  Get today, then change to 1st April in the current calendar year.  If the date moved forward in time then today must be between 1st January and 31st March.  If so revert to the previous year.

```php
$dt = today()->setMonth(Carbon::APRIL)->setDay(1);

if($dt > today()) {
 $dt->subYear(1);
}
```

## Reset the count in each Financial Year

This is a little tricky as the invoice number must restart at 0001 in each year.

We don't want to have something that needs to run on a specific date when we can let our database do it for us.  We are going to create a database table to keep track of the current invoice number in each financial year;

### Model and Migration

```bash
php artisan make:model InvoiceSequence -m
```

In our invoice\_sequences migration

```php
        Schema::create('invoice_sequences', function (Blueprint $table) {
            $table->id();
            $table->integer('fy')->unique();
            $table->integer('current')->default(0);
        });
    
```

the `fy` column allows us to create a new record for each financial year.  The `current` column stores the next invoice number to be issued.  Unique on the `fy` column ensures that there can only be one entry per year.

### Eloquent FirstOrCreate

Initially, I was using the FirstOrCreate method to create a new record each time the financial year changes;

```php
$is = InvoiceSequence::firstOrCreate(['fy' => $dt->format('Y')]);
```

The `fy` column is set to the current financial year eg 2022.  When the year changes to 2023, there will not be a row with that year so a new row is added, and thanks to the default value, the `current` value is automatically set to 0.  However, it has been pointed out to me by @swaz at Laracasts that the firstOrCreate is not atomic and if two transactions occur at the same time then two entries might be created.  For this reason, \`unique()\` is added to the table.

see: <https://freek.dev/1087-breaking-laravels-firstorcreate-using-race-conditions>

The preferred approach is instead as below;

```php
$fy = $dt->format('Y');

DB::statement("INSERT INTO invoice_sequences (fy) VALUES ({$fy}) ON DUPLICATE KEY UPDATE fy = fy, id=LAST_INSERT_ID(id)");
    
$lastInsertId = DB::getPDO()->lastInsertId();
```

This attempts to insert a new row into the database, and if this fails, returns the record ID for the existing row.  The point is that this single SQL statement is atomic. Two threads cannot interfere with each other's operation.

## Safely Increment the invoice number

If we don't take care at this stage, two requests occurring at the same time could be given the same invoice number.

At first glance, it would be easy to write a solution that;

* gets the invoice\_sequences record for the current year
* adds one the current value
* saves the record

The problem with this approach is that two users might both execute the read part and get the same number, both then increment and both save. The counter has only been incremented once and both requests get the same number.

This is not a problem in testing, or when your application is small, but as it increases in activity the chances of this happening start to increase, are unpredictable and extremely hard to track down.

The example here is for invoice numbers but it could apply to any number that needs to be guaranteed to issue once and once only.

&#x20;The solution, inspired by this <https://www.sqlines.com/mysql/how-to/select-update-single-statement-race-condition> safely increments the `current` number and also returns its value.

```php
DB::statement("UPDATE invoice_sequences SET current = LAST_INSERT_ID(current) + 1 WHERE id = {$lastInsertId}");

$current = DB::getPDO()->lastInsertId();
```

Read the linked article to understand why this works.

{% hint style="warning" %}
This solution is only applicable to MYSQL
{% endhint %}

## Putting it all together

Along with formatting it in the format requested by the question poster

```php
$dt = today()->setMonth(Carbon::APRIL)->setDay(1);

if($dt > today()) {
 $dt->subYear(1);
}

$fy = $dt->format('Y');

// ensure there is a record for the current financial year
DB::statement("INSERT INTO invoice_sequences (fy) VALUES ({$fy}) ON DUPLICATE KEY UPDATE fy = fy, id=LAST_INSERT_ID(id)");
$lastInsertId = DB::getPDO()->lastInsertId();

// automatically increment the count AND get the value
DB::statement("UPDATE invoice_sequences SET current = LAST_INSERT_ID(current) + 1 WHERE id = {$lastInsertId}");
$current = DB::getPDO()->lastInsertId();

$invoiceNumber = sprintf('R%s/%s-%04u', $year, $year+1, $current);
```

Grateful to @swaz at Laracasts for helping with this.  You can read the full thread and how we arrived at the solution here;

{% embed url="<https://laracasts.com/discuss/channels/laravel/generating-unique-invoice-number-based-on-financial-yesr>" %}

{% hint style="info" %}
**You can discuss pages on this site at** [**https://github.com/snapey/talltips/discussions**](https://github.com/snapey/talltips/discussions)
{% endhint %}


# Send Notification to all team members

When one email should be sent using routeNotificationForMail

### Scenario

A team receives a single notification, for instance as a single slack message, but the email version needs to be sent to all members of the team.

Looping over the notification for each member would send individual emails, but would also send multiple slack messages.

### Solution

Add the `use Notifiable` trait to the Team model

Add a method `routeNotificationForMail` to the Team model

```php
    public function routeNotificationForMail($notification)
    {
        return $this->users->pluck('name','email');
    }
```

This function returns a collection of recipients with the user email as the key and the user name as the value.

The Notification, sent by email will be \[To] the complete list of team members rather than as individual emails.&#x20;

An advantage of this approach is that all team members can see that the email was sent to them all.

The method routeNotificationForEmail is covered in the docs, but it is not clear how this should be used to return multiple recipients <https://laravel.com/docs/8.x/notifications#customizing-the-recipient>.

### Use with Jetstream Teams function

When using [Jetstream Teams](https://jetstream.laravel.com/2.x/features/teams.html) the team owner is not returned by the `$team->users()` relationship.  To obtain the full list including the owner, use the `allUsers()` function.

```php
    public function routeNotificationForMail($notification)
    {
        return $this->allUsers()->pluck('name','email');
    }
```

{% hint style="info" %}
**You can discuss pages on this site at** [**https://github.com/snapey/talltips/discussions**](https://github.com/snapey/talltips/discussions)
{% endhint %}


# Protect Staging site with Basic Auth

When you don't want public sites crawling

If you have a staging instance of your website, it will probably look just like your production server and have all the same pages.

This can cause your production site to be penalised by search engines (because of duplicate content) or potential confusion by customers who happen upon your staging site when then actually want production.

A simple solution is to add basic authentication when the site's environment is \`staging\`.

### Create a middleware

I called this StagingBasicAuth, you can choose this name or whatever makes sense to you

{% code title="App\Http\Middleware\StagingBasicAuth.php" %}

````php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\App;
use Illuminate\Contracts\Auth\Factory as AuthFactory;


class StagingBasicAuth
{
        /**
         * The guard factory instance.
         *
         * @var \Illuminate\Contracts\Auth\Factory
         */
        protected $auth;
    
        /**
         * Create a new middleware instance.
         *
         * @param  \Illuminate\Contracts\Auth\Factory  $auth
         * @return void
         */
        public function __construct(AuthFactory $auth)
        {
            $this->auth = $auth;
        }
    
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @param  string|null  $guard
         * @param  string|null  $field
         * @return mixed
         *
         * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
         */
        public function handle($request, Closure $next, $guard = null, $field = null)
        {
            if(App::environment() == 'staging') {
                $this->auth->guard($guard)->basic($field ?: 'email');
            }
            
            return $next($request);
        }
    }
    

```

````

{% endcode %}

If the app environment is NOT staging then the middleware is skipped and has no effect

### Add middleware to the 'web' group

Add the middleware to the array of `$middlewaregroups` (Laravel 10) / `$routeMiddleware` (earlier).

```php
            \App\Http\Middleware\StagingBasicAuth::class,
            
```

When `APP_ENV = staging` no content from the site will be accessible without first logging in.

### Setting Credentials

By default, the basic authentication guard will validate the user against the users table with the `email` field and hashed password.

### Clearing Basic Auth credentials in Chrome

Whilst testing this, you may come across an issue where Chrome (and possibly others) refuse to logout from the site since as soon as you access the site it sends the cached credentials.  The only reliable method I have found to clear the credentials is to add a route to your site like;

```php
Route::get('/clearbasic', function() { auth()->logout(); abort(401);});

```

Hit this route and Chrome will forget what it thinks are invalid credentials.  You can then revisit the site and be re-prompted for the login.


# Working with Enums

Tips and handy utilities for working with enums in your Laravel project

## Making your Enum invokable

When working with enums, its handy to reference them statically such as when authorizing access to a controller method using a role name.

Personally, I find it clumsy to have to access the Enum static method such as&#x20;

&#x20;   `$this->authorize(Role::MANAGER->name);`

With a tiny function we can simplify this to

&#x20;   `$this->authorize(Role::MANAGER());`

This is because we can make the enum invokable using the magic `__callStatic()` method on the enum. This method is called when a static method is requested but does not exist. Using this we can return an instance of the Enum being used.

An example of an enum class for expenses, using this method;

{% code title="App\Enums\Expenses.php" %}

```php
<?php

namespace App\Enums;

use Illuminate\Support\Arr;

enum Expenses
{
  case CREATE_EXPENSE;
  case DELETE_EXPENSE;
  case APPROVE_EXPENSE;

  public static function __callStatic($name, $args)
  {
    $case = Arr::first(static::cases(), fn($case) => $case->name === $name);

    throw_unless($case, sprintf('Undefined Enum Case %s::%s',static::class,$name));
    
    return empty($case->value) ? $case->name : $case->value;
  }
}

```

{% endcode %}

Now&#x20;

```php
Expenses::CREATE_EXPENSE()   // "CREATE_EXPENSE"
```

{% hint style="warning" %}
Why not just type CREATE\_EXPENSE directly as a string?  Well, the power of enums (and hopefully why you are using them) is because you cannot accidentally use a string which is not declared in your application  Expenses::CRAETE\_EXPENSE() will throw and exception whereas "CRAETE\_EXPENSE" will not.
{% endhint %}

{% hint style="success" %}
Adding this method to your enums does not remove any existing enum abilities
{% endhint %}

### Invokable Enum with Backed Enums

The method shown supports both plain Enums and Backed Enums

A Backed Enum returns a value (string or int) rather than the enum name

For instance, if we wanted to just work with ints in the database, our Enum might be declared as&#x20;

```php
enum Expenses: int
{
  case CREATE_EXPENSE = 1;
  case DELETE_EXPENSE = 2;
  case APPROVE_EXPENSE = 3;
```

Now;

```php
Expenses::CREATE_EXPENSE()   // 1
```

The method returns the name or the value, depending on whether your enum is backed.

### Make a Trait

Since the function is not dedicated to any specific enum, you can create a trait and include it in every enum class.

{% code title="App\Enums\Traits" %}

```php
<?php

namespace App\Enums\Traits;

use Illuminate\Support\Arr;

trait Invokable
{
    public static function __callStatic($name, $args)
    {
      $case = Arr::first(static::cases(), fn($case) => $case->name === $name);
  
      throw_unless($case, sprintf('Undefined Enum Case %s::%s',static::class,$name));
  
      return empty($case->value) ? $case->name : $case->value;

    }
}
```

{% endcode %}

And then use in every enum

```php
<?php

namespace App\Enums;

use App\Enums\Traits\Invokable;

enum Expenses:string
{
    use Invokable;
    
    case CREATE_EXPENSE = 'can create expense';
    case DELETE_EXPENSE = 'can delete expense';
    case APPROVE_EXPENSE = 'can approve expense';

}
```

## Enum methods

Enums are not like normal classes, but they do still allow public methods which may be called against a given Enum case.

For Instance;

### Returning an icon component for a case

```php
    public function icon()
    {
        return match($this) {
            self::CREATE_EXPENSE => '<x-icons-create-expense />',
            self::DELETE_EXPENSE => '<x-icons-delete-expense />',
            self::APPROVE_EXPENSE => '<x-icons-approve-expense />',
        };
    }
```

The match statement is very useful for this type of thing and allows an easy way to return different data for each case.

### Returning a long block of text

```php
    public function longDescription()
    {
        return match($this) {
            self::CREATE_EXPENSE => $this->createDescription(),
            self::DELETE_EXPENSE => $this->deleteDescription(),
            self::APPROVE_EXPENSE => $this->approveDescription(),
        };
    }

    private function createDescription()
    {
        return "A user with this permission is allowed to create expense records for approval by the accounting team";
    }
```

And then use it in blade like

```php
{{ Expenses::APPROVE_EXPENSE->longDescription() }}
```

Or if you are already working with an instance of an enum

```php
{{ $permission->longDescription() }}
```

{% hint style="info" %}
If you are having problems with using enum classes directly in your blade files, dont forget that you can import the class at the top of your blade file with `@use('\App\Enums\Expenses')`
{% endhint %}


# PHP DateTime formatting cribsheet

Describes the placeholders you can use when formatting dates in PHP or with Carbon

Usage with php datetime object or [Carbon](https://carbon.nesbot.com/)

{% tabs %}
{% tab title="Time" %}

### Hour

| Character | Description                                     | Example           |
| :-------: | ----------------------------------------------- | ----------------- |
|     H     | 24-hour format of an hour with leading zeros    | *00* through *23* |
|     G     | 24-hour format of an hour without leading zeros | *0* through *23*  |
|     h     | 12-hour format of an hour with leading zeros    | *01* through 12   |
|     g     | 12-hour format of an hour without leading zeros | *1* through *12*  |
|     a     | Lowercase Ante meridiem and Post meridiem       | `am` or `pm`      |
|     A     | Uppercase Ante meridiem and Post meridiem       | `AM` or `PM`      |

### Minutes and Seconds

|   Character  | Description                | Example           |
| :----------: | -------------------------- | ----------------- |
|       i      | Minutes with leading zeros | *00* through *59* |
|       s      | Seconds with leading zeros | *00* through *59* |
|       u      | Microseconds               | eg *123456*       |
|       v      | Milliseconds               | eg *654*          |
| {% endtab %} |                            |                   |

{% tab title="Date" %}

### Day

| Character | Description                                                    | Example                                                    |
| :-------: | -------------------------------------------------------------- | ---------------------------------------------------------- |
|     d     | Day of the month, 2 digits with leading zeros                  | *01* through *31*                                          |
|     j     | Day of the month without leading zeros                         | *1* through *31*                                           |
|     D     | Textual representation of a day, three letters                 | *Mon* through *Sun*\*\*                                    |
|     l     | (lowercase L) Full textual representation of a day of the week | <p><em>Monday</em> through </p><p><em>Sunday</em> \*\*</p> |
|     N     | ISO-8601 numeric representation of the day of the week         | <p><em>1</em> through <em>7</em> </p><p>(mon=1)</p>        |
|     S     | English ordinal suffix for the day of the month                | *st*, *nd*, *rd*, or *th*                                  |
|     w     | Numeric representation of the weekday                          | *0* (sun) through *6*                                      |
|     z     | The day of the year (zero index for Jan 1)                     | *0* through *365*                                          |

### Week

| Character | Description                                            | Example                                                        |
| :-------: | ------------------------------------------------------ | -------------------------------------------------------------- |
|     W     | ISO-8601 week number of year, weeks starting on Monday | <p>Example: <em>42</em> </p><p>(the 42nd week in the year)</p> |

### Month

| Character | Description                                                        | Example                      |
| --------- | ------------------------------------------------------------------ | ---------------------------- |
| m         | Numeric representation of a month, with leading zeros              | *01* through *12*            |
| n         | Numeric representation of a month, without leading zeros           | *1* through *12*             |
| M         | A short textual representation of a month, three letters           | *Jan* through *Dec \*\**     |
| F         | A full textual representation of a month, such as January or March | *January* through *December* |
| t         | The number of days in the given month                              | *28* through *31*            |

### Year

| Character | Description                                                                                                                                                         | Example                                    |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| Y         | a four digit numeric representation of the year                                                                                                                     | *2020* or *1999*                           |
| y         | a two digit numeric representation of the year                                                                                                                      | *20* or *99*                               |
| L         | Whether it is a leap year                                                                                                                                           | <p>1 for leap year, </p><p>0 otherwise</p> |
| o         | ISO-8601 week-numbering year. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. | 2020 or 1999                               |

\*\* can be varied through localisation
{% endtab %}

{% tab title="Other" %}

|   Character   | Description                                                             | Example                           |
| :-----------: | ----------------------------------------------------------------------- | --------------------------------- |
|       c       | ISO 8601 Full Date Time                                                 | `2020-06-26T13:40:10+00:00`       |
|       r       | [ RFC 2822](http://www.faqs.org/rfcs/rfc2822) formatted date            | `Fri, 26 Jun 2020 13:41:32 +0100` |
|       U       | Seconds since the Unix Epoch (commonly referred to as timestamp format) | *1593175400*                      |
|  {% endtab %} |                                                                         |                                   |
| {% endtabs %} |                                                                         |                                   |


# Livewire Resources

## Datatables

{% embed url="<https://github.com/kdion4891/laravel-livewire-tables>" %}

{% embed url="<https://github.com/mediconesystems/livewire-datatables>" %}

{% embed url="<https://github.com/asantibanez/livewire-charts>" %}


# Naming Livewire Components

If you put your livewire component in a sub folder then its name will be lowercase foldername followed by a period; eg

Livewire model of App\Http\Livewire\Modals\Notifications.php

```
@livewire('modals.notifications')

onclick="Livewire.emit('openModal', 'modals.notifications')" />
```

If your Component Class is Pascal Case, eg **DatabaseNotifications** then the class name called from the frontend will be lowercase and with a hyphen before any uppercase, eg

```
@livewire('modals.database-notifications')

onclick="Livewire.emit('openModal', 'modals.database-notifications')" />
```


# Dynamic Cascading Dropdown with Livewire

When one dropdown depends on what was selected in another

A problem I see frequently on the [Laracasts Forum](https://laracasts.com/discuss) is people struggling with the Javascript required to create dynamic cascading dropdowns. A cascading dropdown uses one form input select box to determine the list presented by a second select. If the dataset is small, all the options can be held locally and the problem is relatively simple Javascript one.

On the other hand, if the dataset is large, the options for the second select might need to be queried from the backend. This then adds the challenge of creating an AJAX request in the browser, creating an API on the server side, and merging the returned data into the current document.

This is a lot of work for someone not comfortable with Javascript, and a lot of opportunity for issues.

I decided to see just how simple this could be using [Livewire ](https://laravel-livewire.com/)by [Caleb Porzio](https://calebporzio.com/). Livewire provides client side components that are ‘hotwired’ to Laravel components, providing two-way data-binding and automatic DOM updates.

![](/files/-MBsfzvfvTFOenKu-0QF)

### Setup

If you already have a project with suitable dataset, then you can skip this section.

I started with a new Laravel project (in the examples below, using Tailwind, but not relevant) and then added a dataset that could be used by the dropdowns.

After (not much!) searching, I came across a Laravel package of countries and cities which seemed it would be suitable. Other datasets are available, but this one migrated then seeded the database tables.

The package is <https://github.com/khsing/laravel-world> . Follow the instructions on the Github page to add the package, service provider and initialise the database.

Unfortunately, the package has not been recently maintained and does not understand that the string helpers have been removed. For our purposes this is not a great issue, we can create new Eloquent models and just tell them to use the `world_` tables;

```php
~/Sites/livewire (master) $ php artisan make:model Country
~/Sites/livewire (master) $ php artisan make:model City
// Country model
protected $table = 'world_countries';
//City model
protected $table = 'world_cities';
```

### Install Livewire

`composer require livewire/livewire`

All the magic of Livewire happens client side through a Javascript library that can be included near the bottom of any page that uses Livewire using a simple blade directive  `@livewireScripts` and then in the head section include the few Livewire styles with `@livewireStyles`.

OK, so now our page is ready for our component. I’m going to call this one simply `dropdowns`. An Artisan command helpfully scaffolds the Laravel module and the blade view.

`php artisan make:livewire dropdowns`

The command creates two files `app\Http\Livewire\Dropdowns.php` and `resources\views\livewire\dropdowns.blade.php`

### The View

The view element of the component is not so different from a regular blade include file. I just create the view pretty much as I would when creating a form containing select dropdowns.

```php
<div>
    <div class="mb-8">
        <label class="inline-block w-32 font-bold">Country:</label>
        <select name="country" wire:model="country" class="border shadow p-2 bg-white">
            <option value=''>Choose a country</option>
            @foreach($countries as $country)
                <option value={{ $country->id }}>{{ $country->name }}</option>
            @endforeach
        </select>
    </div>
    @if(count($cities) > 0)
        <div class="mb-8">
            <label class="inline-block w-32 font-bold">City:</label>
            <select name="city" wire:model="city" 
                class="p-2 px-4 py-2 pr-8 leading-tight bg-white border border-gray-400 rounded shadow appearance-none hover:border-gray-500 focus:outline-none focus:shadow-outline">
                <option value=''>Choose a city</option>
                @foreach($cities as $city)
                    <option value={{ $city->id }}>{{ $city->name }}</option>
                @endforeach
            </select>
        </div>
    @endif
</div>
```

The only things you might not recognise here are the `wire:model` directives. These provide two-way [data binding](https://laravel-livewire.com/docs/data-binding/) with public attributes of the back-end component. Here I have excluded the cities select element if the list would be empty. Leave lines 11 and 22 out if you would prefer to always show the second select.

The component is included in the page blade file with a Livewire directive;

```php
    <div class="flex flex-col justify-around h-full">
        @livewire('dropdowns')
    </div>
```

### Dropdowns Component

So this is your class that is going to to the back-end work 'live' for the dropdown elements

```php
<?php
namespace App\Http\Livewire;
use App\City;
use App\Country;
use Livewire\Component;
class Dropdowns extends Component
{
    public $country;
    public $cities=[];
    public $city;

    public function render()
    {
        if(!empty($this->country)) {
            $this->cities = City::where('country_id', $this->country)->get();
        }
        return view('livewire.dropdowns')
            ->withCountries(Country::orderBy('name')->get());
    }
}
```

Ok, some new stuff to get to grips with here. The public attributes are shared with the view ‘live’ whatever the public property contains, the view has access to. Initially, the cities is an empty array, as until we select a country we don’t know which cities to show.

The `render()` method is called whenever one of the elements in the view component changes, such as when the user changes the Country dropdown. Before invoking render, Livewire re-hydrates the public properties of the component. Thanks to the `wire:model` attribute on the select element, the select’s value is bound to the `country` property in our component. We can then use this to set the cities array using an Eloquent query. When the render method ends by returning the view component, the view is updated with the cities populated in the second dropdown.

We now have a working cascading dropdown. Changing the Country field presents a list of cities in the second dropdown. **Not a single line of Javascript was written.. not even a script tag.**

### Extra: The Mount() method

Suppose these dropdowns are on an edit page, where the user’s previous selection must be presented. In this case, the form is for a Concert.  The previous values can be passed into the `@livewire` directive;

```php
<div class="flex flex-col justify-around h-full">
    @livewire('dropdowns', ['country'=>$concert->country_id), 'city'=>$concert->city_id])
</div>
```

The additional two properties are passed into the `mount()` method where they can be used to initialise the country and city public properties of the Dropdown component. Since the data is bound two-way to the select element, when the page is rendered, the previous entries will be selected.

```php
<?php
namespace App\Http\Livewire;
use App\City;
use App\Country;
use Livewire\Component;
class Dropdowns extends Component
{
    public $country;
    public $cities=[];
    public $city;

    public function mount($country, $city)
    {
        $this->country=$country;
        $this->city=$city;
    }
    
    public function render()
    {
        if(!empty($this->country)) {
            $this->cities = City::where('country_id', $this->country)->get();
        }
        return view('livewire.dropdowns')
            ->withCountries(Country::orderBy('name')->get());
    }
}
```

### **Conclusion**

Livewire makes it super easy to provide areas of your web application front-end that can interact directly with your backend without writing any API or Javascript. It requires a bit of a mind shift in the way you think about how applications should be built. I’m a fan!

<figure><img src="/files/bSseCP4mKTLLj7Mz7XWS" alt=""><figcaption><p>Affiliate Link</p></figcaption></figure>

Support the talltips site by purchasing Ash Allen's excellent book [via this link](https://ashallen.lemonsqueezy.com/?aff=1O08w)


# Hiding a button after click

Livewire can handle loading states in the browser to provide responsive feedback

You can hide a button, and reveal alternate text or spinner when the button is clicked

```markup
<button wire:loading.remove wire:target="send" wire:click="send" class="px-4 mt-2">Send Message</button>
<span wire:loading wire:target="send" class="inline-block px-4 my-3 font-bold text-red-700">Sending</span>
```

When the button is pressed, the `wire:loading.remove` will immediately hide the button until the request is complete. The target is itself.  At the same time the span is revealed with the same `wire:loading` and `wire:target` directives. Once the request is complete, the two elements are swapped back.

Use this for long requests (such as sending an email) to prevent multiple requests from the user impatiently pressing the button.


# Working with Javascript Components

Updating Livewire using Bootstrap DatePicker

{% hint style="warning" %}
Not TALL stack, but this note useful for Livewire + Bootstrap projects when trying to bring Livewire to an older project&#x20;
{% endhint %}

{% hint style="success" %}
If possible, just use the native controls such as `type='date'`
{% endhint %}

When using Bootstrap date picker, just using wire:model on the field is not going to work. Livewire needs telling that the Input field has changed.

```markup
<input 
    wire:model="taskduedate"
    type="text" class="form-control datepicker" placeholder="Due Date" autocomplete="off"
    data-provide="datepicker" data-date-autoclose="true" data-date-format="mm/dd/yyyy" data-date-today-highlight="true"                        
    onchange="this.dispatchEvent(new InputEvent('input'))"
>
```

Livewire is looking for an input event to know that the field is dirty. The date picker seems to not trigger any input events, but it does trigger a regular input onchange event. We can hook into this and dispatch an input event. This causes Livewire to sync the field with the server.

This strategy may work with other similar components.

{% hint style="info" %}
**You can discuss pages on this site at** [**https://github.com/snapey/talltips/discussions**](https://github.com/snapey/talltips/discussions)
{% endhint %}


# SweetAlert2 with Livewire

Use SweetAlert2 to display animated popup alert following Livewire action

Install SweetAlert in master layout

On views that expect to use SweetAlert, or in master layout file, add a listener

```javascript
window.addEventListener('swal',function(e){ 
    Swal.fire(e.detail);
});
```

Then trigger an event from Livewire component when you want to show the popup

```php
$this->dispatchBrowserEvent('swal', ['title' => 'Feedback Saved']);
```

Pass any SweetAlert config elements to style your popup

```php
$this->dispatchBrowserEvent('swal', [
	'title' => 'Feedback Saved',
	'timer'=>3000,
	'icon'=>'success',
	'toast'=>true,
	'position'=>'top-right'
]);
```

{% embed url="<https://sweetalert2.github.io/#usage>" %}

You might instead use this package;

{% embed url="<https://github.com/jantinnerezo/livewire-alert>" %}


# Select Multiple or Checkboxes

How to set the values from existing data

I see many questions on Laracasts where the person seems confused about how to set the value of the select input or checkboxes when using Laravel Livewire.

The simple answer is that you don't need to worry if you set your component up right then Laravel will take care of it for you.

The component illustrates the use of both select (with "multiple" enabled), and checkboxes.&#x20;

<figure><img src="/files/scKkBiAAoatI7Nt2LbCr" alt=""><figcaption></figcaption></figure>

The code for the component looks like

{% code title="Livewire/RolesTest.php (part)" lineNumbers="true" %}

```php
<?php

namespace App\Http\Livewire;

use App\Models\User;
use Livewire\Component;
use Spatie\Permission\Models\Role;

class RolesTest extends Component
{
    public int $user_id;
    public string $user_name;
    public array $userRoles;

    public function mount(User $user)
    {
        $this->user_id = $user->id;
        $this->user_name = $user->name;

        $this->userRoles = $user->roles()->pluck('id')->toArray();
    }

    public function render()
    {
        return view('livewire.roles-test')
            ->withRoles(
                cache()->remember('roles',60, function(){
                    return Role::all();
                })
            );
    }

    protected $rules = [
        'userRoles.*' => 'exists:roles,id',
    ];

    public function submit()
    {
        $this->validate();
 
        $user = User::findOrFail($this->user_id);

        $user->roles()->sync($this->userRoles);

     }

}

```

{% endcode %}

The important points are;

**Line 13,** the user's existing roles are pulled from the user model roles relationship, and stored in the component **as an array**

**Line 26,** all possible roles are passed to the view.  Here, the cache is used to keep a record of the possible options in the cache for 60 seconds. So if the component is changed multiple times, then the roles are not re-queried from the database each time.

**Line 34**, since we are holding the choices as an array, we can use Laravel's array validation to ensure that each member of the array matches the rules, and in this case, the role must exist in the roles table.

**Line 43**, once the roles have been chosen, since they are an array of IDs, they can be simply passed into the sync() function against the roles relationship.  No matter what the roles before the save, syncing the roles with the array will set the new roles only.

### The view

#### Select box with multiple option

The view contains both input types for the benefit of this article only.  I suggest that you use checkboxes over the multi-select because of the tricky UI to select multiple, and not accidentally clearing existing roles.

{% code title="roles-test.blade.php (part)" lineNumbers="true" %}

```html
    {{-- Method using multi-select input --}}
    <select multiple wire:model.lazy="userRoles" class="w-1/2 rounded form-multiselect">
        @foreach($roles as $role)
            <option value="{{$role->id}}">{{$role->name}}</option>
        @endforeach
    </select>
    <div class="text-sm italic text-gray-600">Alt/Cmd click to select multiple roles</div>
    
```

{% endcode %}

For the Multiple Select, we need to `wire:model` the select to the roles array then iterate over the possible roles for each option.  We do NOT need to use the `selected` html parameter on each option as this will be applied automatically by Livewire' `wire:model.`

Neither do we need to worry about the `old()` helper, again Livewire will take care of this for us.

#### The Checkbox Option

{% code lineNumbers="true" %}

```html
    {{-- method using checkboxes --}}
    <div class="flex flex-col my-8 space-y-1">
        @foreach($roles as $role)
            <div class="flex justify-between">
                <label for="role-{{$role->name}}">{{$role->name}}</label>
                <input class="rounded form-checkbox" id="role-{{$role->name}}" 
                    type="checkbox" value="{{$role->id}}" wire:model.lazy="userRoles" />
            </div>
        @endforeach
    </div>
```

{% endcode %}

For each of the possible roles, we output a label and checkbox. Each individual checkbox is bound to the array of the user roles. Livewire takes care of changing the correct array member according to the `value` of the checkbox.&#x20;

Again, we don't need to care about the `old()` helper or the `checked` state

## Beware of user manipulation of public component properties

With Livewire2, you should be wary of a user manipulating the public properties of your component, such as changing the user\_id to that of another user.  Check the video;

{% embed url="<https://www.youtube.com/watch?v=bA1dMbUiwuA>" %}

{% hint style="info" %}
**You can discuss pages on this site at** [**https://github.com/snapey/talltips/discussions**](https://github.com/snapey/talltips/discussions)
{% endhint %}


# Clearing checkboxes in Livewire

When setting empty array does not clear checkboxes

I encountered an issue where checkboxes are not cleared when the array they are bound to is cleared.

Checkbox input elements like this;

```markup
<input wire:model="recipients.{{ $contact->id }}" 
    type="checkbox" 
    name="recipient-{{ $contact->id }}" 
    id="recipient-{{ $contact->id }}"
>
```

{% hint style="info" %}
the array of recipients uses dotted notation to specify the index
{% endhint %}

Recipients are contacts in the system and are output as an array for the user to select a number of recipients and then perform the action.  As each recipient is checked a key/value pair is added to the array.

```php
array:2 [
  42 => true
  34 => true
]
```

Checkboxes that have not been selected are not present in the array. Selecting then deselecting a checkbox causes its entry to be set false;

```php
array:2 [
  42 => false
  34 => true
]
```

Once the user has selected a number of recipients and then performed the activity, the checkboxes should be cleared.  Setting the `$recipients` to an empty array does nothing to the view. The previously checked checkboxes are still checked following render.

The solution to clear the checkboxes is to iterate through the array in the Livewire component, setting recipients to false;

```php
foreach($this->recipients as &$recipient) {
    $recipient = false;
}
```

*note the use of & to pass the array entry by reference.*


# Livewire File Uploads Using S3

## Enabling CORS on your S3 Bucket

If you want to upload directly from the browser to S3 then you need to create a CORS policy for the domain. Without the correct policy your direct upload to S3 will not be permitted, and in the browser console, you will see an error such as; `Access to XMLHttpRequest at '......' from origin 'http://example.test' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.` &#x20;

{% embed url="<https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html>" %}

Go to your bucket in the S3 console, click **Permissions** then **CORS**&#x20;

Create a policy that mentions the domain name(s) of your site and the Verbs that you want to allow.

```markup
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>http://mysite.test</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>https://www.my-live-domain.org</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

```

## Removing filename from input field after upload

If you have a view in which the file upload control remains on-screen after completing an upload, you will find that after saving the upload, the file input cannot be returned to the 'No file chosen' state.  The file input element is immutable and cannot be set or cleared.

One trick is to give the upload button an id that is specified in the Livewire back-end component. A simple counter suffices.  After the upload of a file, increment the count. The component will be removed from the dom and replaced with a new file input element using a new ID - and an empty filename.

![](/files/-MFkoOq_BK0bgWqnR8eR)

```markup
<input wire:model="attachment" type="file" name="attachment" id="upload{{ $iteration }}" />
```

After saving

```php
        //clean up
        $this->attachment=null;
        $this->iteration++;
        
```


# Simple Log File Viewer

Display log files in your application using Livewire and Alpine

I was having a lot of problems displaying log files in the admin area of my application.  The Logfiles were being written to following each transaction, and a typical daily log file could be up to 10MB. The best available packages would crash and burn with this size of logfile.

I tried both [rap2hpoutre/laravel-log-viewer](https://github.com/rap2hpoutre/laravel-log-viewer) and [arcanedev/log-viewer](https://github.com/ARCANEDEV/LogViewer) and ended up building a simple viewer with pagination of the logs. Alpine is used to collapse stack trace.

In TALL stack style, the logfiles are displayed using Tailwind.

### Livewire Component

`php artisan make:livewire LogsViewer`

{% code title="app/Http/Livewire/LogsViewer.php" %}

```php
<?php

namespace App\Http\Livewire;

use Illuminate\Support\Facades\File;
use Livewire\Component;
use SplFileInfo;

class LogsViewer extends Component
{
    public $file=0;
    public $page=1;
    public $total;
    public $perPage = 500;
    public $paginator;

    protected $queryString=['page'];

    public function render()
    {

        $files = $this->getLogfiles();

        $log=collect(file($files[$this->file]->getPathname(), FILE_IGNORE_NEW_LINES));

        $this->total = intval(floor($log->count() / $this->perPage)) + 1;

        $log = $log->slice(($this->page - 1) * $this->perPage, $this->perPage)->values();

        return view('livewire.logs-viewer')->withFiles($files)->withLog($log);


    }

    protected function getLogFiles()
    {
        $directory = storage_path('logs');

        return collect(File::allFiles($directory))
            ->sortByDesc(function (SplFileInfo $file) {
                return $file->getMTime();
            })->values();
    }

    public function goto($page)
    {
        $this->page=$page;
    }

    public function updatingFile()
    {
        $this->page=1;
    }
}

```

{% endcode %}

### View file

Of note here is the detection of the \[stackdump] sections, moving these to a nested block and hiding that block using Alpine. Clicking the `[stackdump]` in the log expands the nested section and reveals the stack dump.

{% code title="resources/views/livewire/logs-viewer.blade.php" %}

```php
<div>
    <x-slot name="header">
        <h2 class="text-xl font-semibold leading-tight text-gray-800">
            Log Files
        </h2>
    </x-slot>

    <div class="px-4 py-2 mx-4 my-8 bg-white shadow-xl sm:rounded-lg">
        <div class="flex justify-around">
            <select wire:model="file" class="px-4 py-2 font-mono text-sm bg-red-200 rounded">
                @foreach($files as $file)
                <option value="{{ $loop->index }}">{{ $file->getFilename() }}</option>
                @endforeach
            </select>
        </div>

        @include('layouts.logs-paginator')
        
        @if($log->count()>0)
            <ul class='font-mono text-xs'>
                
                @for($i=0; $i < $log->count(); $i++)
                    @if(Illuminate\Support\Str::startsWith($log[$i],'[stacktrace]') || Illuminate\Support\Str::startsWith($log[$i],'#'))
                        <li x-data="{expanded:false}" x-on:click="expanded = !expanded">[stacktrace]
                            <ul class="ml-8" x-show="expanded" x-cloak >
                                @while($i < $log->count())
                                    <li wire:key="{{$page}}-line-{{ $i }}">{{ $log[$i] }}</li>
                                    @break(Illuminate\Support\Str::startsWith($log[$i++],'"}'))
                                @endwhile
                            </ul>
                        </li>
                    @endif
                    @break($i>=$log->count())
                    
                    <li wire:key="{{ $page }}-line-{{ $i }}" class="font-mono text-xs leading-5  
                        {{ Illuminate\Support\Str::contains($log[$i], '.CRITICAL:') ? 'text-red-800':''}}
                        {{ Illuminate\Support\Str::contains($log[$i], '.ERROR:') ? 'text-orange-600':'' }}
                        {{ Illuminate\Support\Str::contains($log[$i], '.INFO:') ? 'text-blue-900':'' }}
                        {{ Illuminate\Support\Str::contains($log[$i], '.WARNING:') ? 'text-indigo-700':'' }}
                        ">{{ $log[$i] }}
                    </li>
                @endfor
            </ul>
        @endif
    </div>

    
</div>

```

{% endcode %}

### Paginator

{% code title="resources/views/layouts/logs-paginator.blade.php" %}

```php
<div class="flex float-right">
    <button id="first" wire:click="goto(1)" class="w-10 outline-none px-2 border rounded-l-lg m-0 {{ $page==1 ? 'bg-gray-600 text-white font-bold' :'' }}" >1</button>
    
    @if($page-4 > 1)
        <button id="dots1" class="w-10 px-2 m-0 -ml-px border outline-none">&hellip;</button>
    @endif

    @if($page-3 > 1)
        <button id="minus3" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page-3 }})">{{ $page-3 }}</button>
    @endif
    @if($page-2 > 1)
        <button id="minus2" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page-2 }})">{{ $page-2 }}</button>
    @endif
    @if($page-1 > 1)
        <button id="minus1" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page-1 }})">{{ $page-1 }}</button>
    @endif

    @if($page != 1 && $page != $total )
        <button id="current" class="w-10 px-2 m-0 -ml-px font-bold text-white bg-gray-600 border outline-none">{{ $page }}</button>
    @endif

    @if($page+1 < $total )
        <button id="plus1" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page+1 }})">{{ $page+1 }}</button>
    @endif
    @if($page+2 < $total )
        <button id="plus2" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page+2 }})">{{ $page+2 }}</button>
    @endif
    @if($page+3 < $total )
        <button id="plus3" class="w-10 px-2 m-0 -ml-px border outline-none" wire:click="goto({{ $page+3 }})">{{ $page+3 }}</button>
    @endif
    
    @if($page+4 < $total )
        <button id="dots2" class="w-10 px-2 m-0 -ml-px border outline-none">&hellip;</button></button>
    @endif
    
    @if($total>1)
        <button id="last" class="rounded-r-lg w-10 outline-none px-2 border -ml-px m-0 {{ $page == $total ? 'text-white bg-gray-600 font-bold':'' }}" wire:click="goto({{ $total }})">{{ $total }}</button>
    @endif

</div>
```

{% endcode %}

Add a protected link to your `web.php` file to the Logfile component.


# Testing resources

{% embed url="<http://laravel.at.jeffsbox.eu/laravel-5-testing-cheatsheet-laravel-specific-phpunit-testing-assertion>" %}


# When Composer runs out of memory

{% hint style="info" %}
Upgrade to Composer 2 to avoid memory issues
{% endhint %}

You can remove php memory restrictions with

`php -d memory_limit=-1 /usr/local/bin/composer require league/flysystem-aws-s3-v3`

Where `/usr/local/bin` is the path to your composer file.

You can check the path to composer with the command `which composer`


# Deployment

articles to help get your project on-line

{% embed url="<https://calebporzio.com/easy-free-serverless-laravel-with-vercel>" %}

{% content-ref url="/pages/ygvUXsh2FAIaYMroJDhC" %}
[Cpanel resources](/related-resources/cpanel-resources)
{% endcontent-ref %}


# Security

Computer Security lectures from Stanford <https://web.stanford.edu/class/cs253/>


# Scheduler & Cron tips

The method below has been usurped in Laravel 8.10 by the command `php artisan schedule:work`

{% embed url="<https://dev.to/jcs224/laravel-task-scheduling-without-cron-jobs-for-local-development-4i84>" %}


# LastPass tips

`data-lpignore="true"` if you want to disable LastPass on the input.


# Using Git

## How to Contribute to Open Source

A great free video course on contributing to Open Source projects by Kevin McKee and using Livewire for his pull request examples

{% embed url="<https://contributetoopensource.com/>" %}


# VSCode Tips

## Keyboard Shortcuts cribsheet

{% embed url="<https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf>" %}


# Markdown

{% embed url="<https://www.markdownguide.org/>" %}


# Cpanel resources

### Running a queue worker continuously on a Cpanel server

{% embed url="<https://laracasts.com/discuss/channels/laravel/laravel-job-queue>" %}

### Deployment

{% embed url="<https://hafizmohammed.medium.com/how-to-deploy-laravel-in-cpanel-the-right-way-78d0a767d5a2>" %}


