Matthew Daly's Blog

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

9th January 2018 5:26 pm

Creating Laravel Helpers

Although helpers are an important part of Laravel, the documentation doesn’t really touch on creating them. Fortunately, doing so it fairly easy.

Here I’m building a helper for formatting dates for the HTML5 datetime-local form input. First we define the helper function in app\Helpers.php:

<?php
use Carbon\Carbon;
if (!function_exists('format_date')) {
function format_date(string $date)
{
return Carbon::parse($date, config('app.timezone'))->format('Y-m-d\TH:i:s');
}
}

Then we create a service provider to load them:

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class HelperServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
//
require_once app_path() . '/Helpers.php';
}
}

Finally,we register the service provider in config/app.php:

'providers' => [
...
App\Providers\HelperServiceProvider::class,
],

Of course, once you have this all set up for one helper, it’s easy to add more because they can all go in app/Helpers.php.

Creating your own helpers is a good way of refactoring unwanted logic out of your Blade templates or controllers and making it more reusable and maintainable, particularly for things like formatting dates or strings.

8th January 2018 2:00 pm

Getting the Type of An Unsupported Postgres Field in Laravel

Today I’ve been working on a generic, reusable Laravel admin interface, loosely inspired by the Django admin, that dynamically picks up the field types and generates an appropriate input field accordingly.

One problem I’ve run into is that getting a representation of a database table’s fields relies on doctrine/dbal, and its support for the more unusual PostgreSQL field types is spotty at best. I’ve been testing it out on a Laravel-based blogging engine, which has full-text search using the TSVECTOR field type, which isn’t supported, and it threw a nasty Unknown database type tsvector requested error.

Fortunately, it’s possible to register custom field type mappings easily enough. In this case we can safely treat a TSVECTOR field as a string` type anyway, so we can map it to the string type. We can do so in the boot method of a service provider:

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
// Register the TSVECTOR column
$conn = $this->app->make('Illuminate\Database\ConnectionInterface');
$conn->getDoctrineSchemaManager()
->getDatabasePlatform()
->registerDoctrineTypeMapping('tsvector', 'string');
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}

We register a Doctrine type mapping that maps the tsvector type to a string. Now Doctrine will just treat it as a string.

We can then retrieve the field types as follows:

$table = $this->model->getTable();
$fields = array_values(Schema::getColumnListing($table));
$fielddata = [];
foreach ($fields as $field){
if ($field != 'id' && $field != 'created_at' && $field != 'updated_at' && $field != 'deleted_at') {
try {
$fielddata[$field] = Schema::getColumnType($table, $field);
} catch (\Exception $e) {
$fielddata[$field] = 'unknown';
}
}
}

Note that we specifically don’t want to retrieve the ID or timestamps, so we exclude them - the user should never really have the need to update them manually. We fetch the table from the model and then call Schema::getColumnListing() to retrieve a list of fields for that table. Finally we call Schema::getColumnType() to actually get the type of each column.

Now, I suspect the performance of this admin interface is going to be inferior to a more specific one because it has to retrieve the fields all the time, but that’s not the point here - with a non-user facing admin interface, performance isn’t quite as much of an issue. For the same reason the admin doesn’t do any caching at all. It’s still useful under certain circumstances to be able to reverse-engineer the table structure and render an appropriate form dynamically.

8th January 2018 12:52 pm

Creating An Artisan Task to Set Up a User Account

When working with any Laravel application that implements authentication, you’ll need to set up a user account to be able to work with it. One way of doing that is to add a user in a seeder, but that’s only really suitable if every user is going to use the same details.

Instead, you may want to create an Artisan command to set up the user account. Here’s an example of a command that does that:

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Hash;
class CreateUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'create:user';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Creates a single user';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Get user model from config
$model = config('auth.providers.users.model');
// Let user know what this will do
$this->info('I\'ll ask you for the details I need to set up the user');
// Get username
$name = $this->ask('Please provide the username');
// Get email
$email = $this->ask('Please provide the email address');
// Get password
$password = $this->secret('Please provide the password');
// Create model
$user = new $model;
$user->name = $name;
$user->email = $email;
$user->password = Hash::make($password);
$user->save();
$this->info('User saved');
}
}

We fetch the user model from the config, before asking the user for the data we need. Then we insert it into the database and confirm it to the user.

Then we just need to register the command in App\Console\Kernel.php:

protected $commands = [
\App\Console\Commands\CreateUser::class,
];

And we can run our command with php artisan create:user.

7th January 2018 5:21 pm

Adding Comments to Models in Laravel

Laravel Comments is a package I recently released that allows you to add comments to any model in your application. Possible models you could use it to enable comments on might include:

  • Blog posts
  • Forum posts
  • Issues on an issue tracker

It’s loosely inspired by Django’s comments system.

Installation

Run this command to install it:

$ composer require matthewbdaly/laravel-comments

You will also need to run php artisan migrate to create the appropriate tables.

Making a model commentable

Add the following trait to a model to make it commentable:

Matthewbdaly\LaravelComments\Eloquent\Traits\Commentable

The comments table uses a polymorphic relation, so it should be possible to attach it to pretty much any model. The model should now have a comments relation, allowing you to get the comments for a model instance.

Displaying the comments

Obviously you can just render the comments in a view you can create yourself, but it’s usually going to be more convenient to use the existing view, even if just as a starting point, which includes the ability to submit new comments and flag existing ones. Include it in your views as follows:

@include('comments::comments', ['parent' => $post])

The argument passed to parent should be the model instance for which you want to display the comments form. Obviously, you can easily override this to use your own custom form instead.

The package also contains the following views:

  • comments::commentsubmitted
  • comments::flagsubmitted

These are basically just acknowledgement screens for when a comment has been submitted or flagged, and you’ll probably want to override them.

The package also has its own routes and controller included for submitting comments and flags.

Using the models directly

Of course there’s nothing stopping you creating your own routes and controllers for creating, viewing and flagging comments, and if, for instance, you wish to build a REST API that allows for adding comments to objects you can just use these models directly:

  • Matthewbdaly\LaravelComments\Eloquent\Models\Comment
  • Matthewbdaly\LaravelComments\Eloquent\Models\Comment\Flag

I recommend that you use my repositories, which are as follows:

  • Matthewbdaly\LaravelComments\Contracts\Repositories\Comment
  • Matthewbdaly\LaravelComments\Contracts\Repositories\Comment\Flag

These use matthewbdaly/laravel-repositories and so implement caching on the decorated repository, making it simple to ensure your models get cached appropriately. However, they aren’t compulsory.

Events

You can set up listeners for the following events:

  • Matthewbdaly\LaravelComments\Events\CommentReceived

Fired when a new comment is submitted. The package does not include any kind of validation of comments, so you can instead listen for this event and implement your own functionality to validate them (eg, check it with Akismet, check for links). That way you can easily customise how it handles potentially spammy comments for your own particular use case.

  • Matthewbdaly\LaravelComments\Events\CommentFlagged

This event indicates that a comment has been flagged for moderator attention. You can use this event to send whatever notification is most appropriate (eg, email, Slack, SMS).

7th January 2018 4:32 pm

More Tricks for Speeding Up Your Laravel Test Suite

When you first start doing test-driven development with Laravel, it can be quite hard to produce a test suite that runs quickly enough. The first time I used Laravel for a large project, I had a test suite that at one time, took over seven minutes to run, which was pretty awful considering that the ideal time for a test suite to take to run is no more than ten seconds.

Fortunately, with experience you can pick up some techniques which can quite drastically speed up your test suite. Here are some of the ones I’ve learned that can be useful.

Note that some of these are contradictory, and what works for one use case won’t necessarily work for another, so my advice is to try these and see what makes a difference for your use case.

Reduce the cost of hashing

Inside the createApplication() method of tests\CreatesApplication.php, place the following statement:

        Hash::setRounds(4);

This makes hashing passwords quicker and less demanding, and since you don’t care about the security of a password in a test, you’re not losing out in any way by doing so.

This, by itself, can massively reduce the time taken by your test suite - your mileage may vary, but I’ve personally seen it cut to a third of the previous time by using this. In fact, it’s recently been added to Laravel by default.

If you’re creating a lot of fixtures for tests, do so in a transaction

Sometimes, your application requires a lot of data to be added to the database just to be usable, and it’s quite common to use seeders for this purpose. However, it can take some time to insert a lot of data, especially if it has to be re-run for every test. If you do have to insert a lot of data before a test, you can cut down the time substantially by wrapping the seeder calls in a transaction:

<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use DB;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
DB::beginTransaction();
$this->call(GroupTableSeeder::class);
$this->call(UserTableSeeder::class);
$this->call(ProjectTableSeeder::class);
DB::commit();
Model::reguard();
}
}
`

I’ve personally seen this trick cut the insert time by over half, every single time the database is seeded. If you don’t have much data to insert, it may not help, but for large amounts of data it can make a big difference.

If a lot of tests need the same data, migrate and seed it first, then wrap the tests in transactions and roll them back afterwards

If multiple tests need to work with the same dataset, you should consider running the migrations and seeders before the first test, and then wrapping each test inside a transaction. That way the data will only be inserted once, and will be rolled back to that initial good state after each test.

protected static $migrated = false;
public function setUp()
{
parent::setUp();
DB::beginTransaction();
}
public function tearDown()
{
DB::rollback();
parent::tearDown();
}
public static function setUpBeforeClass()
{
if (!self::$migrated) {
Artisan::call('migrate:fresh');
Artisan::call('db:seed');
self::$migrated = true;
}
}

Using something like this instead of one of the existing testing traits may be a better fit under those circumstances. However, if your application uses transactions for some functionality this might cause problems.

Don’t create a full instance of the Laravel application unless you have to

Not every test requires that you instantiate the full Laravel application, and doing so slows your tests down. If you don’t absolutely need the full application instantiated in the test, consider having your test inherit from the below simple test case class instead:

<?php
namespace Tests;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use PHPUnit\Framework\TestCase as BaseTestCase;
class SimpleTestCase extends BaseTestCase
{
use MockeryPHPUnitIntegration;
}

For properly isolated unit tests, using this base class instead can have a noticeable effect on performance.

If you can, use an in-memory SQLite database for testing

This isn’t an option if you’re relying on the features of another database, but if it is, this is usually the fastest way to go. Configure it as follows in phpunit.xml:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>

Use the new Refresh Database trait

   use RefreshDatabase;

This testing trait is generally more efficient than migrating down and up, because it empties the database afterwards rather than stepping through the changes of each migration. If you have a non-trivial number of migrations, it will almost certainly be quicker than migrating down, then back up for the next test.

Mock what you can’t control

You should never, ever be making calls to external APIs in your test suite, because you can’t control whether those external API’s work - if a third-party API goes down, you may get a failed test run even if your application is working perfectly, not to mention it will add the time taken to send the request and receive a response to the test time. Instead, mock the calls to the third-party API.

For large applications, consider moving parts into a separate package

If you have a particularly large application, it’s worth considering moving parts of it out into standalone packages and requiring them using Composer’s support for private Git repositories. That way, those packages can have their own test suites, and the main application’s test suite can cover the remaining functionality.

For instance, it’s fairly straightforward to pull out your models and migrations and put them in a separate package, and the tests for them can go with them to that package.

You should also consider whether parts of your application would be useful as standalone packages, and if so pull them out along with their tests. That way, not only are you making your test suite quicker, but you’re also saving yourself work by creating a reusable solution for a problem you might encounter again in the future.

Turn off XDebug

XDebug has a horrendous effect on the performance of the test suite. Turn it off unless you need it to generate test coverage. Better yet, set up continuous integration and have that generate the coverage for you.

Summary

When you first start using Laravel, it can be hard to keep your test suite lean, and the longer a test suite takes to run, the less likely it is to actually get run regularly. To practice TDD properly, your test suite should not take long enough that your mind starts to wander, and ten seconds is a good target to aim for in this regard - you need to be able to run it several times a minute without problem. Obviously things like having a faster computer or an SSD will help, but there’s a lot you can do to make your test suite more efficient, even when running on a quite basic machine.

Recent Posts

Decorating Service Classes

Simplify Your Tests With Anonymous Classes

Adding React to a Legacy Project

Do You Still Need Jquery?

An Approach to Writing Golden Master Tests for PHP Web Applications

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.