Blog


Reusing form in Laravel for both 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

It starts with simplifying the controller;

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

<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()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 its 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 to save the form data also.

<?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. 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. This is not the case. Understanding the 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.


Styling Livewire Paginator with Tailwind css

Spent some time today, styling the Livewire paginator in Tailwind. May be useful to someone.

Wrap the pagination links in a container;

<div class="livewire-pagination">{{ $flowStatus->onEachSide(2)->links() }}</div>

Add styles to your app.css

/* purgecss start ignore */
.livewire-pagination {
     @apply inline-block w-auto ml-auto float-right;
}
ul.pagination {
    @apply flex border border-gray-200 rounded font-mono;
}
.page-link {
    @apply block bg-white text-blue-800 border-r border-gray-200 outline-none py-2 w-12 text-sm text-center;
}
.page-link:last-child {
    @apply border-0;
}
.page-link:hover {
    @apply bg-blue-700 text-white border-blue-700;
}
.page-item.active .page-link {
    @apply bg-blue-700 text-white border-blue-700;
}
.page-item.disabled .page-link {
    @apply bg-white text-gray-300 border-gray-200;
}

/* purgecss end ignore */

Be sure to wrap your css in purgecss ignore comments since the classes used by the paginator are not in any files that are scanned by purgecss and will be stripped if you don’t tell it to ignore.


User interface for display of Laravel Logfiles

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 would be 10MB.

The best available packages would crash and burn with this size of logfile.

I tried both rap2hpoutre/laravel-log-viewer and arcanedev/log-viewer

Controller

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use SplFileInfo;
use File;

class LogsController extends Controller
{
    public $perPage = 500;

    public function index()
    { 

        $files = $this->getLogfiles();

        return view('logs.index')->withFiles($files)->withLog([]);
    }

    public function show(Request $request, $index)
    {
        
        $files = $this->getLogfiles();
        
        $log = collect(file($files[$index]->getPathname(), FILE_IGNORE_NEW_LINES));
        
        $page = intval($request->get('page',1));
        
        $paginator['page'] = $page;
        $paginator['total'] = intval(floor($log->count() / $this->perPage))+1;

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

        return view('logs.index')
            ->withFiles($files)
            ->withLog($log)
            ->withP($paginator)
            ->withIndex($index);
        
    }

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

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

        return $logFiles;
    }

}

View (Bootstrap 4 flavour)

@extends('layouts.app')

@section('content')
<div class="container-fluid">

    <h5 class="pt-3">Logs</h5 class="pt-3">

    <div class='row'>
        <div class='col-md-2'>
            <ul class='list-group'>
                @foreach($files as $key => $file)
                    <li class='list-group-item'><a href="{{ route('logs.show',$key) }}">{{ $file->getFilename() }}</a></li>
                @endforeach
            </ul>
        </div>
        <div class='col-md-10'>
            @if(isset($p))
            <ul class="pagination">
                <li class="page-item @if($p['page']==1) active @endif"><a class="page-link" href="{{ route('logs.show',['index'=>$index, 'page'=>1]) }}">First</a></li>

                @if($p['page']-4 > 1) <li class="page-item disabled"><a class="page-link"  href="#">&hellip;</a></li>@endif
                @if($p['page']-3 > 1) <li class="page-item"><a class="page-link" href="{{ route('logs.show',['index'=>$index, 'page'=>$p['page']-3]) }}">{{ $p['page']-3 }}</a></li>@endif
                @if($p['page']-2 > 1) <li class="page-item"><a class="page-link" href="{{ route('logs.show',['index'=>$index, 'page'=>$p['page']-2]) }}">{{ $p['page']-2 }}</a></li>@endif
                @if($p['page']-1 > 1) <li class="page-item"><a class="page-link" href="{{ route('logs.show',['index'=>$index, 'page'=>$p['page']-1]) }}">{{ $p['page']-1 }}</a></li>@endif
                
                @if($p['page'] != 1 && $p['page'] != $p['total'] )
                    <li class="page-item active"><span class="page-link">{{ $p['page'] }}</span></li>
                @endif

                @if($p['page']+1 < $p['total']) <li class="page-item"><a class="page-link" href="{{ route('logs.show',['index'=>$index, 'page'=>$p['page']+1]) }}">{{ $p['page']+1 }}</a></li>@endif
                @if($p['page']+2 < $p['total']) <li class="page-item"><a class="page-link" href="{{ route('logs.show',['index'=>$index, 'page'=>$p['page']+2]) }}">{{ $p['page']+2 }}</a></li>@endif
                @if($p['page']+3 < $p['total']) <li class="page-item"><a class="page-link" href="{{ route('logs.show',['index'=>$index, 'page'=>$p['page']+3]) }}">{{ $p['page']+3 }}</a></li>@endif
                @if($p['page']+4 < $p['total']) <li class="page-item disabled"><a class="page-link"  href="#">&hellip;</a></li>@endif

                @if($p['total']!=1)
                    <li class="page-item @if($p['page'] == $p['total']) active @endif"><a class="page-link" href="{{ route('logs.show',['index'=>$index, 'page'=>$p['total']]) }}">Last</a></li>
                @endif
            </ul>

            @endif

            <ul class='list-group list-group'>
                @foreach($log as $logline)
                        <li class='list-group-item small 
                            @if(Illuminate\Support\Str::contains($logline, '.CRITICAL:')) list-group-item-danger @endif
                            @if(Illuminate\Support\Str::contains($logline, '.ERROR:')) list-group-item-warning @endif
                            'style="padding: 0.25rem 1.25rem;">{{ $logline }}</li>
                @endforeach
            </ul>
        </div>
    </div>

</div>

@endsection

Routes

Route::get('/logs', 'LogsController@index')->name('logs');
Route::get('/logs/{index}', 'LogsController@show')->name('logs.show');

Preferably wrapped in Auth Middleware

Thats it.  Just add /logs to your navigation


Using Spatie Valuestore to hold frequently used settings

This was a reply posted to the Laracasts forum.  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;

  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;

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

and in your model when you want to apply this;

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


Using Laravel Polymorphic relationships for different user profiles

Laravel Eloquent polymorphic relations are similar to standard relationships, except that the model on one side of the relationship is defined by a column in the database table. Typically this would allow a single ‘child’ model to be attached to multiple different parents, for example, a Comment model can belong to a Post or a Page. An Image model can belong equally to an Item or a Category.  In this example we are going to reverse this typical arrangement and have the User model belong to different profile types.

Imagine a belongsTo relationship where in a table contains a foreign key to the ID of the other model, a polymorphic relationship uses an additional column to specify the model also.

In this example, we will be able to create tables (and models) that contain profile information for different types of users. The Admin user has one set of attributes and a Customer has a totally different set of attributes. Both types still use the User model for identification and access to our application.

Method

Assuming a simple Laravel installation, where php artisan make:authhas been used to create a basic authentication solution.

We can add an AdminProfile and CustomerProfile

php artisan make:model AdminProfile -m
php artisan make:model CustomerProfile -m

(the -m will create migrations as well as the model)

In the migration files, add the attributes that are specific to that type of user

Schema::create('admin_profiles',function(Blueprint$table){
  $table->increments('id');
  $table->string('department')->nullable();
  $table->string('payroll')->nullable();
  $table->string('manager')->nullable();
  $table->timestamps();
});

 Schema::create('customer_profiles', function (Blueprint $table) {
   $table->increments('id');
   $table->string('address1')->nullable();
   $table->string('address2')->nullable();
   $table->string('address_city')->nullable();
   $table->string('address_postcode')->nullable();
   $table->string('address_country')->nullable();
   $table->string('mobile')->nullable();
   $table->string('landline')->nullable();
   $table->timestamps();
 });

The only other database work to do is to add the polymorphic columns to the existing users table.  We can create a new migration to extend the users.

php artisan make:migration add_polymorphism_to_users --table=users
 Schema::table('users', function (Blueprint $table) {
   $table->string('profile_type')->nullable();
   $table->unsignedInteger('profile_id')->nullable();
 });

A little work on the models so that they have the polymorphic relationship

User Model

Add to the existing user model;

 protected $with = ['profile'];
 
 public function profile()
 {
   return $this->morphTo();
 }

AdminProfile Model

<?php namespace App; 

use Illuminate\Database\Eloquent\Model; 

class AdminProfile extends Model 
{ 
  protected $guarded = [];
  
  public function user() 
  { 
    return $this->morphOne('App\User', 'profile');
  }
}

CustomerProfile Model

<?php namespace App; 

use Illuminate\Database\Eloquent\Model; 

class CustomerProfile extends Model 
{ 
  protected $guarded = [];
  
  public function user() 
  { 
    return $this->morphOne('App\User', 'profile');
  }
}

Run the migrations php artisan migrate

We can now have two different profile types associated with any user.  Two users have been created, and they have no profile associated with them at the moment.

Let’s test it with Tinker;
$profile = App\AdminProfile::create(['manager'=>'Donald','department'=>'Retail'])
$profile->user()->save(User::find(1))

We now have created a profile for our administrator user and attached it to their user account

$profile = App\CustomerProfile::create(['address1'=>'Lilac Cottage','address2'=>'Leeming Lane'])
$profile->user()->save(User::find(2))

And created a customer profile and attached this user 2

We can check the database and see the profile_type column has been populated

Thanks to the $withattribute added to the user model, whenever we get a user, we will also get the profile fields

>>> User::find(1)
=> App\User {#2962
 id: 1,
 name: "john doe",
 email: "john@mycorp.com",
 email_verified_at: null,
 created_at: "2019-01-19 13:00:23",
 updated_at: "2019-01-19 13:06:06",
 profile_type: "App\AdminProfile",
 profile_id: 1,
 profile: App\AdminProfile {#2972
   id: 1,
   department: "Retail",
   payroll: null,
   manager: "Donald",
   created_at: "2019-01-19 13:05:39",
   updated_at: "2019-01-19 13:05:39",
   },
 }
>>> User::find(2)
=> App\User {#2966
 id: 2,
 name: "jane smith",
 email: "jane@hotmail.com",
 email_verified_at: null,
 created_at: "2019-01-19 13:00:54",
 updated_at: "2019-01-19 13:12:12",
 profile_type: "App\CustomerProfile",
 profile_id: 1,
 profile: App\CustomerProfile {#2980
   id: 1,
   address1: "Lilac Cottage",
   address2: "Leeming Lane",
   address_city: null,
   address_postcode: null,
   address_country: null,
   mobile: null,
   landline: null,
   created_at: "2019-01-19 13:12:02",
   updated_at: "2019-01-19 13:12:02",
   },
 }

Bonus

To make it easy to know what type of user profile we are dealing with, we can extend the User model with a couple of accessors

  public function getHasAdminProfileAttribute()
  {
    return $this->profile_type == 'App\AdminProfile';
  }

  public function getHasCustomerProfileAttribute()
  {
    return $this->profile_type == 'App\CustomerProfile';
  }


>>> User::find(2)->hasAdminProfile
=> false
>>> User::find(2)->hasCustomerProfile
=> true
>>>


Using Browser-Sync with Laravel … without Mix or Webpack

Prompted by a question on Laracasts, I wanted to see if I could use Browsersync to automatically refresh the browser after each code change.  I mostly do back-end development, but all the docs about Browsersync are related to front-end JS and CSS development.  My needs were simpler.  I don’t have a front-end build process such as Laravel Mix, and wanted to monitor my view files and controllers etc.

After reading a little about Browsersync, I have my browser reloading automatically any time a view is saved, or anything in the app folder. It was quite easy to setup.

Install Browsersync

in the root of your laravel project, install Browsersync globally;

> npm install -g browser-sync

Create a Browsersync init file

> browser-sync init

Edit the Config file

Open Browsersync init file bs-config.js in your editor and tell it what folders to monitor;

"files": ["resources/views/**","app/**","public/**"],

and where to send the sync messages (within socket:)

"domain": 'localhost:3000'

Launch Browsersync

From the console, start Browsersync

> browser-sync start --config bs-config.js

You should now be able to navigate to the Browsersync UI in the browser at http://localhost:3000

Add javascript client to your application

I know it seems a lot of steps, but these are all really quick. The final step is to add the Browsersync javascript to your project – inside a check to see if we are in development mode.

In a master layout file (eg app.blade.php) add the following before the closing tag.

@if (getenv('APP_ENV') === 'local')
    <script id="__bs_script__">//<![CDATA[
    document.write("<script async src='http://HOST:3000/browser-sync/browser-sync-client.js?v=2.24.4'><\/script>".replace("HOST", location.hostname));
    //]]></script>
@endif

 

Gotchas

The above is working on OSX using Valet+Nginx as the web server – your mileage may vary.

If your code crashes and whoops appears, then obviously the browsersync code is no longer running in the browser, and after you fix the issue you will need to refresh manually.  I wonder if its possible to extend the whoops handler….

I was able to get Whoops handler to include the browsersync javascript but only by editing the file in the vendor folder – which is never a good idea;

vendor/filp/whoops/src/Whoops/Resources/views/layout.html.php

put the browsersync code before the closing body tag

Shout out to JH – I have been to their offices several times for meetings of phpMinds in Nottingham, and not realised Browsersync was their open source project.

 


Laravel Blade to deal with null dates

This example is for Laravel 5.5 and should be compatible with 5.6 onwards but no guarantees!

The problem

When using date and datetime fields in Laravel it is really useful to add them to the $dates array in the Eloquent model so that they are automatically cast to Carbon instances.

Then in the views, the format can be easily specified;

<td>{{ $task->completed_at->format('d-m-Y H:i') }}</td>

This works fine until the date is null. In our example, the task completed date is not set until the task is completed, and with the above code, sometimes we are trying to apply Carbon format to a null object.

The view then starts to get messy, checking if the date is null before outputting the Carbon format.

Enter Blade.  Using Blade we can hide some of this complexity from our views.

If you haven’t written Blade directives before, they can be confusing. The purpose of a blade directive is to return a string of PHP that can be inserted into the cached view file, and then executed later, when the view is rendered.  This means it is not possible to directly interact with any of your data within the blade component itself.

 Creating a blade directive

If you are creating just a single blade Directive then its possible to just put this in the AppServiceProvider, but as I might create more, and also be able to easily lift this and put it in other projects, I decided to create a BladeServiceProvider.php file and then put all my directives in there.

Start with the template for a Service Provider  php artisan make:provider BladeServiceProvider

and add this new provider to your config/app.php file

/*
 * Application Service Providers...
 */
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\BladeServiceProvider::class,

Add our new directive to the BladeServiceProvider  boot method

public function boot()
{
    /**
     * @date blade directive 
     * use as @date($object->datefield) 
     * or with a format @date($object->datefield,'m/d/Y')
     */
    Blade::directive('date', function ($expression) {

        $default = "'d-m-Y H:i'";           //set default format if not present in $expression
        
        $parts = str_getcsv($expression);

        $parts[1] = (isset($parts[1]))?$parts[1]:$default;

        return '<?php if(' . $parts[0] . '){ echo e(' . $parts[0] . '->format(' . $parts[1] . ')); } ?>';
    });
}

Line 8 is the start of our Blade definition.  Make sure you import Illuminate\Support\Facades\Blade  at the top of the service provider. Here we declare ‘date’ is the name of our new directive. When the Blade extension is used it will be passed $expression, and this will be a single string from which we have to parse our parameters.

Line 12 breaks the expression into multiple parts as an array

Line 14 ensures that we have a $parts[1] element so that we can always pass a string to the format method.  The default value is specified on line 10 and will be used as a fallback if the format is not specified in our view.

Finally, Line 16 is the meat of this Blade. This strange concatenation of strings and parts is building up the final output that will go into our cached view file for processing when the final view is rendered. The function of this is to create an in-line php code that checks if the value is not null and if so, echo it with the Carbon format command appended.  If the value is null then nothing is echoed.

What have we created?

We now have a Blade directive that can format dates if they are not null and optionally accepts a date format

<td>@date($task->completed_at)</td>

<td>@date($task->archived_at,'Y-m-d')</td>

This last example, produces Blade output of

<td><?php if($task->archived_at){ echo e($task->archived_at->format('Y-m-d')); } ?></td>

Tips!

Working with Blade can be extremely time consuming.  Don’t underestimate the amount of time it takes fiddling with the output string.

Don’t forget, you cannot interact with the data within the blade directive itself.

Always clear cached views php artisan view:clear each time you make a change to your BladeServiceProvider


Using FontAwsome SVG and Laravel Blade to output a star rating

Simple code snippet to put out star outlines, stars and half stars as a rating

Uses Font Awesome (https://fontawesome.com/) for SVG icons

@foreach(range(1,5) as $i)
    <span class="fa-stack" style="width:1em">
        <i class="far fa-star fa-stack-1x"></i>

        @if($rating >0)
            @if($rating >0.5)
                <i class="fas fa-star fa-stack-1x"></i>
            @else
                <i class="fas fa-star-half fa-stack-1x"></i>
            @endif
        @endif
        @php $rating--; @endphp
    </span>
@endforeach

Note that $rating is expected to contain a value 0 to 5 (including fractions).  It will be affected by the [php]$rating–;[/php] so keep a copy if you need it later.

By altering the stack order, the stars can be outlined.

 


Allow user to choose pagination length via dropdown (Laravel)

The following technique allows the user of your site to choose the pagination length from a dropdown menu.

In this example, the members of a club are paginated.

When displaying the links, we need to append the current page length

{{ $members->appends(compact('items'))->links() }}

The Select list is created as follows

<form>
    <select id="pagination">
        <option value="5" @if($items == 5) selected @endif >5</option>
        <option value="10" @if($items == 10) selected @endif >10</option>
        <option value="25" @if($items == 25) selected @endif >25</option>
    </select>
</form>
<script>
    document.getElementById('pagination').onchange = function() { 
        window.location = "{!! $members->url(1) !!}&items=" + this.value; 
    }; 
</script>

Changing the dropdown causes the page to be reloaded, passing `items=10` etc to the controller. The controller checks for items or uses a default value.

    public function index(Request $request)
    {
        $items = $request->items ?? 10;      // get the pagination number or a default

        $club = Club::findOrFail(session('club'));

        $members = $club->members()->paginate($items);
       
        return view('club.' . config('app.locale') . '.index')
            ->withClub($club)
            ->withMembers($members)
            ->withItems($items);
    }

In the controller method, $itemsis passed back to the view so that it can be used to ensure the current length is selected


Methods to obtain Laravel 5.5 user ID

Obtaining the user instance in Laravel Controller;

All these return the same result

dump($request->user()->id);        //using the $request variable passed into to controller

dump(request()->user()->id);        //using the request() helper method

dump(Auth::id());                   //using Auth facade

dump(Auth::user()->id);             //getting user object from Auth facade

dump(auth()->id());                 //using auth() helper

dump(auth()->user()->id);           //getting user object from auth helper

Methods that use the id from the user object will throw an error if there is no logged in user


Using multiple Laravel pagination links on one page

I came across a problem in a Laravel 5.3 project where I wanted two independent paginated areas on a single view.

multiple-pagination

Using just {{ $model->links() }} on the page would cause both lists to be paged at the same time since the URL contains ?page=1 and does not distinguish which set is being paged.

The answer is to include additional parameters to the paginate() function in the controller.

https://laravel.com/api/5.4/Illuminate/Database/Eloquent/Builder.html#method_paginate

By passing in a third parameter we can change the default ‘page’ to a name that can vary for our two sets of data.

 

    public function index()
    {
        $cbShows = Show::champBreed()->paginate(150, ['*'], 'cbShows');
        $owShows = Show::openWorking()->paginate(300, ['*'], 'owShows');

        return view('shows.index')->with(compact('owShows','cbShows'));
    }

We can now page through the two sets of data independently. The links will be adapted like

/shows?owShows=3

The second parameter [‘*’] is an array of the columns to be selected and must be set in order to allow the third parameter to be passed.


Changing the Laravel 5.3 password reset email text

passwordreset
Problem:  You use the out of the box authentication and password reset code.  The email that is sent to the user is in English, but you need it in another language.

You know that you should never edit code that is in the vendor folder, so what do you do?

Thankfully, Taylor included a hook where we can write our own mailable notification, and the password broker provides the required token.

Assuming you have working boilerplate auth functions but you need to change the text of the password reset email as easily as possible;

Create a notification

php artisan make:notification MailResetPasswordToken

edit this file which you find in a new folder App\Notifications

<?php

namespace App\Notifications;

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

class MailResetPasswordToken extends Notification
{
    use Queueable;

    public $token;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($token)
    {
        $this->token = $token;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
                    ->subject("Reset your password")
                    ->line("Hey, did you forget your password? Click the button to reset it.")
                    ->action('Reset Password', url('password/reset', $this->token))
                    ->line('Thankyou for being a friend');
    }

}

 

Override the send password reset trait with your local implementation in your User.php user model

    /**
     * Send a password reset email to the user
     */
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new MailResetPasswordToken($token));
    }

remembering to import the class at the top of the user model

use App\Notifications\MailResetPasswordToken;

If you need to alter the layout of the message, including the text “If you’re having trouble clicking the “Reset Password” button,” then you need to run

php artisan vendor:publish

and then edit the files in

/resources/views/vendor/notifications

Logging SQL queries in Laravel 5

To create Log entries for every sql query, include the following before the code you need to check. Even drop it into the routes file if you want to log every transaction.

DB::listen(function ($sql) {
        Log::info($sql->sql);
        Log::info('Bindings: ' . implode(', ',$sql->bindings));
        Log::info('Last Query took ' . $sql->time . ' seconds');
    }
); 

If including in a controller, you will need to add at the top of the controller

use DB;
use Log;

Restful delete with Laravel

A quick snippet if you are wanting to delete models and are not using the Collective Forms;

 <form action="/run/{{ $item_id }}" method="POST">
 {{ csrf_field() }}
 <input type="hidden" name="_method" value="DELETE" />
 <button type="submit"><i class="fa fa-trash" /></i></button>
 </form>

Install Laravel 5.2 on shared hosting from Fasthosts

Fasthosts is a popular hosting provider in the UK although shared hosting should be avoided if possible for Laravel projects.  Unless you are happy to accept unpredictable response times, you will always be better off with dedicated hosting, and my recommendation is Digital Ocean and clicking through here will start your account with $10 credit (an affiliate link).

When working with Fasthosts, you will notice that most configuration changes are queued for execution. If you are struggling to connect to the database or to ftp, leave it 10 minutes and try again.

In this post, you will learn how to move local copy of your site to Fasthosts and then to replace the htdocs folder with a symlink that points to the Laravel public folder.

1. Create a linux hosting account

Here I am creating an account for the project ‘gxplan.com’.  Choose Linux hosting with mysql database.

Since my domain name is not yet fully registered, request a test url. Through this, you can reach your site even though the correct URL is not yet assigned.

Since DNS is not yet configured, click Enable under test domain

Since DNS is not yet configured, click Enable under test domain

The banner confirms the test account;

Note the test URL. You will use this later to access the site

Note the test URL. You will use this later to access the site

2. Setup FTP

The default web folder for public content at Fasthosts is called htdocs. This will cause us some problems but we can tackle that in a moment.  The first step is to upload your dev install onto the web server.

Go into the FTP settings and create a password for the hosting account.  Make sure this is a strong password and use a tool like Lastpass to keep a record of the password chosen.

Create FTP account and set the password.

Create FTP account and set the password.

 

Once you have the password set (give it a few minutes) then configure your FTP client application to use these credentials.  If, like me, you have not yet configured the domain name, you will need to use the server IP address rather than the Hostname.

Copy the entire project folder into the folder above the htdocs folderThis is important.  When you first login, you will be landed in the htdocs folder.  You need to move up a level (as below).

The default folder structure

The default folder structure

Copy all your files into the root folder.

Make sure the .htaccess file is uploaded to the public folder. My FTP client excludes this by default.

Note:

I had to remove the Multiviews option in htaccess as this was not supported by Fasthosts (here I have commented out the line with #)

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        # Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

3. SSH

Although it is possible to ssh into the server, the version of PHP available to you in SSH shell is 5.2 (at the time of writing). This means that it will not be possible to run any artisan commands from the SSH shell.

You will use SSH though, so create a password and if necessary (for Windows) install PuTTY or other ssh client. Mac users can run ssh directly from Terminal.

Create a password for your SSH user

Create a password for your SSH user

Once SSH is setup, connect to the server.

The task now is to create a symbolic link called htdocs, pointing to the public folder.  A symbolic link redirects one file or folder to another file or folder.  By this, when the web server tries to look in htdocs for the index.php file, it will be directed instead to the public folder.

  • Change to the htdocs folder and delete the cgi-bin folder.
  • Move up a level and delete the htdocs folder.
  • Create a symlink from htdocs to public
$ cd htdocs
$ rmdir cgi-bin
$ cd ..
$ rmdir htdocs
$ ln -s public htdocs

htdocs now points to the public folder, meaning you can FTP synchronise your development machine with the shared hosting whenever you need to push updates.

Remember to exclude the hidden .env file from the FTP folder from future synchronisation since it will contain your development settings and not your production settings.

4. Setup MySQL database

You will almost certainly use a database.  If so, request a new database from the control panel;

Setting up a mysql database

Setting up a mysql database

After requesting the database, it will take a few minutes for it to be installed.  Wait for this to be completed before adding a database user.

Make a note of the database IP address. It is not 'localhost'

Make a note of the database IP address. It is not ‘localhost’

Add a user. Make sure the DBO flag is checked. Remember the password and store it in Lastpass or similar.

Add a user. Make sure dbo is selected

Add a user. Make sure dbo is selected

Once the database is setup, you can log into phpMyAdmin using the username and password just created.  Having previously backed up your development database, you can now restore it to your new host.

Importing a database backup into your new database

Importing a database backup into your new database

 

Completed upload

Completed upload

The upload is completed, and my basic install of migrations, password_resets and users is visible in the gxplan database.

5. Edit the .env file on the Fasthosts webserver

The final step is to configure the .env file on the FTP server.

Compared to the development server copy;

  • delete the APP_ENV line (it will default to production)
  • delete the APP_DEBUG line (it will default to false)
  • Configure the database credentials according to the settings from the new server

6. Done

If you have managed to follow this far, then you should now have a working copy of your application, hosted at Fasthosts

Thats it! My test site is published and working

Thats it! My test site is published and working


Backing up a Laravel site to Amazon S3 with laravel-backup

Installing Laravel-backup

For this activity I’m going to be using the Laravel-Backup tool from Spatie

https://github.com/spatie/laravel-backup

Follow the instructions on the github readme.

  1. composer require spatie/laravel-backup
  2. add service provider to app.php  Spatie\Backup\BackupServiceProvider::class,
  3. publish the config file. This adds a new laravel-backup config item to the config folder
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"

To start with, we will test the backup using the local file system, as comes enabled  by default.

You now have a couple of additional functions in Artisan

backup-commands

Try php artisan backup:run

If it returns an error then it’s probably because your system cannot find the needed mysqldump command. If this is not found then you will have to track it down on your machine and then adjust the myqsl dump_command_path in the laravel-backup.php file.  Make sure the path ends in a forward slash as the mysqldump command will be appended to it.

'mysql' => [
 /*
 * The path to the mysqldump binary. You can leave this empty
 * if the binary is installed in the default location.
 */
 'dump_command_path' => '/Applications/MAMP/Library/bin/',

When using the default Local storage, the backup will be in the storage/app/backups folder. At this point, you should test the backup files

Configuring S3 to receive the files

Assuming that you have an AWS S3 account with Amazon.

First of all create the bucket that will be used. I recommend a separate bucket for each site since that allows you to secure them individually.

Next, create an IAM identity for the bucket. Identity and Access Management is an identity management solution and will prevent the credentials stored on this website from accessing other backups.  The last thing you want is an intruder on one site accessing the backups for other sites since these backups will contain access credentials for those other sites.

  1. Select create user
  2. Enter a name for the user (the name of the site perhaps)
  3. Copy the access credentials. These will be used to configure Laravel Flysystem in a moment.

Select the User and click Permissions then Inline Policies

Select Create One, then Custom Policy

Provide a policy name (no spaces)

Add the policy as below, adding the name of your new bucket

{
    "Version": "2012-10-17",
    "Statement": [
    {
        "Effect": "Allow",
        "Action": "*",
        "Resource": [
            "arn:aws:s3:::your-bucket-name",
            "arn:aws:s3:::your-bucket-name/*"
         ],
         "Condition": {}
     } 
     ]
}

Configure Laravel Flysystem

Edit config/filesystems.php

Under disks->s3, change the following so that the S3 keys can be picked up from .env and not end up in your repo.

 'driver' => 's3',
 'key' => env('S3_KEY'),
 'secret' => env('S3_SECRET'),
 'region' => env('S3_REGION'),
 'bucket' => env('S3_BUCKET'),

Then set the .env file with the actual values

S3_KEY='AKI****F2CH4****PFKQ' #your access key
S3_SECRET='kEjL********r3r+4QjkbU********NQIiiEfhb' #secret access key
S3_REGION='eu-west-1'
S3_BUCKET='your-bucket-name'

Next tell laravel-backup to use S3 (config/laravel-backup.php)

/* 
* The filesystem(s) you on which the backups will be stored. Choose one or more 
* of the filesystems you configured in app/config/filesystems.php 
*/ 
  'filesystem' => ['s3'],

and set the folder in which to store the backup

 /*
 * The path where the backups will be saved. This path
 * is relative to the root you configured on your chosen
 * filesystem(s).
 *
 * If you're using the local filesystem a .gitignore file will
 * be automatically placed in this directory so you don't
 * accidentally end up committing these backups.
 */
 'path' => 'backup',

Install S3 library

The S3 libraries are not shipped by default so you will need to add these via composer

composer require league/flysystem-aws-s3-v3 ~1.0

Test so far…

You should run the backup again and hopefully your files will be pushed to S3, which you can inspect through the S3 file browser

 

Configuring CRON to run the job

Configure the server to call the Schedule:run artisan command every minute. This is covered in the Laravel docs.

If your host does not support CRON, then A suggestion is made in an earlier blog post.

Setup entries in your Http/kernel.php file;

protected function schedule(Schedule $schedule)
 {
    $schedule->command('backup:run',['--only-files' => '','--suffix' => '_files'])
        ->weekly()->mondays()->at('03:00')
        ->description('My-project Files backup')
        ->sendOutputTo('storage/logs/backup.log')
        ->emailOutputTo('mark@novate.co.uk')
        ->before(function(){
            Log::info('Commencing Files Backup');
        })
        ->after(function(){
            Log::info('My-project Files backup complete');
        });

    $schedule->command('backup:run',['--only-db' => '','--suffix' => '_db'])
        ->twiceDaily(2,14)
        ->description('My-project Database backup')
        ->sendOutputTo('storage/logs/backup.log')
        ->emailOutputTo('mark@novate.co.uk')
        ->before(function(){
            Log::info('Commencing Database backup');
        })
        ->after(function(){
            Log::info('My-project Database backup complete');
    });
 }

So here, I have two backup jobs, one running once per week for all the files, and then a twice-daily database backup.  Following each, the log of the backup is sent via email.

Summary

This has been a long-winded setup as there are multiple steps.  Laravel-backup is a very flexible backup solution and leverages league\flysystem to store backups to the cloud.

Using Amazon S3 and protecting it with IAM provides a robust destination for your backups.


Weekly notables August 9th 2015

Awesome list of Laravel related links at getawsomeness.com

Hacking with PHP is where I found inspiration for the use of flock http://www.hackingwithphp.com/8/11/0/locking-files-with-flock

BBC Bloggers publish 13 tips for making responsive web design multi-lingual

I use Google Authenticator for Gmail, Lastpass and Digital Ocean – keep an eye on this one A One Time Password Authentication package, compatible with Google Authenticator.

One of my favourite YouTubers Travis Neilson shares his favourite tools http://travisneilson.com/workflow-tools/


Using a file lock to stop cron jobs updating the same record

Recently I was faced with an issue of deliberately overlapping cron jobs both trying to work on the same database record.

After some research, using flock (file lock) seemed to be a good option.

At each minute, I trigger a job to update the database with information scraped from another site (public domain information – before you ask). I let each job run for just under 3 minutes, so that at any time there are 3 jobs running. If I want to push it further I can just up the maximum time for each job.

The project is in Laravel and uses Eloquent for ORM and mysql for the database.  The problem faced was that the query to find out which record to service could take a relatively long time so that two processes could come to the same answer about which was the next record to update.

//open a lock file - can be used to pause other processes when they are also trying to query db
$lockfile = fopen(storage_path('locks/operatorUpdate.lock'),"w");

while(microtime(true)-$time_start < 178) {

    flock($lockfile, LOCK_EX);

        // establish oldest record
        $operator = \App\Operator::orderBy('checked_at','asc')->orderBy('id','asc')->first();
        $operator->checked();

    flock($lockfile, LOCK_UN);

    // do what I need here to process the record just grabbed

}
fclose ($lockfile);

I decided to keep the lock file in a storage folder called locks. I used the storage_path() call to ensure that the path is the same irrespective of how I called the function. (cron jobs default to the root user home folder)

Bear in mind that I’m placing a blocking lock on the file just whilst I grab the oldest record and set its checked_at date. A second job coming along at the same time will hit the lock and wait for the quarter second it takes for the original query to run (I have 280,000 rows in the table).