Laravel5


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


Supporting delete cascade with SQLite and Laravel

If using SQLite, it is useful to be able to cascade a delete to related models. For instance, if a user is deleted, all their posts should also be deleted rather than being orphaned.

In the schema for the pivot table, you specify;

    $table->integer('post_id')->unsigned();
    $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade'); 

    $table->integer('user_id')->unsigned();
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 

This works out of the box for mysql, but with sqllite it is not supported without turning it on.

I had this issue, and created a workaround, but I’m not comfortable with the solution because it required me to change the Laravel source. I’m only a newbie so could not really see an ‘app’ way of doing it.

In config/database.php

    'sqlite' => [
        'driver'   => 'sqlite',
        'database' => storage_path().'/database2.sqlite',
        'prefix'   => '',
        'exec'	   => 'PRAGMA foreign_keys = ON;',  //enable delete cascade
    ],

I added a new element ‘exec’

Then in /vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php, replace;

    return $this->createConnection("sqlite:{$path}", $config, $options);

with

    $pdo=$this->createConnection("sqlite:{$path}", $config, $options);

    //any exec statement?
    $exec = array_get($config, 'exec');
    if(isset($exec))
    {
        $pdo->exec($exec);
    }
    return $pdo;

This allows the foreign_keys property to be set each time the connection is opened, and also any additional exec statements that might be needed.


Deploy Laravel 5 on shared hosting from Heart Internet

For trial sites, and quick to deploy, low traffic tools, its perfectly possible to host your site at Heart Internet using subdomains. Although these instructions are specific to Heart, they will work for other hosts, with and without subdomains.

Wait.....
Before you do anything – check that your host is providing PHP V5.4 or better (Laravel 5.0) or PHP 5.5.9 or better (Laravel 5.1 / 5.2)

1. Request subdomain setup

Heart run their subdomains on the same server. A folder is created in the public_html folder for the subdomain. For instance, I’m creating a service that will respond to dj3.mydomain.com  on the mydomain.com server, there will be a folder called public_html/dj3

After requesting the subdomain, wait an hour for the DNS to all be in place.

2. Upload your site

Your laravel code base should be located in a folder that is not accessible from the web.

Create a new folder in your root folder based on the name of your subdomain.  This is incase you want to install another application, you can put each backend in its own space.  Here i have used the name dj3core

dj3 directory

FTP everything except your public folder into the back-end folder that you created (dj3core in my example)

FTP the contents of your public folder into the subdomain folder (dj3 in my example)

Make sure that you copy the hidden file .htaccess also into your subdomain folder.  Do not put it in the root or the public_html folder

3. Fix the paths in the index.php file

You need different paths in the index.php file to what you have probably been testing with, so before uploading or inplace on the hosted server, edit the index.php (the one in the subdomain, eg /public_html/dj3/index.php) file as follows;

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels nice to relax.
|
*/

// require __DIR__.'/../bootstrap/autoload.php';
require __DIR__.'/../../dj3core/bootstrap/autoload.php';

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

// $app = require_once __DIR__.'/../bootstrap/app.php';
$app = require_once __DIR__.'/../../dj3core/bootstrap/app.php';

I have retained the original lines, and added the modified lines below.

Compared to the distribution, the application can be found up two directories (../) and in the dj3core folder.

Thats it! Your site should now be working in the subdomain dj3.mydomain.com

4. Problems?

If you are still having problems, check that the storage folder is writable.

At the time of writing, I have not tested email, but I don’t expect there to be a problem.

If you are using the HTML and URL helpers make sure the url is set correctly in the config/app.php file.

 


Laravel 5 checkbox processing

One of the annoyances of HTML form processing is that checkboxes are not returned if they are unchecked.

This causes an issue if you just want to use Laravel’s automatic validation of forms and then want to be able to pass the validated form response to the model. Whilst it is possible to manage checkboxes in the controller it always strikes me as messy. My solution is below. There will always be detractors that claim the validator is not the place for this, my argument is that I am validating that what comes from the validator is either true or false and not true or missing.

Since the rules area of the request object is actually a method, it is possible to interact with the content of the request.

So, in my EditUserRequest class, where I have a checkbox named ‘is_admin’;

	public function rules()
	{
		// default the value of the is_admin checkbox
		$this->merge(['is_admin' => $this->input('is_admin', 0)]);

		return [
			'name' => 'required|min:5',
			'email' => 'required|email',
		];
	}

I merge back into the request, the value of the input, or a default (the second option to Request->input) of 0. This sets the checkbox element to 0 if it is not present.

Then in the controller, I can use the simple;

		$user->update($request->all());

Laravel 5 csrf tokens in ajax calls

In Laravel 5, all requests must pass through the Middleware which will not allow any POST requests without the correct CSRF token.

CSRF (Cross Site Request Forgery) prevents the site receiving requests from clients that it has not established a connection with. IE a random post request from a third party.

When using ajax to post form or changes in state, the csrf token must be supplied along with the request.

For instance, if the view being rendered contains the javascript, simply use blade tags to insert the token directly into the script:

                $.ajax({
                      type: "POST",
                      url: "/poke",
                      data: {   lat: lastlat,
                                lng: lastlng, 
                                bearing: 90,
                                '_token': '{!! csrf_token() !!}'
                            }
                    })

If the javascript is in a separate file (not processed by Blade) then the token can be set on a meta element and then queried by jQuery at runtime.

<meta name="csrf-token" content="{!! csrf_token() !!}">

Putting the above in the master page layout ensures that the csrf token is available in every page

Referring then to the meta element in each javascript ajax request;

                $.ajax({
                      type: "POST",
                      url: "/poke",
                      data: {   lat: lastlat,
                                lng: lastlng, 
                                bearing: 90,
                                '_token': $('meta[name="csrf-token"]').attr('content')
                            }
                    })

Thanks to Kelt Dockin for inspiration