Matthew Daly's Blog

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

29th November 2016 11:00 pm

Testing Laravel Middleware

It’s widely accepted that high-level integration tests alone do not make for a good test suite. Ideally each individual component of your application should have unit tests, which test that component in isolation. These unit tests are usually much quicker to run, making it easier to practice test-driven development. However, it can sometimes be hard to grasp how to test that one component on its own.

The other day I had an issue with several middleware classes for a Laravel application and I wanted to verify that they were working as expected. Sounds like a job for dedicated unit tests, but I hadn’t tested custom middleware in isolation before, and figuring out how to do so took a while.

Laravel middleware accepts an instance of Illuminate\Http\Request, itself based on the Symfony request object, as well as a closure for the action to take next. Depending on what the middleware does, it may return a redirect or simply amend the existing request or response. So in theory you can instantiate a request object, pass it to the middleware, and check the response. For middleware that does something simple, such as redirecting users based on certain conditions, this is fairly straightforward.

In this example we have a fairly useless piece of middleware that checks to see what the route is for a request and redirects it if it matches a certain pattern:

<?php
namespace App\Http\Middleware;
use Closure;
class RedirectFromAdminMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->is('admin*')) {
return redirect('/');
}
return $next($request);
}
}

While this example is of limited use, it wouldn’t take much work to develop it to redirect conditionally based on an account type, and it’s simple enough to demonstrate the principles involved. In these tests, we create instances of Illuminate\Http\Request and pass them to the middleware’s handle() method, along with an empty closure representing the response. If the middleware does not amend the request, we get the empty response from the closure. If it does amend the request, we get a redirect response.

<?php
use Illuminate\Http\Request;
class RedirectFromAdminMiddlewareTest extends TestCase
{
public function testRedirectMiddlewareCalledOnAdmin()
{
// Create request
$request = Request::create('http://example.com/admin', 'GET');
// Pass it to the middleware
$middleware = new App\Http\Middleware\RedirectFromAdminMiddleware();
$response = $middleware->handle($request, function () {});
$this->assertEquals($response->getStatusCode(), 302);
}
public function testRedirectMiddlewareNotCalledOnNonAdmin()
{
// Create request
$request = Request::create('http://example.com/pages', 'GET');
// Pass it to the middleware
$middleware = new App\Http\Middleware\RedirectFromAdminMiddleware();
$response = $middleware->handle($request, function () {});
$this->assertEquals($response, null);
}
}

For middleware that fetches the response and acts on it, things are a little more complex. For instance, this is the Etag middleware I use on many projects:

<?php
namespace App\Http\Middleware;
use Closure;
class ETagMiddleware {
/**
* Implement Etag support
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// Get response
$response = $next($request);
// If this was a GET request...
if ($request->isMethod('get')) {
// Generate Etag
$etag = md5($response->getContent());
$requestEtag = str_replace('"', '', $request->getETags());
// Check to see if Etag has changed
if($requestEtag && $requestEtag[0] == $etag) {
$response->setNotModified();
}
// Set Etag
$response->setEtag($etag);
}
// Send response
return $response;
}
}

This acts on the response object, so we need to pass that through as well. Fortunately, Mockery allows us to create a mock of our response object and set it up to handle only those methods we anticipate being called:

<?php
use Illuminate\Http\Request;
class ETagMiddlewareTest extends TestCase
{
/**
* Test new request not cached
*
* @return void
*/
public function testModified()
{
// Create mock response
$response = Mockery::mock('Illuminate\Http\Response')->shouldReceive('getContent')->once()->andReturn('blah')->getMock();
$response->shouldReceive('setEtag')->with(md5('blah'));
// Create request
$request = Request::create('http://example.com/admin', 'GET');
// Pass it to the middleware
$middleware = new App\Http\Middleware\ETagMiddleware();
$middlewareResponse = $middleware->handle($request, function () use ($response) {
return $response;
});
}
/**
* Test repeated request not modified
*
* @return void
*/
public function testNotModified()
{
// Create mock response
$response = Mockery::mock('Illuminate\Http\Response')->shouldReceive('getContent')->once()->andReturn('blah')->getMock();
$response->shouldReceive('setEtag')->with(md5('blah'));
$response->shouldReceive('setNotModified');
// Create request
$request = Request::create('http://example.com/admin', 'GET', [], [], [], [
'ETag' => md5('blah')
]);
// Pass it to the middleware
$middleware = new App\Http\Middleware\ETagMiddleware();
$middlewareResponse = $middleware->handle($request, function () use ($response) {
return $response;
});
}
public function teardown()
{
Mockery::close();
}
}

In the first example we mock out the getContent() and setEtag() methods of our response to make sure they get called, and then pass the request to the middleware, along with a closure that returns the response. In the second example, we also mock out setNotModified() to ensure that the correct status code of 304 is set, and add an ETag to our request. In this way we can easily test our middleware in isolation, rather than having to resort to building up our entire application just to test one small method.

Middleware is a convenient place to put functionality that’s needed for many routes, but you shouldn’t neglect testing it, and ideally you shouldn’t have to resort to writing a slow integration test to test it works as expected. By mocking out your dependencies, it’s generally not too hard to test it in isolation, resulting in faster and more robust test suites.

26th November 2016 9:40 pm

Easy Static Asset Versioning in PHP

It’s prudent to cache static assets such as images, Javascript and CSS to improve performance, but that raises the issue of changes not being reflected in your site due to visitor’s browsers retaining the cached versions. Many content management systems and frameworks already handle this for you (such as Laravel’s Elixir build system), but what if you have to work with a legacy application that doesn’t do this?

Fortunately there’s a quite easy solution in PHP. Using the filemtime() function described here, we can get a Unix timestamp for when a file was last altered. This is perfect to use to identify when a file last changed, because by appending a new query string to the file name when loading it, we can trick the browser into thinking it’s a new file when it’s not, as in this example for a CodeIgniter application:

<link rel="stylesheet" type="text/css" href="<?=$path?>?v=<?=filemtime($path)?>">

Obviously, this is a bit repetitive, so you may want to refactor this into some kind of template helper to make it easier to use, but the underlying principle applies to most programming languages. For instance, if you wanted to do so in a Handlebars template, you might want to create a helper something like this:

var fs = require('fs');
var Handlebars = require('handlebars');
Handlebars.registerHelper('version', function (path) {
return path + '?v=' + fs.statSync(path).mtime.getTime();
});

Where more robust solutions such as Elixir are already available, I’d advise making full use of them. However, this technique is a quick and easy way to implement versioning for static assets in existing projects.

13th November 2016 4:15 pm

Building a Phonegap App With Laravel and Angular - Part 4

In this instalment we’ll return to the back end. What we’ve done so far is typical of the kind of proof of concept we might do for a client early on, before going back and implementing the full set of features later on. Now we’ll go back and start to improve on that rather quick-and-dirty API by making sure we follow a few best practices.

For those of you who want to follow the Laravel Phonegap tutorials, I’ve created a dedicated category here for those tutorials. This category include RSS and Atom feeds, so if you only want to read those posts, you can do so. I’ve also done the same for the Django tutorials.

The Repository pattern

One of the issues we currently have with our API is that we’re passing our Eloquent models into our controllers. This may not seem like a huge issue, but it means that our controllers are tightly coupled to the Eloquent ORM, so if we wanted to switch to another ORM, or to a completely different database such as MongoDB, we’d have to amend our controllers. That’s not good.

However, using the Repository pattern we can first of all define an interface for our repository, and then create a repository class that implements that interface. That way we can interact with the repository class in our controllers, rather than using Eloquent models directly. Then, if we want to switch databases, we merely amend the repository class to change the implementation of those methods, without having to touch our controllers. Also, it makes it much easier to test our controllers in isolation, because we can easily mock our repository class using Mockery and hard-code the response, so our tests won’t touch the database and will therefore run more quickly. We won’t touch on that this time, but it’s a very significant advantage.

If you haven’t used interfaces before in PHP, they aren’t that hard. They merely specify what methods an object implementing that method must have and what arguments they must accept, but do not specify the details of the implementation. This makes it easy to determine if a class implements an interface correctly, because it will throw an exception if it doesn’t.

<?php
namespace AnimalFriend\Repositories\Interfaces;
interface PetRepositoryInterface {
public function all();
public function findOrFail($id);
public function create($input);
}

That’s all there is to it. We define it using the interface keyword and we specify the methods it must implement. Save this file at app/Repositories/Interfaces/PetRepositoryInterface.php.

Next, we implement the repository class:

<?php
namespace AnimalFriend\Repositories;
use AnimalFriend\Pet;
use AnimalFriend\Repositories\Interfaces\PetRepositoryInterface;
class EloquentPetRepository implements PetRepositoryInterface {
private $pet;
public function __construct(Pet $pet)
{
$this->pet = $pet;
}
public function all()
{
return $this->pet->all();
}
public function findOrFail($id)
{
return $this->pet->findOrFail($id);
}
public function create($input)
{
return $this->pet->create($input);
}
}

Save this to app/Repositories/EloquentPetRepository.php. Note how the methods closely mirror the underlying Eloquent methods, but they don’t need to - you could change the underlying implementation of each method, but the repository would still work in exactly the same way.

To make this work, we need to make a few changes elsewhere. In composer.json, we need to add the new Repositories folder to our classmap:

"autoload": {
"classmap": [
"database",
"app/Repositories"
],
"psr-4": {
"AnimalFriend\\": "app/"
}
},

And in app/Providers/AppServiceProvider.php, we need to bind our new files:

<?php
namespace AnimalFriend\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(
'AnimalFriend\Repositories\Interfaces\PetRepositoryInterface',
'AnimalFriend\Repositories\EloquentPetRepository'
);
}
}

With that done, we can now update app/Http/Controllers/PetController.php to use the repository:

<?php
namespace AnimalFriend\Http\Controllers;
use Illuminate\Http\Request;
use AnimalFriend\Http\Requests;
use AnimalFriend\Repositories\Interfaces\PetRepositoryInterface as Pet;
class PetController extends Controller
{
private $pet;
public function __construct(Pet $pet) {
$this->pet = $pet;
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
// Get all pets
$pets = $this->pet->all();
// Send response
return response()->json($pets, 200);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
// Get pet
$pet = $this->pet->findOrFail($id);
// Send response
return response()->json($pet, 200);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

Our repository is now injected automatically into the controller. To make this work we need to run the following command:

$ composer dump-autoload

Running our tests should confirm that everything is still working:

$ vendor/bin/phpunit
PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
............ 12 / 12 (100%)
Time: 897 ms, Memory: 18.00MB
OK (12 tests, 46 assertions)

Let’s do the same for the User model. First we implement our interface in app/Repositories/Interfaces/UserRepositoryInterface.php:

<?php
namespace AnimalFriend\Repositories\Interfaces;
interface UserRepositoryInterface {
public function all();
public function findOrFail($id);
public function create($input);
}

Next we create our repository at app/Repositories/EloquentUserRepository.php:

<?php
namespace AnimalFriend\Repositories;
use AnimalFriend\User;
use AnimalFriend\Repositories\Interfaces\UserRepositoryInterface;
use JWTAuth;
use Hash;
class EloquentUserRepository implements UserRepositoryInterface {
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function all()
{
return $this->user->all();
}
public function findOrFail($id)
{
return $this->user->findOrFail($id);
}
public function create($input)
{
$user = new $this->user;
$user->email = $input['email'];
$user->name = $input['name'];
$user->password = Hash::make($input['password']);
$user->save();
// Create token
return JWTAuth::fromUser($user);
}
}

Note how we’ve moved much of the logic for creating a user into the create() method, and we return the token, not the user model. This makes sense as right now we only ever want to get a token back when we create a user. Later that may change, but there’s nothing stopping us adding a new method to implement that behaviour alongside this.

Then we update app/Http/Controllers/UserController.php to use our repository:

<?php
namespace AnimalFriend\Http\Controllers;
use Illuminate\Http\Request;
use AnimalFriend\Http\Requests;
use AnimalFriend\Repositories\Interfaces\UserRepositoryInterface as User;
use JWTAuth;
use Hash;
class UserController extends Controller
{
private $user;
public function __construct(User $user) {
$this->user = $user;
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// Validate request
$valid = $this->validate($request, [
'email' => 'required|email|unique:users,email',
'name' => 'required|string',
'password' => 'required|confirmed'
]);
// Create token
$token = $this->user->create($request->only(
'email',
'name',
'password'
));
// Send response
return response()->json(['token' => $token], 201);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

And add a new binding in app/Providers/AppServiceProvider.php:

<?php
namespace AnimalFriend\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(
'AnimalFriend\Repositories\Interfaces\PetRepositoryInterface',
'AnimalFriend\Repositories\EloquentPetRepository'
);
$this->app->bind(
'AnimalFriend\Repositories\Interfaces\UserRepositoryInterface',
'AnimalFriend\Repositories\EloquentUserRepository'
);
}
}

Note that we bind the two sets separately - this allows Laravel to figure out which one maps to which.

Let’s run our tests to make sure nothing is broken:

$ vendor/bin/phpunit
PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
............ 12 / 12 (100%)
Time: 956 ms, Memory: 18.00MB
OK (12 tests, 46 assertions)

Now that we’ve got our repositories in place, we’re no longer tightly coupled to Eloquent, and have a more flexible implementation which is easier to test.

Separating our models from our JSON with Fractal

Another problem with our API is that our representation of our data is tightly coupled to our underlying implementation of our models. We therefore can’t change our models without potentially changing the data returned by the API. We need to separate our representation of our data from our actual model so that we can more easily specify the exact data we want to return, regardless of the underlying database structure.

Enter Fractal. From the website:

Fractal provides a presentation and transformation layer for complex data output, the like found in RESTful APIs, and works really well with JSON. Think of this as a view layer for your JSON/YAML/etc.

In other words, Fractal lets you specify the format your data will take in one place so that it’s easier to return that data in a desired format. We’ll use Fractal to specify how we want our API responses to be formatted.

Install Fractal with the following command:

$ composer require league/fractal

Then amend the classmap in composer.json:

"autoload": {
"classmap": [
"database",
"app/Repositories",
"app/Transformers"
],
"psr-4": {
"AnimalFriend\\": "app/"
}
},

Then create the folder app/Transformers and run composer dump-autoload. We’re now ready to write our first transformer. Save this as app/Transformers/PetTransformer.php:

<?php
namespace AnimalFriend\Transformers;
use AnimalFriend\Pet;
use League\Fractal;
class PetTransformer extends Fractal\TransformerAbstract
{
public function transform(Pet $pet)
{
return [
'id' => (int) $pet->id,
'name' => (string) $pet->name,
'type' => (string) $pet->type,
'available' => (bool) $pet->available,
'picture' => (string) $pet->picture
];
}
}

The transform method specifies how we want to represent our objects with our API. We can return only those attributes we want to expose, and amend the structure as we see fit. We could easily represemt relations in whatever manner we see fit, whereas before we needed to amend our queries to return the data in the right format, which would potentially be cumbersome.

Now let’s amend PetController.php to use this:

<?php
namespace AnimalFriend\Http\Controllers;
use Illuminate\Http\Request;
use AnimalFriend\Http\Requests;
use AnimalFriend\Repositories\Interfaces\PetRepositoryInterface as Pet;
use AnimalFriend\Transformers\PetTransformer;
use League\Fractal;
use League\Fractal\Manager;
class PetController extends Controller
{
private $pet, $fractal;
public function __construct(Pet $pet, Manager $fractal) {
$this->pet = $pet;
$this->fractal = $fractal;
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
// Get all pets
$pets = $this->pet->all();
// Format it
$resource = new Fractal\Resource\Collection($pets, new PetTransformer);
$data = $this->fractal->createData($resource)->toArray();
// Send response
return response()->json($data, 200);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
// Get pet
$pet = $this->pet->findOrFail($id);
// Format it
$resource = new Fractal\Resource\Item($pet, new PetTransformer);
$data = $this->fractal->createData($resource)->toArray();
// Send response
return response()->json($data, 200);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

Note that by default, Fractal places our data inside a dedicated data namespace. This is good because it leaves a place for us to put metadata such as pagination links, but it does mean our controller test has been broken. Let’s fix it:

<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
class PetControllerTest extends TestCase
{
use DatabaseMigrations;
/**
* Test fetching pets when unauthorised
*
* @return void
*/
public function testFetchingPetsWhenUnauthorised()
{
// Create a Pet
$pet = factory(AnimalFriend\Pet::class)->create([
'name' => 'Freddie',
'type' => 'Cat',
]);
$this->seeInDatabase('pets', ['type' => 'Cat']);
// Create request
$response = $this->call('GET', '/api/pets');
$this->assertResponseStatus(400);
}
/**
* Test fetching pets when authorised
*
* @return void
*/
public function testFetchingPets()
{
// Create a Pet
$pet = factory(AnimalFriend\Pet::class)->create([
'name' => 'Freddie',
'type' => 'Cat',
]);
$this->seeInDatabase('pets', ['type' => 'Cat']);
// Create a User
$user = factory(AnimalFriend\User::class)->create([
'name' => 'bobsmith',
'email' => 'bob@example.com',
]);
$this->seeInDatabase('users', ['email' => 'bob@example.com']);
// Create request
$token = JWTAuth::fromUser($user);
$headers = array(
'Authorization' => 'Bearer '.$token
);
// Send it
$this->json('GET', '/api/pets', [], $headers)
->seeJsonStructure([
'data' => [
'*' => [
'id',
'name',
'type',
'available',
'picture'
]
]
]);
$this->assertResponseStatus(200);
}
/**
* Test fetching pet when unauthorised
*
* @return void
*/
public function testFetchingPetWhenUnauthorised()
{
// Create a Pet
$pet = factory(AnimalFriend\Pet::class)->create([
'name' => 'Freddie',
'type' => 'Cat',
]);
$this->seeInDatabase('pets', ['type' => 'Cat']);
// Send request
$response = $this->call('GET', '/api/pets/'.$pet->id);
$this->assertResponseStatus(400);
}
/**
* Test fetching pet which does not exist
*
* @return void
*/
public function testFetchingPetDoesNotExist()
{
// Create a User
$user = factory(AnimalFriend\User::class)->create([
'name' => 'bobsmith',
'email' => 'bob@example.com',
]);
$this->seeInDatabase('users', ['email' => 'bob@example.com']);
// Create request
$token = JWTAuth::fromUser($user);
$headers = array(
'Authorization' => 'Bearer '.$token
);
// Send it
$this->json('GET', '/api/pets/1', [], $headers);
$this->assertResponseStatus(404);
}
/**
* Test fetching pet when authorised
*
* @return void
*/
public function testFetchingPet()
{
// Create a Pet
$pet = factory(AnimalFriend\Pet::class)->create([
'name' => 'Freddie',
'type' => 'Cat',
]);
$this->seeInDatabase('pets', ['type' => 'Cat']);
// Create a User
$user = factory(AnimalFriend\User::class)->create([
'name' => 'bobsmith',
'email' => 'bob@example.com',
]);
$this->seeInDatabase('users', ['email' => 'bob@example.com']);
// Create request
$token = JWTAuth::fromUser($user);
$headers = array(
'Authorization' => 'Bearer '.$token
);
// Send it
$this->json('GET', '/api/pets/'.$pet->id, [], $headers)
->seeJsonStructure([
'data' => [
'id',
'name',
'type',
'available',
'picture'
]
]);
$this->assertResponseStatus(200);
}
}

We’re also going to amend our test settings to use the array backend for the cache, as this does not require any external dependencies, but still allows us to tag our cache keys (I’ll cover that in a future instalment). Change the cache settings in phpunit.xml as follows:

<env name="CACHE_DRIVER" value="array"/>

Let’s run our tests to make sure everything’s fine:

$ vendor/bin/phpunit
PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
............ 12 / 12 (100%)
Time: 859 ms, Memory: 18.00MB
OK (12 tests, 44 assertions)

At present our User controller doesn’t actually return anything, and the auth only ever returns the token, so it’s not worth while adding a transformer now.

Wrapping up

That ends this lesson. We haven’t added any functionality, but we have improved the design of our API, and we’re now ready to develop it further. As usual, the backend repository has been tagged as lesson-4.

Next time we’ll start adding the additional functionality we need to our API.

24th October 2016 12:25 am

Creating An Azure Storage Adapter for Laravel

About a year ago I was working on my first non-trivial Laravel application. The client had, for their own reasons, wanted to use Microsoft’s Azure platform, particularly for its blob storage functionality, which is somewhat comparable to Amazon S3. Now, Laravel has the excellent Storage facade that allows consistent access to both local files and those stored on various file hosting services, which is built on top of Flysystem. Flysystem has an Azure driver, but the Laravel storage doesn’t include support for it, so at the time I resigned myself to using Flysystem directly, which wasn’t actually that bad, but not ideal.

A few days ago I stumbled across this section of the Laravel documentation, which had me kicking myself. It’s actually trivially easy to implement a custom filesystem for Laravel if it already has a Flysystem adapter, as demonstrated in their Dropbox implementation in the docs. Using this as a guide, I was able to produce the following service provider for using Azure as a storage backend very quickly:

<?php
namespace App\Providers;
use Storage;
use League\Flysystem\Filesystem;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Azure\AzureAdapter;
use WindowsAzure\Common\ServicesBuilder;
class AzureStorageServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
Storage::extend('azure', function($app, $config) {
$endpoint = sprintf(
'DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s',
$config['name'],
$config['key']
);
$blobRestProxy = ServicesBuilder::getInstance()->createBlobService($endpoint);
return new Filesystem(new AzureAdapter($blobRestProxy, $config['container']));
});
}
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
//
}
}

This should be saved as app/Providers/AzureStorageServiceProvider.php. You also need to add this to the list of service providers in config/app.php:

        App\Providers\AzureStorageServiceProvider::class,

And add this to config/filesystems.php:

'azure' => [
'driver' => 'azure',
'name' => env('STORAGE_NAME'),
'key' => env('STORAGE_KEY'),
'container' => env('STORAGE_CONTAINER'),
],

I like to also set my storage backend using environment variables in this file, as in this example:

'default' => env('STORAGE_BACKEND', 'local'),

That way we can easily set a different backend for testing, development and production so we don’t upload files when running PHPUnit. You can also keep your other config settings in your .env file, which is always a better idea than keeping it under version control. You also need to install the microsoft/windowsazure and league/flysystem-azure packages via Composer for this to work.

As I’ve since changed jobs it’s unlikely I’ll ever actually use this Azure integration in production - it’s not a service I’d choose of my own accord to use. However, since it’s so straightforward to implement an adapter like this I imagine I may be doing something similar - I’m currently working on a web app that uses MongoDB for some of its data and currently stores files locally, so it might make sense to create a GridFS integration along similar lines. It may also be useful for someone else, so feel free to use it if you wish.

16th October 2016 6:10 pm

Building a Phonegap App With Laravel and Angular - Part 3

Apologies for how long it’s taken for this post to appear. I’ve got a lot on my plate at present as I recently started a new job, so I haven’t been able to devote as much time to this series as I’d like.

In this instalment we’ll begin extending our app beyond the basic authentication we’ve already implemented. We’ll start by adding the means to sign up, before adding the list of pets.

Adding a signup method to our backend

We’ll create a controller for our users in the Laravel backend. First we’ll create our tests:

$ php artisan make:test UserControllerTest

We’ll create three tests. The first will check to see that an invalid request raises the correct status code (422). The second will check that a valid request returns the correct status code (201) and creates the user. The third will check that trying to create a duplicate user raises an error. Here they are - they should be saved in the new tests/UserControllerTest.php file:

<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
class UserControllerTest extends TestCase
{
/**
* Test creating a user - invalid
*
* @return void
*/
public function testPostingInvalidUser()
{
// Create a request
$data = array(
'name' => 'Bob Smith',
'email' => 'bob@example.com'
);
$this->json('POST', '/api/users', $data);
$this->assertResponseStatus(422);
}
/**
* Test creating a user
*
* @return void
*/
public function testPostingUser()
{
// Create a request
$data = array(
'name' => 'Bob Smith',
'email' => 'bob@example.com',
'password' => 'password',
'password_confirmation' => 'password'
);
$this->json('POST', '/api/users', $data);
$this->assertResponseStatus(201);
$this->seeInDatabase('users', ['email' => 'bob@example.com']);
// Check user exists
$saved = User::first();
$this->assertEquals($saved->email, 'bob@example.com');
$this->assertEquals($saved->name, 'Bob Smith');
}
/**
* Test creating a duplicate user
*
* @return void
*/
public function testPostingDuplicateUser()
{
// Create user
$user = factory(AnimalFriend\User::class)->create([
'name' => 'Bob Smith',
'email' => 'bob@example.com',
'password' => 'password'
]);
$this->seeInDatabase('users', ['email' => 'bob@example.com']);
// Create a request
$data = array(
'name' => 'Bob Smith',
'email' => 'bob@example.com',
'password' => 'password',
'password_confirmation' => 'password'
);
$this->json('POST', '/api/users', $data);
$this->assertResponseStatus(422);
}
}

Note the use of $this->json() to make the request. This method is ideal for testing a REST API.

Running our tests should confirm that they fail:

$ vendor/bin/phpunit
PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
........FFF. 12 / 12 (100%)
Time: 827 ms, Memory: 18.00MB
There were 3 failures:
1) UserControllerTest::testPostingInvalidUser
Expected status code 422, got 404.
Failed asserting that 404 matches expected 422.
/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:648
/home/matthew/Projects/mynewanimalfriend-backend/tests/UserControllerTest.php:21
2) UserControllerTest::testPostingUser
Expected status code 201, got 404.
Failed asserting that 404 matches expected 201.
/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:648
/home/matthew/Projects/mynewanimalfriend-backend/tests/UserControllerTest.php:39
3) UserControllerTest::testPostingDuplicateUser
Expected status code 422, got 404.
Failed asserting that 404 matches expected 422.
/home/matthew/Projects/mynewanimalfriend-backend/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php:648
/home/matthew/Projects/mynewanimalfriend-backend/tests/UserControllerTest.php:71
FAILURES!
Tests: 12, Assertions: 43, Failures: 3.

Next, we create our new controller:

$ php artisan make:controller UserController --resource

Let’s populate it:

<?php
namespace AnimalFriend\Http\Controllers;
use Illuminate\Http\Request;
use AnimalFriend\Http\Requests;
use AnimalFriend\User;
use JWTAuth;
use Hash;
class UserController extends Controller
{
private $user;
public function __construct(User $user) {
$this->user = $user;
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// Validate request
$valid = $this->validate($request, [
'email' => 'required|email|unique:users,email',
'name' => 'required|string',
'password' => 'required|confirmed',
]);
// Create user
$user = new $this->user;
$user->email = $request->input('email');
$user->name = $request->input('name');
$user->password = Hash::make($request->input('password'));
$user->save();
// Create token
$token = JWTAuth::fromUser($user);
// Send response
return response()->json(['token' => $token], 201);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

For now we’ll leave the other methods blank, but we’ll be using them later so we won’t get rid of them. At the top, note we load not only the User model, but also the JWTAuth and Hash facades. We use JWTAuth::fromUser() to return a JSON web token for the given user model.

In the store() method we first of all use Laravel’s validation support to validate our input. We specify that the user must provide a unique email address, a username, and a password, which must be confirmed. Note that we don’t need to specify an action if the request is invalid, as Laravel will do that for us. Also, note that the confirmed rule means that the password field must be accompanied by a matching password_confirmation field.

Next, we create the user. Note that we hash the password before storing it, which is a best practice (storing passwords in plain text is a REALLY bad idea!). Then we create the token for the new user and return it. From then on, the user can use that token to authenticate their requests.

We also need to add this route in routes/api.php:

Route::resource('users', 'UserController');

Let’s check the test passes:

$ vendor/bin/phpunit
PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
............ 12 / 12 (100%)
Time: 905 ms, Memory: 20.00MB
OK (12 tests, 46 assertions)

Building the registration in the app

With registration in place on the server side, we can move back to the app. We need to create another route for the registration form. Add this to test/routes.spec.js:

it('should map register route to register controller', function () {
inject(function ($route) {
expect($route.routes['/register'].controller).toBe('RegisterCtrl');
expect($route.routes['/register'].templateUrl).toEqual('templates/register.html');
});
});

Running the tests should confirm that this fails. So next you should add this to the route provider section of js/main.js:

.when('/register', {
templateUrl: 'templates/register.html',
controller: 'RegisterCtrl'
})

We also need to allow the register path to be accessed when not logged in:

.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function (event) {
if (!Auth.isLoggedIn()) {
if ($location.path() !== '/login' && $location.path() !== '/register') {
$location.path('/login');
}
}
});
}])

Our next step is to create a service representing the User endpoint. Here’s the test for it:

describe('User service', function () {
var mockBackend, User;
beforeEach(inject(function (_User_, _$httpBackend_) {
User = _User_;
mockBackend = _$httpBackend_;
}));
it('can create a new user', function () {
mockBackend.expectPOST('http://localhost:8000/api/users', '{"email":"bob@example.com","name":"bobsmith","password":"password","password_confirmation":"password"}').respond({token: 'mytoken'});
var user = new User({
email: 'bob@example.com',
name: 'bobsmith',
password: 'password',
password_confirmation: 'password'
});
user.$save(function (response) {
expect(response).toEqualData({token: 'mytoken'});
});
mockBackend.flush();
});
});

We’re only interested in using this model to create new users at this point, so this is the full scope of this test for now. Make sure the test fails, then we’re ready to create the new service in js/services.js:

.factory('User', function ($resource) {
return $resource('http://localhost:8000/api/users/:id', null, {
'update': { method: 'PATCH' }
});
})

Note that angular-resource does not support the PUT or PATCH methods by default, but as shown here it’s easy to implement it ourselves. That should be sufficient to make our test pass.

Next, we need to create the controller for registration. Here’s the test for it:

describe('Register Controller', function () {
var mockBackend, scope;
beforeEach(inject(function ($rootScope, $controller, _$httpBackend_) {
mockBackend = _$httpBackend_;
scope = $rootScope.$new();
$controller('RegisterCtrl', {
$scope: scope
});
}));
// Test controller scope is defined
it('should define the scope', function () {
expect(scope).toBeDefined();
});
// Test doRegister is defined
it('should define the register method', function () {
expect(scope.doRegister).toBeDefined();
});
// Test doRegister works
it('should allow the user to register', function () {
// Mock the backend
mockBackend.expectPOST('http://localhost:8000/api/users', '{"email":"user@example.com","name":"bobsmith","password":"password","password_confirmation":"password"}').respond({token: 123});
// Define login data
scope.credentials = {
email: 'user@example.com',
name: "bobsmith",
password: 'password',
password_confirmation: 'password'
};
// Submit the request
scope.doRegister();
// Flush the backend
mockBackend.flush();
// Check login complete
expect(localStorage.getItem('authHeader')).toEqual('Bearer 123');
});
});

Make sure the test fails before proceeding. Our RegisterCtrl is very similar to the login controller:

.controller('RegisterCtrl', function ($scope, $location, User, Auth) {
$scope.doRegister = function () {
var user = new User($scope.credentials);
user.$save(function (response) {
if (response.token) {
// Set up auth service
Auth.setUser(response.token);
// Redirect
$location.path('/');
}
}, function (err) {
alert('Unable to log in - please check your details are correct');
});
};
})

Check the tests pass,and we’re ready to move on to creating our HTML template. Save this as www/templates/register.html:

<md-content md-theme="default" layout-gt-sm="row" layout-padding>
<div>
<md-input-container class="md-block">
<label>Email</label>
<input ng-model="credentials.email" type="email">
</md-input-container>
<md-input-container class="md-block">
<label>Username</label>
<input ng-model="credentials.name" type="text">
</md-input-container>
<md-input-container class="md-block">
<label>Password</label>
<input ng-model="credentials.password" type="password">
</md-input-container>
<md-input-container class="md-block">
<label>Confirm Password</label>
<input ng-model="credentials.password_confirmation" type="password">
</md-input-container>
<md-button class="md-raised md-primary" ng-click="doRegister()">Submit</md-button>
<md-button class="md-raised md-primary" href="/login">Log in</md-button>
</div>
</md-content>

It’s very similar to our login template. Speaking of which, we need to add a link to this route there:

<md-content md-theme="default" layout-gt-sm="row" layout-padding>
<div>
<md-input-container class="md-block">
<label>Email</label>
<input ng-model="credentials.email" type="email" />
</md-input-container>
<md-input-container class="md-block">
<label>Password</label>
<input ng-model="credentials.password" type="password" />
</md-input-container>
<md-button class="md-raised md-primary" ng-click="doLogin()">Submit</md-button>
<md-button class="md-raised md-primary" href="register">Register</md-button>
</div>
</md-content>

With that done, you should now be able to run the Gulp server for the app with gulp and the Laravel backend with php artisan serve and create a new user account.

Adding pets to the home page

Our final task for this lesson is to display a list of pets on the home page. Later we’ll refine that functionality, but for now we’ll just get a list of all current pets and display them. First we need to write a test for our Pet service:

describe('Pet service', function () {
var mockBackend, Pet;
beforeEach(inject(function (_Pet_, _$httpBackend_) {
Pet = _Pet_;
mockBackend = _$httpBackend_;
}));
it('can fetch pets', function () {
mockBackend.expectGET('http://localhost:8000/api/pets').respond([{id:1,name:"Freddie",type:"Cat"}]);
expect(Pet).toBeDefined();
var pets = Pet.query();
mockBackend.flush();
expect(pets).toEqualData([{id: 1,name:"Freddie",type:"Cat"}]);
});
});

Once you know that fails, it’s time to implement the service:

.factory('Pet', function ($resource) {
return $resource('http://localhost:8000/api/pets/:id', null, {
'update': { method: 'PATCH' }
});
})

Next, we want to add the pets to the scope of the home controller. Amend the test for it as follows:

describe('Home Controller', function () {
var pets, scope;
beforeEach(inject(function ($rootScope, $controller, Pet) {
pets = Pet;
scope = $rootScope.$new();
$controller('HomeCtrl', {
$scope: scope,
pets: [{id:1},{id:2}]
});
}));
// Test controller scope is defined
it('should define the scope', function () {
expect(scope).toBeDefined();
});
// Test pets
it('should define the pets', function () {
expect(scope.pets).toEqualData([{id: 1}, {id: 2}]);
});
});

We check to see if the scope contains the pets variable. Once you have a failing test, amend the home controller as follows:

.controller('HomeCtrl', function ($scope, Pet, pets) {
$scope.pets = pets;
});

We could fetch the via AJAX inside the controller, but there’s a better way. We’ll create a loader for the pet data and have it resolve that before the page is displayed. To do so, first we need to add the loader service to js/services.js:

.factory('PetsLoader', ['Pet', '$q', function (Pet, $q) {
return function () {
var delay = $q.defer();
Pet.query(function (response) {
delay.resolve(response);
}, function () {
delay.reject('Unable to fetch pets');
});
return delay.promise;
};
}])

Then we set that route up to resolve it in js/main.js:

.when('/', {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl',
resolve: {
pets: ['PetsLoader', function (PetsLoader) {
return PetsLoader();
}]
}
})

Now, when we load that route, it will first of all fetch those pets and populate $scope.pets with them.

Now, we need to have some pets in the database, so we’ll make a seeder for it. Head back to the backend and run this command:

$ php artisan make:seeder PetTableSeeder

Then amend the file at database/seeds/PetTableSeeder.php as follows:

<?php
use Illuminate\Database\Seeder;
use Carbon\Carbon;
class PetTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// Add Pets
DB::table('pets')->insert([[
'name' => 'Freddie',
'type' => 'Cat',
'available' => 1,
'picture' => 'https://placekitten.com/300/300',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
], [
'name' => 'Sophie',
'type' => 'Cat',
'available' => 1,
'picture' => 'https://placekitten.com/300/300',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
]]);
}
}

And we need to update database/seeds/DatabaseSeeder.php to call our seeder:

<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->call(UserTableSeeder::class);
$this->call(PetTableSeeder::class);
}
}

For now we’ll use placeholder images, but at a later point our backend will be set up to use images uploaded from the admin. Then we need to refresh our migrations and apply the seeders:

$ php artisan migrate:refresh
$ php artisan db:seed

Now we just need to amend our home template to show the pets and we’re done for today:

<md-toolbar>
<div class="md-toolbar-tools">
<md-button aria-label="Log out" href="/logout">
Log out
</md-button>
</div>
</md-toolbar>
<div layout="column" flex="grow" layout-align="center stretch">
<md-card md-theme="default" ng-repeat="pet in pets">
<md-card-title>
<md-card-title-text>
<span class="md-headline">{{ pet.name }}</span>
<span class="md-subhead">{{ pet.type }}</span>
</md-card-title-text>
</md-card-title>
<md-card-content>
<img class="md-card-image md-media-lg" ng-src="{{ pet.picture }}"></img>
</md-card-content>
</md-card>
</div>

Now we can see our pets in the app.

Wrapping up

That’s enough for today - the fact that we can log in and out, register, and view the home page is sufficient as a proof of concept for a client. As usual, the results are on Github, tagged lesson-3.

Next time, we’ll concentrate exclusively on the back end. We’ll build upon what we already have using Laravel to create a full REST API for our app. In a later instalment, we’ll move on to build our admin interface for the staff, before switching back to finish off the app. I hope you’ll join me then.

Recent Posts

Testing Laravel Middleware

Easy Static Asset Versioning in PHP

Building a Phonegap App With Laravel and Angular - Part 4

Creating An Azure Storage Adapter for Laravel

Building a Phonegap App With Laravel and Angular - Part 3

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.

My GitHub repositories

  • Loading repositories...