Matthew Daly's Blog

I'm a web developer in Norfolk. This is my blog...

6th November 2017 12:00 pm

Catching Debug Statements in PHP

It’s unfortunately quite easy to neglect to remove debugging statements in PHP code. I’ve done so many times myself, and it’s not unknown for these to wind up in production. After I saw it happen again recently, I decided to look around for a way to prevent it happening.

As mentioned earlier, I generally use PHP CodeSniffer to enforce a coding standard on my projects, and it’s easy to set it up and run it. With a little work, you can also use it to catch these unwanted debugging statements before they get committed.

First, you need to make sure squizlabs/php_codesniffer is included in your project’s development dependencies in composer.json. Then, create a phpcs.xml file that looks something like this:

<?xml version="1.0"?>
<ruleset name="PHP_CodeSniffer">
<description>Coding standard.</description>
<file>src</file>
<arg value="np"/>
<rule ref="PSR2"/>
<rule ref="Squiz.Commenting.FunctionComment" />
<rule ref="Squiz.Commenting.FunctionCommentThrowTag" />
<rule ref="Squiz.Commenting.ClassComment" />
<rule ref="Squiz.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array" value="eval=>NULL,dd=>NULL,die=>NULL,var_dump=>NULL,sizeof=>count,delete=>unset,print=>echo,create_function=>NULL"/>
</properties>
</rule>
</ruleset>

The key is the rule Squiz.PHP.ForbiddenFunctions. This allows us to define a list of functions that are forbidden in our project. Typically this will be things like die(), eval(), var_dump() and dd().

Now, this ruleset will catch the unwanted functions (as well as enforcing PSR2 and certain rules about comments), but we can’t guarantee that we’ll always remember to run it. We could run CodeSniffer in continuous integration (and this is a good idea anyway), but that doesn’t stop us from committing code with those forbidden functions. We need a way to ensure that CodeSniffer runs on every commit and doesn’t allow it to go ahead if it fails. To do that we can use a pre-commit hook. Save the following in your repository as .git/hooks/pre-commit:

vendor/bin/phpcs

Then run the following command to make it executable:

$ chmod +x .git/hooks/pre-commit

A pre-commit hook is run before every commit, and if it returns false, will not allow the commit to go ahead. That means that if CodeSniffer fails for any reason, we will have to go back and fix the problem before we can commit. If for some reason you do need to bypass this check, you can still do so by using the --no-verify flag with git commit.

The advantage of this method is that it’s not dependent on any one IDE or editor, so it’s widely applicable. However, if you’re doing this sort of thing with Git hooks, you may want to look at some of the solutions for managing hooks, since .git/hooks is outside the actual Git repository.

29th October 2017 7:31 pm

An Azure Filesystem Integration for Laravel

My earlier post about integrating Laravel and Azure storage seems to have become something of a go-to resource on this subject (I suspect this is because very few developers actually use Laravel and Azure together). Unfortunately it hasn’t really aged terribly well - changes to the namespace and to Guzzle mean that it needs some work to integrate it.

I’ve therefore created a package for it. That way, it’s easier to keep it up to date as if someone finds and fixes an issue with it, they can submit their changes back.

20th October 2017 10:55 pm

Using Phpiredis With Laravel

Laravel has support out of the box for using Redis. However, by default it uses a Redis client written in PHP, which will always be a little slower than one written in C. If you’re making heavy use of Redis, it may be worth using the phpiredis extension to squeeze a little more performance out of it.

I’m using PHP 7.0 on Ubuntu Zesty and I installed the dependencies with the following command:

$ sudo apt-get install libhiredis-dev php-redis php7.0-dev

Then I installed phpiredis as follows:

git clone https://github.com/nrk/phpiredis.git && \
cd phpiredis && \
phpize && \
./configure --enable-phpiredis && \
make && \
sudo make install

Finally, I configured Redis to use phpiredis in the redis section of config/database.php for a Laravel app:

'redis' => [
'cluster' => false,
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
'options' => [
'connections' => [
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP streams
'unix' => 'Predis\Connection\PhpiredisSocketConnection', // ext-socket
],
]
],
],

Now, I’m going to be honest - in a casual comparison I couldn’t see much difference in terms of speed. I would probably only bother with setting this up on a site where high Redis performance was absolutely necessary. If you just want a quicker cache response it might make more sense to put Varnish in front of the site instead. However, in cases where Redis gets used heavily, it’s probably worth doing.

3rd October 2017 11:56 pm

Simple Fuzzy Search With Laravel and Postgresql

When implementing fuzzy search, many developers reach straight for specialised tools like Elasticsearch. However, for simple implementations, this is often overkill. PostgreSQL, my relational database of choice, can natively handle fuzzy search quite easily if you know how. Here’s how you might use this with Laravel.

Suppose we have the following migration to create a locations table, storing towns, cities and villages:

<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateLocations extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// Create locations table
Schema::create('locations', function (Blueprint $table) {
$table->increments('id')->unsigned();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// Drop locations table
Schema::drop('locations');
}
}

The key to this implementation of fuzzy search is trigrams. A trigram is a group of three consecutive characters taken from a string. Using the pg_trgm module, which comes with PostgreSQL, we can break a string into as many trigrams as possible, and then return the strings with the most matching trigrams.

We can ensure that pg_trgm is set up on the database by creating a migration:

<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddTrgmExtension extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::statement('CREATE EXTENSION IF NOT EXISTS pg_trgm');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
DB::statement('DROP EXTENSION IF EXISTS pg_trgm');
}
}

Make sure you run the migration as well. Once that is done, we can make a raw fuzzy query against the name field as follows:

SELECT * FROM locations WHERE 'burgh' % name;

Translating that to work with the Eloquent ORM, we can perform fuzzy queries against the name field as follows:

$location = Location::whereRaw("'burgh' % name")->get();

This query might match both Aldeburgh and Edinburgh. It’s also able to handle slight misspellings, as in this example:

$location = Location::whereRaw("'hendrad' % name")->get();

This query will match East Hendred or West Hendred successfully. As you can see, we can match strings at any point in the name string, and handle slight mis-spellings without any problems.

In practice, rather than using whereRaw() every time, you’ll probably want to create a local scope that accepts the name you want to match against. You’ll also want to use query parameters to prevent SQL injection:

$location = Location::whereRaw("? % name", [$name])->get();

Improving performance with an index

The performance of these queries isn’t that great out of the box. We can improve them by creating an index:

<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddTrgmExtension extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::statement('CREATE EXTENSION IF NOT EXISTS pg_trgm');
DB::statement('CREATE INDEX locations_name_trigram ON locations USING gist(name gist_trgm_ops);');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
DB::statement('DROP INDEX IF EXISTS locations_name_trigram');
DB::statement('DROP EXTENSION IF EXISTS pg_trgm');
}
}

Adding an index should produce a noticeable improvement in the response time.

Final thoughts

PostgreSQL’s pg_trgm module is a fairly straightforward way of implementing fuzzy search. It’s not much more involved than a LIKE or ILIKE clause in your query, and for many use cases, it’s more than sufficient. If you don’t have a huge number of records, it’s probably a more appropriate choice than something like Elasticsearch, and has the advantage of a simpler stack. However, if you have a larger dataset, you may be better off with a dedicated search solution. As always, if you’re unsure it’s a good idea to try both and see what works best for that particular use case.

25th September 2017 10:18 pm

A Generic PHP SMS Library

This weekend I published sms-client, a generic PHP library for sending SMS notifications. It’s intended to offer a consistent interface when sending SMS notifications by using swappable drivers. That way, if your SMS service provider suddenly goes out of business or bumps up their prices, it’s easy to switch to a new one.

Out of the box it comes with drivers for the following services:

  • Nexmo
  • ClockworkSMS

In addition, it provides the following test drivers:

  • Null
  • Log
  • RequestBin

Here’s an example of how you might use it with the ClockworkSMS driver:

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Psr7\Response;
use Matthewbdaly\SMS\Drivers\Clockwork;
use Matthewbdaly\SMS\Client;
$guzzle = new GuzzleClient;
$resp = new Response;
$driver = new Clockwork($guzzle, $resp, [
'api_key' => 'MY_CLOCKWORK_API_KEY',
]);
$client = new Client($driver);
$msg = [
'to' => '+44 01234 567890',
'content' => 'Just testing',
];
$client->send($msg);

If you want to roll your own driver for it, it should be easy - just create a class that implements the Matthewbdaly\SMS\Contracts\Driver interface. Most of the existing drivers work using Guzzle to send HTTP requests to an API, but you don’t necessarily have to do that - for instance, you could create a driver for a mail-to-SMS gateway by using Swiftmailer or the PHP mail class. If you create a driver for it, please feel free to submit a pull request so I can add it to the repository.

For Laravel or Lumen users, there’s an integration package that should make it easier to use. For users of other frameworks, it should still be fairly straightforward to integrate.

Recent Posts

Logging to the ELK Stack With Laravel

Full-text Search With Mariadb

Building a Letter Classifier in PHP With Tesseract OCR and PHP ML

Console Applications With the Symfony Console Component

Rendering Different Views for Mobile and Desktop Clients in Laravel

About me

I'm a web and mobile app developer based in Norfolk. My skillset includes Python, PHP and Javascript, and I have extensive experience working with CodeIgniter, Laravel, Django, Phonegap and Angular.js.