Laravel 5.8 – Dusk Chrome Driver and Chrome Browser must be the same

I was setting up dusk for browser testing and ran into an issue that was causing an error when I ran tests.

session not created: This version of ChromeDriver only supports Chrome version 75

If you want to run a different version of Chrome you may specify the Chrome Driver with its artisan command.

php artisan dusk:chrome-driver 74

The Chrome browser and Dusk Chrome Driver must be the same.

Laravel conditional for @extends in blade

I have a Laravel app that has multiple users.  Depending on the user I need to display a different blade template.  The problem is that it is not possible to use a regular

@if($role == $something)
  @extends('layout.app')
@else
  @extends('layout.other')
@endif

After much searching I found that @extends is required to be on the first line so it is possible to have a conditional if you do it this way.

@extends($var ? 'layout.app' : 'layout.other')

Hope this saves you some time.

Laravel AWS s3 file storage – IAM users and Permissions

This seemed easy with Laravel 5.4 supporting s3 disks but it took a bit of setting up and a lot of trial and error so this is what is needed to get it working.

  1. Setting Laravel up to use s3

Ensure that you follow the docs and add flysystem to your composer.json.

"league/flysystem-aws-s3-v3": "~1.0"

Then update composer.

composer update

2. Sign up for AWS

3. Create your first bucket.

There are 4 steps to creating a bucket.

Add a unique bucket name and choose a region.

Nothing to change or add on this screen or up to step 4. Just click next on each.

Your bucket will now be added to the system.

4. Create a IAM user with the right permissions.

Click on services in the top bar and then click on IAM.

On the IAM screen click on users and then add user.

Add a user name and tick the programmatic access. Click next.

Click on the ‘Attach Policies’ and then ‘Create Policy’.

A new tab will open (remember that as we will be coming back to the above page shortly. Click on the select button for ‘Create your own policy’.

To add a policy copy the code below exactly and do not change the date for the version. Make sure you do change the ‘bucket-name’ in two locations to your buckets name which you just created.

Use Amazon S3 with Laravel 5

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::bucket-name"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::bucket-name/*"
            ]
        }
    ]
}

Check the policy and then click on create if all is good.

Go back to the previous tab we talked about earlier not to forget. Find the policy that you just made.  You should click on the refresh button first then you may need to use the search so just start typing the policy name.  Tick the checkbox next to the policy you just created and click next down the bottom right.

Create the user.

This is the newly created user.  It is VERY IMPORTANT to copy the key and secret key down here.  You will not be able to access it at any other time.

Done the bucket and the user has been added to AWS s3 now.  You can start to store files here now.

5. Adding Bucket to Laravel

IN you .env file add this to the bottom of the page.

S3_KEY=XXXXXXXXXXXXXXX
S3_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
S3_REGION=XX-XXXXXXX-XX
S3_BUCKET=your-bucket

In your config/filesystems.php

in disks change to refer to your .env file.  This keeps the access keys out of your version control.

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

You can also change the default disk to ‘s3’ if that suits you.

Why are my images not showing up?

In AWS s3 a buckets contents are private and cannot be accessed from a website or even with a direct url.  Laravel allows you to individually set permission for files which are saved to s3.  If you would like to allow all images to be visible from you bucket you can set permission for the bucket in AWS.  By doing this you will be able to show images on your website which are stored in your bucket.  This also means that you do not need to specify in your project for each file.

In AWS go to s3 and click on your bucket.

Click on permission on the top tab. Click on Bucket policy. Add the code snippet below and save.

This article helped and provided the code below. Of course change the ‘your bucket’ to the name of your bucket. Click on save.

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "AllowPublicRead",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-bucket/*"
        }
    ]
}

Saving files.

However you have your upload set up here are a few tips.

I am using intervention to assist with my file uploads.  So in your code mine is a service class I use for uploads but yours might just be in your Model or controller.

This article helped

$saved = Image::make($file)->resize($imageSize, null, function ($constraint)
        {
            $constraint->aspectRatio();
            $constraint->upsize();
        })->orientate();

        $newImage = $saved->stream();

        Storage::disk('s3')->put($path . $name, $newImage->__toString());

        return $saved;

The important parts here are the stream() and then the __toString().

Dont forget to include Storage facade in your code e.g.

use Illuminate\Support\Facades\Storage;

That is about it . You have all the elements to make this work.

If you are having problems getting this to work locally check out my trouble uploading to s3 from local

Laravel Dom PDF issues with font awesome and a Unicode work around

So I am using Laravel-DomPDF by Barryvdh in my Laravel 5.4 project, which makes it super easy to create PDF documents from Laravel Views.

Kudos to Barryvdh for providing a simple way to produce PDF documents from Laravel.

So, I wanted to pull Font Awesome into the blade view for a couple of icons I had on the page.  Problem was that it was not working.  I tried everything suggested from full url for the link, to adding a fonts folder to /storage/app.  None worked for me.

As I only need a tick and an exclamation on the page I decided to add the styles straight into the head of my blade file and use Unicode.

I found that Dom PDF already pulls in DeJaVu Fonts which is a project that is aiming for complete coverage of alphabetic scripts. The other thing is that it has a bunch of symbols.  Not as awesome as Font Awesome but enough for my needs.

In between the head tags I added this.

<style>
.fa-check:before {
            font-family: DejaVu Sans;
            content: "\2611";
            color:darkgreen;
            font-size:1.2rem;
}
.fa-exclamation-triangle:before{
            font-family: DejaVu Sans;
            content: "\26A0";
            color:darkorange;
            font-size:1.2rem;
 }
</style>

This is the check:

And this is the exclamation:

Neat hey! Where did I get the content:”\????” from?

Miscellaneous_Symbols

This was a quick and dirty solution to my issue and may help you.  While I agree it’s not ideal it is better than changing to another PDF project or spending more time trying to get this to work with Font Awesome at the moment.

Laravel event handler for last login

I recently needed to show on the admin side of an app the last login of all users and if they had logged in at all.  I also wanted to show the user the last time that they visited the site.  I used event handlers for both these tasks.

Update the Users Table

I have used the user model to store a timestamp for each event, so I added two new rows to my users table.  I will then be able to access these easily in my blade template.

$table->timestamp('latest_login')->nullable();
$table->timestamp('previous_visit')->nullable();

On the user model I also added both timestamps to the proteced $dates array. By doing this I will be able to use ‘Carbon’ helpers like ‘diffForHumans’.

protected $dates = [
        'created_at',
        'updated_at',
        'latest_login',
        'previous_visit'
    ];

Creating the Event Handlers:

On the admin side I used the login event and for the user I used the logout event to record the current time and date to the users table.

In the EventServiceProvider service provider (app/Providers/EventServiceProvider.php) I added the following to the $listen array:

'Illuminate\Auth\Events\Login' => [
    'App\Listeners\Users\LatestLogin',
],
'Illuminate\Auth\Events\Logout' => [
    'App\Listeners\Users\PreviousLogin',
],

I then ran php artisan event:generate. This command then creates two event listeners located at app\Listeners\Users\LatestLogin and app\Listeners\Users\PreviousLogin.

Each event listener’s handle()method I added the following.  

public function handle(Login $event)
    {
        $event->user->latest_login = Carbon::now();
        $event->user->save();
    }
 public function handle(Logout $event)
    {
        $event->user->previous_visit = Carbon::now();
        $event->user->save();
    }

I also added:

use Carbon\Carbon;

to each listener.

Now when a user logs in or logs out the event listener fires and updates the appropriate row of the users table for the user.

Blade

Finally for completeness in my blade file I can call:

{{ $user->latest_login->diffForHumans() }}

And it will output something like – 1 hour ago – or however long it was since the user signed in.

For the user in the view I add:

Last Visit: {{ Auth::user()->previous_visit->diffForHumans() }}

It outputs something like: Last Visit: 2 days ago

That is about it. I hope this helps someone else out.

Laravel Raw Statements for Database Queries

Laravel has a powerful query builder but there are limitations when database queries become more complex.  Enter Raw queries.

The docs have an example  of a raw query which will get you going but sometimes there is a need to completely ditch the builder and and go it alone.

The following example is what I used create a query which is completely raw.

DB::select(DB::raw('
        SELECT  Months.id AS `month` ,
            COUNT(story.id) AS `count`
            FROM 
            (
              SELECT 1 as ID UNION SELECT 2 as ID UNION  SELECT 3 as ID UNION SELECT 4 as ID 
              UNION  
              SELECT 5 as ID UNION SELECT 6 as ID UNION SELECT 7 as ID UNION SELECT 8 as ID 
              UNION  
              SELECT 9 as ID UNION SELECT 10 as ID UNION SELECT 11 as ID UNION SELECT 12 as ID
            ) as Months
            LEFT JOIN `story` on Months.id=month(story.created_at)
                                   AND 
                                   (company_id = :companyId) 
                                   AND (YEAR(created_at)= :thisYear)
            GROUP BY Months.id 
            ORDER BY Months.id ASC'), array('companyId'=>Auth::user()->company_id, 'thisYear'=>Carbon::now()->year));

The important thing to notice here is that when using the raw query you can pass variables straight into the query which could be a security issue especially if the information is being passed from a from by a user.

To ensure that any values that need to be passed to the query are sanitized they can be passed in an array after the query.

Take a look at the query and the ‘And’ clauses.

company_id = :companyId

The :companyId in the example is how you add your variables using the array.

...ORDER BY Months.id ASC'), array('companyId'=>Auth::user()->company_id, 'thisYear'=>Carbon::now()->year));

In the array give the key and then the value to be added.

That is it.  Hope it helps with your complex Laravel queries.

cURL error 60: SSL certificate problem: unable to get local issuer certificate – Laravel Notifications error

In a new application I was adding in Slack notifications and while testing I got cURL error 60: SSL certificate problem: unable to get local issuer certificate...

I am working on a Windows 10 machine with xampp.  I found the solution on Laracast Forum with a little bit of effort so I thought I would outline the steps succinctly to save a little time in the future.

I am going to assume that you have a version of Guzzle which is 6.2 or around that.

Step 1.

Download a fresh PEM file from this link https://gist.github.com/VersatilityWerks/5719158/download

This is a zip file which you will save into xampp.

Step 2

Unzip this file and save the file cacert.pem in D:/xampp/php/extras/ssl.

Step 3

Update the php.ini file.  D:/xampp/php/php.ini

Alternatively to locate the php.ini file open the XAMPP control panel and click on Config adjacent to Apache.

Search and find curl.cainfo and add the full path to the the cacert.pem file you just added.
The new line should be something like this:
curl.cainfo = D:\xampp\php\extras\ssl\cacert.pem

Step 4

Restart Apache and try and fire off your notification again. It should just work now.

Laravel error – Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes

When trying to migrate the Auth database tables on a fresh Laravel 5.4 install I got the following error.

[Illuminate\Database\QueryException]
  SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQ
  L: alter table `users` add unique `users_email_unique`(`email`))

Aftera bit of Google’ing I found a solution that workded for me.

Add the following line in the users migration file.

 Schema::defaultStringLength(191);

So the create_users_table should look like this.

public function up()
    {
        Schema::defaultStringLength(191);
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

Original solution was found here.

Using Factories to seed your database with Laravel

I have recently explored factories for seeding my database. Previously I looked at seeding a database in a way which is very simple but necessary for my needs.  I also needed some dummy data on bulk to save a little time so I have used a factory to do this.

So I needed to add some dummy data to a table but I needed it to follow some specific rules.  This is what I did.

Open database\factories\ModelFactory.php

$factory->define(App\FrameworkOutcome::class, function (Faker\Generator $faker) {
    return [
        'framework_id' => rand(1,3),
        'category_id' => rand(1,5),
        'outcome' => $faker->sentence(10),
        'order' => 0,
        'active' => 0,
        'created_at' => date('Y-m-d H:i:s'),
        'updated_at' =>  date('Y-m-d H:i:s')
    ];

});

There is a factory for users already set up and ready to go.  It is pretty self explanatory but I will expand a little on what I did.  If you want you can skip down to where I add in the call within the DatabaseSeeder.php to get it to run.

In the code above and for your needs you can override any details you want by passing an array for your table contents.

The FrameworkOutcome model I am working with relates to two other tables Framework and Categories.  Suffice it to say that I have populated these tables but to make my outcomes relevant to these other tables I have had to specify certain attributes such as 'framework_id' needs an integer of 1, 2 or 3.  I used rand(1,3) to achieve this and the same for ‘category_id’.

I used $faker->sentence with 10 words in the sentence. There are a bunch of faker formatters which can be found on the Github page.

Now once this is all set up, go to database\seeds\DatabaseSeeder.php and add the following line in the ‘run’ method.

factory(FrameworkOutcome::class, 20)->create();

Run the seeder: php artisan db:seed

Twenty records with random data within the specified guidleines will be added to the database.