Skip to main content

Getting Spatie Media Library’s PDF thumbnail to work with Laravel

I’ve just spent a good couple of hours troubleshooting why Spatie Media Library won’t create a thumbnail of a PDF I upload to my server.

I’ve already setup up the correct code to generate a conversion, but nothing happens.

The real issue is that there are a couple of dependencies to make it work, which is Ghost Script and Imagick. Both of these need to be installed on your server for it to work.

Install Ghost Script
sudo apt-get install ghostscript
Once installed, check it works: gs –version

Install PHP Imagick
sudo apt-get install php-imagick
Once installed, check it works: php -m | grep imagick

However, even when everything is set up properly, I was receiving a server error:
“not authorized @ error/constitute.c/ReadImage/412”

To fix this, you need to SSH in and edit the policy.xml of Imagick located into etc/ImageMagick-6/policy.xml

Look for this line:

 <policy domain="coder" rights="none" pattern="PDF" /> 

and replace with:

 <policy domain="coder" rights="read|write" pattern="PDF" /> 

You also may want to add:

 <policy domain="coder" rights="read|write" pattern="LABEL" /> 

You then need to restart your server, in my case sudo service nginx restart

Then, restart your PHP FPM Workers.

Only then, should your PDF thumbnails work.

Unfortunately, I couldn’t get it working on Laragon locally.

Laravel session value not persisting when using AJAX requests

I had a very weird issue in Laravel where a specific session value was not persisting after an AJAX request.

To give it some context, I did two AJAX requests, one after the other.

The first AJAX request sets the session value.

The second AJAX request was then not seeing that value.

The issue is a known limitation with sessions in Laravel and the way to fix this is to enable SESSION_BLOCK.

Go into your session.php and if it doesn’t exist, add this:

'block' => env('SESSION_BLOCK', false),

Then, add SESSION_BLOCK=true to your ENV file.

Attach unique in BelongsToMany Laravel/Eloquent

As you probably know, you can use the attach() method to relate two models within a BelongsToMany relationship. The only problem with attach() is that it doesn’t check to see if the relationship already exists. So, essentially, you could attach the same model more than once to a relationship, which isn’t ideal.

For instance, you have pizzas and ingredients.

You could always do something like this:

$relationship = $pizza->ingredients()->where('ingredient_id', $ingredient->id)->get();
if(!$relationship)
{
$pizza->ingredients()->attach($ingredient);
}

Or, you could use this method, which checks if the relationship exists and it doesn’t double up:

$pizza->ingredients()->syncWithoutDetaching($ingredient);

What this will do is sync the ingredient with the pizza (like the sync() method), however, if the relationship already exists, it won’t do anything. The relationship will stay, and nothing else will detach.

Unique email validation in Laravel when re-saving model

When you validate an email in Laravel, you have the ability to specify whether you want a specific model to be excluded. The reason why you would exclude a model is for example, if you have a user account and the user needs to update their first name, but their email stays the same. Typically, Laravel would re-save the whole form and also validate the email, but because that user is already using the email, it will spit back a “unique” validation error mentioning the email already exists in the system (even though it is still being used by said user.

Read More

Convert ISO8601 date to MYSQL date time using Carbon in PHP / Laravel

I was working on a project using VueJS and was using this datepicker which is a great vue plugin.

However, when sending the date to the database in Laravel, it posts as ISO8601 format (eg. 1996-10-15T00:05:32.000Z)

MySQL doesn’t like that format, so we need to convert it to the correct date time format so MYSQL can store it.

To do it, we use Carbon. Carbon comes with Laravel which is awesome, so we can just import Carbon into our controller and make it work.

Read More

Laravel S3 uploads failing when uploading large files in Laravel 5.8

Working on a project for a customer using Laravel 5.8, I found that when uploading files to S3 using Laravel’s Fly System, that the uploads were failing after a certain size.
The error back was a validation error mentioning the file was unable to be uploaded.

To troubleshoot this, there were a few things I did:

  1. Ensured my PHP.INI settings were allowing uploads high enough. I found that PHP.INI only allows 2M by default, so I increased this limit to 20M. I also increased the max post time to 300.
  2. I found out that Amazon S3 doesn’t like files over 5 MB uploading in one go, instead you need to stream it through

Streaming the files

My solution was to have the file upload to the server first, and once it is uploaded, it is streamed from the server to S3.

 

Read More

Using S3 with Laravel 5.8

With any web application, file uploads are a royal pain in the ass.

Storing files on your web server is not the best way to go about things, especially with Laravel.

Ideally, you can have the below setup:

  1. Website code on github
  2. Laravel site hosted using Laravel Forge
  3. Nightly MYSQL backups
  4. Files hosted on s3

With this setup, your site is pretty scalable and if your web host goes down on S3, you can easily copy over to a new server with Laravel Forge, and then simply load up the site again, as all of your files are referenced to Amazon S3.

The below snippet is a simple way to take a file request from a page, validate it for types of files, then save to S3, to a folder called “customer uploads”. You’ll notice I’m also making the file public, so it can be accessed publicly. You can remove the ‘public’ argument if you do not want it to be public.

This function will save the file to S3, then return the full URL to the file on Amazon S3, so you can store it in your database and it can be accessed.

public function uploadAttachment(Request $request)
{

	$file = $request->file('file');

	$request->validate([
	'file' => 'required|mimes:pdf,jpeg,jpg,png,gif',
	]);

	// Save to S3
	$savetoS3 = Storage::disk('s3')->put('customer-uploads', $file,'public');

	// Return file path
	return response()->json( Storage::disk('s3')->url($savetoS3) );

}

If you were to make files private, but still want them downloadable, say through an admin area, you have to do things a bit differently.

You have to save the file as a private file in S3, then store in your database a reference to it.
In the example below, I create a model called “FileLibrary” and have an ID and URL column.
Store the downloaded file in the database.

use Illuminate\Support\Facades\Storage;

public function uploadAttachment(Request $request)
{

	$file = $request->file('file');

	$request->validate([
	'file' => 'required|mimes:pdf,jpeg,jpg,png,gif',
	]);

	// Save to S3
	$savetoS3 = Storage::disk('s3')->put('customer-uploads', $file);

        
        $file = new FileLibrary;
        $file->url = $savetoS3;
        $file->save();

	// Return file ID in database
	return response()->json( $file->id );

}

Then you can create a route which points a get request to the below method.
This will request the file from S3 then download it to your browser.
The great thing about this method is you can restrict downloads to authenticated users.


use Illuminate\Support\Facades\Storage;

public function downloadFile($id)
{
	$file = FileLibrary::findOrFail($id);
	return Storage::disk('s3')->download($file->url);
}