Quantcast
Channel: Matthew Daly
Viewing all 158 articles
Browse latest View live

Building a Phonegap app with Laravel and Angular - Part 2

$
0
0

In this lesson, the initial scope of the app will be extremely simple. We will implement functionality that:

  • Allows users to log in and out
  • Displays the home page

That’s fairly simple, and easily achievable within a fairly short timeframe. We’ll also write automated tests for our app. By the end of this lesson, we’ll have built a first pass for our app using Angular.js.

NOTE: As at time of writing, Angular 2 has just come out. I’m using Angular 1 here, and the two are not compatible, so make sure you’re using Angular 1.

Creating our app

Start by creating a new folder, separate from the backend, for the app. Then, in there, run the following command:

$ npm init -y

Then let’s install our dependencies:

$ npm install --save-dev gulp karma karma-browserify karma-phantomjs-launcher browserify angular angular-route angular-mocks angular-animate angular-messages angular-sanitize angular-material angular-resource vinyl-buffer vinyl-source-stream gulp-sass karma-coverage karma-jasmine jasmine-core gulp-webserver

We’re going to use Angular Material for our user interface as it includes support out of the box for swiping left and right. You’ll notice it mentioned as one of the dependencies above.

We’ll also use Karma for running our tests. Save the following as karma.conf.js:

module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['browserify', 'jasmine'],
files: [
'node_modules/angular/angular.min.js',
'node_modules/angular-mocks/angular-mocks.js',
'node_modules/angular-material/angular-material-mocks.js',
'js/*.js',
'test/*.js'
],
exclude: [
],
preprocessors: {
'js/*.js': ['browserify', 'coverage'],
'tests/js': ['browserify']
},
browserify: {
debug: true
},
reporters: ['progress', 'coverage'],
port: 9876,
colors: true,
logLevel: config.LOG_DEBUG,
autoWatch: true,
browsers: ['PhantomJS'],
singleRun: true,
coverageReporter: {
dir : 'coverage/',
reporters: [
{ type: 'html', subdir: 'report-html' },
{ type: 'cobertura', subdir: 'report-cobertura' }
]
}
});
};

This is our Karma configuration. Karma can run the same test in multiple browsers. Here we’re going to use PhantomJS, but it’s trivial to amend the browsers section to add more. You just need to make sure you install the appropriate launchers for those browsers.

We’ll use Gulp to build the app. Here’s the gulpfile.js:

var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var browserify = require('browserify');
var sass = require('gulp-sass');
var server = require('gulp-webserver');
var paths = {
scripts: ['js/*.js'],
styles: ['sass/*.scss']
};
gulp.task('sass', function() {
gulp.src('sass/style.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('www/css'));
});;
gulp.task('js', function () {
return browserify({ entries: ['js/main.js'], debug: true })
.bundle()
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(gulp.dest('www/js/'));
});
gulp.task('server', function () {
gulp.src('www/')
.pipe(server({
livereload: true,
open: true,
port: 5000
}));
});
gulp.task('watch', function () {
gulp.watch(paths.scripts, ['js']);
gulp.watch(paths.styles, ['sass']);
});
gulp.task('default', ['sass','js','server', 'watch']);

Note that we’re going to be using Browserify to handle our dependencies. If you haven’t used it before, it lets you use the require() syntax from Node.js to include other JavaScript files, including ones available via NPM such as jQuery or Angular, allowing you to compile them all into a single file.

We should be able to test and run the app using NPM, so add these scripts to package.json:

"scripts": {
"test": "karma start",
"run": "gulp"
},

We also need an HTML file. Save this as www/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
<title>My New Animal Friend</title>
<link href="/css/style.css" rel="stylesheet" type="text/css">
</head>
<body>
<div>
<div ng-app="mynewanimalfriend" ng-cloak>
<div ng-view></div>
</div>
</div>
</body>
<script language="javascript" type="text/javascript" src="/js/bundle.js"></script>
</html>

Note the use of the Angular directives. ng-app denotes the name of the app namespace, ng-cloak hides the application until it’s fully loaded, and ng-view denotes the area containing our content.

You should also create the files js/main.js, sass/style.scss, and the test folder.

Creating our first routes

Our first task is to create the routes we need. Our default route will be /, representing the home page. However, users will need to be logged in to see this. Otherwise, they should be redirected to the login route, which will be /login, appropriately enough. We’ll also have a /logout route, which should be self-explanatory.

Before we implement these routes, we need to write a test for them. We’ll start with our login route, and we’ll test that for this route, the controller will be LoginCtrl and the template will be templates/login.html. The significance of these will become apparent later. Save this as test/routes.spec.js:

'use strict';
describe('Routes', function () {
beforeEach(angular.mock.module('mynewanimalfriend'));
it('should map login route to login controller', function () {
inject(function ($route) {
expect($route.routes['/login'].controller).toBe('LoginCtrl');
expect($route.routes['/login'].templateUrl).toEqual('templates/login.html');
});
});
});

Note the beforeEach() hook. This is used to set up the application.

We can run this test with npm test as that calls Karma directly. Note that we’re using Jasmine to write our tests.

$ npm test
> mynewanimalfriend-app@1.0.0 test /home/matthew/Projects/mynewanimalfriend-app
> karma start
12 09 2016 22:22:34.168:DEBUG [config]: autoWatch set to false, because of singleRun
12 09 2016 22:22:34.172:DEBUG [plugin]: Loading karma-* from /home/matthew/Projects/mynewanimalfriend-app/node_modules
12 09 2016 22:22:34.176:DEBUG [plugin]: Loading plugin /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-browserify.
12 09 2016 22:22:34.314:DEBUG [plugin]: Loading plugin /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-coverage.
12 09 2016 22:22:34.484:DEBUG [plugin]: Loading plugin /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine.
12 09 2016 22:22:34.485:DEBUG [plugin]: Loading plugin /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-phantomjs-launcher.
12 09 2016 22:22:34.535:DEBUG [framework.browserify]: created browserify bundle: /tmp/f8c46bd8d72c5b8578e64552192273be.browserify
12 09 2016 22:22:34.553:DEBUG [framework.browserify]: add bundle to config.files at position 3
12 09 2016 22:22:34.559:DEBUG [web-server]: Instantiating middleware
12 09 2016 22:22:34.569:DEBUG [reporter]: Trying to load reporter: coverage
12 09 2016 22:22:34.570:DEBUG [reporter]: Trying to load color-version of reporter: coverage (coverage_color)
12 09 2016 22:22:34.571:DEBUG [reporter]: Couldn't load color-version.
12 09 2016 22:22:34.596:DEBUG [framework.browserify]: updating js/main.js in bundle
12 09 2016 22:22:34.597:DEBUG [framework.browserify]: building bundle
12 09 2016 22:22:35.302:DEBUG [framework.browserify]: bundling
12 09 2016 22:22:35.328:DEBUG [preprocessor.coverage]: Processing "/home/matthew/Projects/mynewanimalfriend-app/js/main.js".
12 09 2016 22:22:35.345:INFO [framework.browserify]: bundle built
12 09 2016 22:22:35.352:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/
12 09 2016 22:22:35.352:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
12 09 2016 22:22:35.361:INFO [launcher]: Starting browser PhantomJS
12 09 2016 22:22:35.361:DEBUG [temp-dir]: Creating temp dir at /tmp/karma-17657666
12 09 2016 22:22:35.364:DEBUG [launcher]: /home/matthew/Projects/mynewanimalfriend-app/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs /tmp/karma-17657666/capture.js
12 09 2016 22:22:35.466:DEBUG [web-server]: serving: /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma/static/client.html
12 09 2016 22:22:35.478:DEBUG [web-server]: serving: /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma/static/karma.js
12 09 2016 22:22:35.541:DEBUG [karma]: A browser has connected on socket /#dQYjOD4F_HJwPXiYAAAA
12 09 2016 22:22:35.564:DEBUG [web-server]: upgrade /socket.io/?EIO=3&transport=websocket&sid=dQYjOD4F_HJwPXiYAAAA
12 09 2016 22:22:35.629:INFO [PhantomJS 2.1.1 (Linux 0.0.0)]: Connected on socket /#dQYjOD4F_HJwPXiYAAAA with id 17657666
12 09 2016 22:22:35.630:DEBUG [launcher]: PhantomJS (id 17657666) captured in 0.277 secs
12 09 2016 22:22:35.642:DEBUG [phantomjs.launcher]:
12 09 2016 22:22:35.643:DEBUG [middleware:karma]: custom files null null
12 09 2016 22:22:35.644:DEBUG [middleware:karma]: Serving static request /context.html
12 09 2016 22:22:35.646:DEBUG [web-server]: serving: /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma/static/context.html
12 09 2016 22:22:35.650:DEBUG [middleware:source-files]: Requesting /base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?b1682a1eb50e00abf147fc1fb28e31006d499aae /
12 09 2016 22:22:35.650:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/jasmine-core/lib/jasmine-core/jasmine.js
12 09 2016 22:22:35.652:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/jasmine-core/lib/jasmine-core/jasmine.js
12 09 2016 22:22:35.654:DEBUG [middleware:source-files]: Requesting /base/node_modules/angular-material/angular-material-mocks.js?9f31553e4bbbad4d6b52638351e3a274352311c2 /
12 09 2016 22:22:35.654:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular-material/angular-material-mocks.js
12 09 2016 22:22:35.654:DEBUG [middleware:source-files]: Requesting /base/node_modules/karma-jasmine/lib/boot.js?945a38bf4e45ad2770eb94868231905a04a0bd3e /
12 09 2016 22:22:35.655:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine/lib/boot.js
12 09 2016 22:22:35.655:DEBUG [middleware:source-files]: Requesting /base/node_modules/karma-jasmine/lib/adapter.js?7975a273517f1eb29d7bd018790fd4c7b9a485d5 /
12 09 2016 22:22:35.655:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine/lib/adapter.js
12 09 2016 22:22:35.656:DEBUG [middleware:source-files]: Requesting /base/node_modules/angular/angular.min.js?78069f9f3a9ca9652cb04c13ccb0670d747666b8 /
12 09 2016 22:22:35.656:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular/angular.min.js
12 09 2016 22:22:35.656:DEBUG [middleware:source-files]: Requesting /base/node_modules/angular-mocks/angular-mocks.js?cc56136dc551d94abe8195cf8475eb27a3aa3c4b /
12 09 2016 22:22:35.657:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular-mocks/angular-mocks.js
12 09 2016 22:22:35.657:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular-material/angular-material-mocks.js
12 09 2016 22:22:35.658:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine/lib/boot.js
12 09 2016 22:22:35.658:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine/lib/adapter.js
12 09 2016 22:22:35.659:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular/angular.min.js
12 09 2016 22:22:35.659:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular-mocks/angular-mocks.js
12 09 2016 22:22:35.660:DEBUG [web-server]: serving: /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma/static/context.js
12 09 2016 22:22:35.661:DEBUG [middleware:source-files]: Requesting /absolute/tmp/f8c46bd8d72c5b8578e64552192273be.browserify?8ffde4eef27d38e92cc62da4e8dd0ffa5a3a4a4c /
12 09 2016 22:22:35.661:DEBUG [middleware:source-files]: Fetching /tmp/f8c46bd8d72c5b8578e64552192273be.browserify
12 09 2016 22:22:35.662:DEBUG [middleware:source-files]: Requesting /base/js/main.js?41c850cecc07c24d7cd0421e914bd2420671e573 /
12 09 2016 22:22:35.662:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/js/main.js
12 09 2016 22:22:35.662:DEBUG [middleware:source-files]: Requesting /base/test/routes.spec.js?92b15bb7c24bc6ead636994fb1c737b91727d887 /
12 09 2016 22:22:35.662:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/test/routes.spec.js
12 09 2016 22:22:35.663:DEBUG [web-server]: serving (cached): /tmp/f8c46bd8d72c5b8578e64552192273be.browserify
12 09 2016 22:22:35.664:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/js/main.js
12 09 2016 22:22:35.664:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/test/routes.spec.js
PhantomJS 2.1.1 (Linux 0.0.0) Routes should map login route to login controller FAILED
Error: [$injector:modulerr] http://errors.angularjs.org/1.5.8/$injector/modulerr?p0=mynewanimalfriend&p1=%5B%24injector%3Anomod%5D%20http%3A%2F%2Ferrors.angularjs.org%2F1.5.8%2F%24injector%2Fnomod%3Fp0%3Dmynewanimalfriend%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular%2Fangular.min.js%3F78069f9f3a9ca9652cb04c13ccb0670d747666b8%3A25%3A111%0Ab%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular%2Fangular.min.js%3F78069f9f3a9ca9652cb04c13ccb0670d747666b8%3A24%3A143%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular%2Fangular.min.js%3F78069f9f3a9ca9652cb04c13ccb0670d747666b8%3A24%3A489%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular%2Fangular.min.js%3F78069f9f3a9ca9652cb04c13ccb0670d747666b8%3A39%3A473%0Aq%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular%2Fangular.min.js%3F78069f9f3a9ca9652cb04c13ccb0670d747666b8%3A7%3A359%0Ag%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular%2Fangular.min.js%3F78069f9f3a9ca9652cb04c13ccb0670d747666b8%3A39%3A320%0Acb%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular%2Fangular.min.js%3F78069f9f3a9ca9652cb04c13ccb0670d747666b8%3A43%3A337%0AworkFn%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular-mocks%2Fangular-mocks.js%3Fcc56136dc551d94abe8195cf8475eb27a3aa3c4b%3A3074%3A60%0Ainject%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fangular-mocks%2Fangular-mocks.js%3Fcc56136dc551d94abe8195cf8475eb27a3aa3c4b%3A3054%3A46%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Ftest%2Froutes.spec.js%3F92b15bb7c24bc6ead636994fb1c737b91727d887%3A5%3A11%0AattemptSync%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1942%3A28%0Arun%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1930%3A20%0Aexecute%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1915%3A13%0AqueueRunnerFactory%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A710%3A42%0Aexecute%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A367%3A28%0Afn%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A2568%3A44%0AattemptAsync%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1972%3A28%0Arun%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1927%3A21%0Aexecute%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1915%3A13%0AqueueRunnerFactory%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A710%3A42%0Afn%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A2553%3A31%0AattemptAsync%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1972%3A28%0Arun%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1927%3A21%0Aexecute%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A1915%3A13%0AqueueRunnerFactory%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A710%3A42%0Aexecute%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A2415%3A25%0Aexecute%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fjasmine-core%2Flib%2Fjasmine-core%2Fjasmine.js%3Fb1682a1eb50e00abf147fc1fb28e31006d499aae%3A772%3A24%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Fnode_modules%2Fkarma-jasmine%2Flib%2Fadapter.js%3F7975a273517f1eb29d7bd018790fd4c7b9a485d5%3A320%3A23%0Aloaded%40http%3A%2F%2Flocalhost%3A9876%2Fcontext.js%3A151%3A17%0Aglobal%20code%40http%3A%2F%2Flocalhost%3A9876%2Fcontext.html%3A50%3A28 in node_modules/angular/angular.min.js (line 40)
node_modules/angular/angular.min.js:40:260
q@node_modules/angular/angular.min.js:7:359
g@node_modules/angular/angular.min.js:39:320
cb@node_modules/angular/angular.min.js:43:337
workFn@node_modules/angular-mocks/angular-mocks.js:3074:60
inject@node_modules/angular-mocks/angular-mocks.js:3054:46
test/routes.spec.js:5:11
loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.044 secs / 0.006 secs)
12 09 2016 22:22:35.778:DEBUG [karma]: Run complete, exiting.
12 09 2016 22:22:35.778:DEBUG [launcher]: Disconnecting all browsers
12 09 2016 22:22:35.778:DEBUG [framework.browserify]: cleaning up
12 09 2016 22:22:35.782:DEBUG [coverage]: Writing coverage to /home/matthew/Projects/mynewanimalfriend-app/coverage/report-html
12 09 2016 22:22:35.876:DEBUG [coverage]: Writing coverage to /home/matthew/Projects/mynewanimalfriend-app/coverage/report-cobertura
12 09 2016 22:22:35.880:DEBUG [launcher]: Process PhantomJS exited with code 0
12 09 2016 22:22:35.881:DEBUG [temp-dir]: Cleaning temp dir /tmp/karma-17657666
12 09 2016 22:22:35.884:DEBUG [launcher]: Finished all browsers
npm ERR! Test failed. See above for more details.

Now that we have a failing test, we can set about making it pass. Save this at js/main.js:

'use strict';
require('angular');
require('angular-route');
require('angular-animate');
require('angular-material');
angular.module('mynewanimalfriend', [
'ngRoute',
'ngAnimate',
'ngMaterial'
])
.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
});
});

As mentioned earlier, because we’re using Browserify, we can use the require() syntax to import our dependencies. Note we also give our module a name and specify the dependencies. Finally, note that we use $routeProvider to set up our first route, and we map the template URL and controller to match our test.

Let’s run the test again:

$ npm test
> mynewanimalfriend-app@1.0.0 test /home/matthew/Projects/mynewanimalfriend-app
> karma start
12 09 2016 22:35:51.231:DEBUG [config]: autoWatch set to false, because of singleRun
12 09 2016 22:35:51.235:DEBUG [plugin]: Loading karma-* from /home/matthew/Projects/mynewanimalfriend-app/node_modules
12 09 2016 22:35:51.237:DEBUG [plugin]: Loading plugin /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-browserify.
12 09 2016 22:35:51.354:DEBUG [plugin]: Loading plugin /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-coverage.
12 09 2016 22:35:51.496:DEBUG [plugin]: Loading plugin /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine.
12 09 2016 22:35:51.497:DEBUG [plugin]: Loading plugin /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-phantomjs-launcher.
12 09 2016 22:35:51.547:DEBUG [framework.browserify]: created browserify bundle: /tmp/02002698e6d413a542186462d3a0a6ce.browserify
12 09 2016 22:35:51.559:DEBUG [framework.browserify]: add bundle to config.files at position 3
12 09 2016 22:35:51.564:DEBUG [web-server]: Instantiating middleware
12 09 2016 22:35:51.581:DEBUG [reporter]: Trying to load reporter: coverage
12 09 2016 22:35:51.582:DEBUG [reporter]: Trying to load color-version of reporter: coverage (coverage_color)
12 09 2016 22:35:51.582:DEBUG [reporter]: Couldn't load color-version.
12 09 2016 22:35:51.602:DEBUG [framework.browserify]: updating js/main.js in bundle
12 09 2016 22:35:51.603:DEBUG [framework.browserify]: building bundle
12 09 2016 22:35:52.306:DEBUG [framework.browserify]: bundling
12 09 2016 22:35:54.095:DEBUG [preprocessor.coverage]: Processing "/home/matthew/Projects/mynewanimalfriend-app/js/main.js".
12 09 2016 22:35:54.170:INFO [framework.browserify]: bundle built
12 09 2016 22:35:54.189:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/
12 09 2016 22:35:54.189:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
12 09 2016 22:35:54.197:INFO [launcher]: Starting browser PhantomJS
12 09 2016 22:35:54.198:DEBUG [temp-dir]: Creating temp dir at /tmp/karma-91342786
12 09 2016 22:35:54.201:DEBUG [launcher]: /home/matthew/Projects/mynewanimalfriend-app/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs /tmp/karma-91342786/capture.js
12 09 2016 22:35:54.300:DEBUG [web-server]: serving: /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma/static/client.html
12 09 2016 22:35:54.308:DEBUG [web-server]: serving: /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma/static/karma.js
12 09 2016 22:35:54.366:DEBUG [karma]: A browser has connected on socket /#FpcuZAJUT-u6Dl4sAAAA
12 09 2016 22:35:54.386:DEBUG [web-server]: upgrade /socket.io/?EIO=3&transport=websocket&sid=FpcuZAJUT-u6Dl4sAAAA
12 09 2016 22:35:54.442:INFO [PhantomJS 2.1.1 (Linux 0.0.0)]: Connected on socket /#FpcuZAJUT-u6Dl4sAAAA with id 91342786
12 09 2016 22:35:54.442:DEBUG [launcher]: PhantomJS (id 91342786) captured in 0.253 secs
12 09 2016 22:35:54.447:DEBUG [phantomjs.launcher]:
12 09 2016 22:35:54.448:DEBUG [middleware:karma]: custom files null null
12 09 2016 22:35:54.448:DEBUG [middleware:karma]: Serving static request /context.html
12 09 2016 22:35:54.449:DEBUG [web-server]: serving: /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma/static/context.html
12 09 2016 22:35:54.451:DEBUG [middleware:source-files]: Requesting /base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js?b1682a1eb50e00abf147fc1fb28e31006d499aae /
12 09 2016 22:35:54.451:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/jasmine-core/lib/jasmine-core/jasmine.js
12 09 2016 22:35:54.452:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/jasmine-core/lib/jasmine-core/jasmine.js
12 09 2016 22:35:54.453:DEBUG [middleware:source-files]: Requesting /base/node_modules/angular-material/angular-material-mocks.js?9f31553e4bbbad4d6b52638351e3a274352311c2 /
12 09 2016 22:35:54.453:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular-material/angular-material-mocks.js
12 09 2016 22:35:54.453:DEBUG [middleware:source-files]: Requesting /base/node_modules/karma-jasmine/lib/boot.js?945a38bf4e45ad2770eb94868231905a04a0bd3e /
12 09 2016 22:35:54.454:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine/lib/boot.js
12 09 2016 22:35:54.454:DEBUG [middleware:source-files]: Requesting /base/node_modules/karma-jasmine/lib/adapter.js?7975a273517f1eb29d7bd018790fd4c7b9a485d5 /
12 09 2016 22:35:54.454:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine/lib/adapter.js
12 09 2016 22:35:54.454:DEBUG [middleware:source-files]: Requesting /base/node_modules/angular-mocks/angular-mocks.js?cc56136dc551d94abe8195cf8475eb27a3aa3c4b /
12 09 2016 22:35:54.454:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular-mocks/angular-mocks.js
12 09 2016 22:35:54.455:DEBUG [middleware:source-files]: Requesting /base/node_modules/angular/angular.min.js?78069f9f3a9ca9652cb04c13ccb0670d747666b8 /
12 09 2016 22:35:54.455:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular/angular.min.js
12 09 2016 22:35:54.455:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular-material/angular-material-mocks.js
12 09 2016 22:35:54.455:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine/lib/boot.js
12 09 2016 22:35:54.455:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma-jasmine/lib/adapter.js
12 09 2016 22:35:54.456:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular-mocks/angular-mocks.js
12 09 2016 22:35:54.457:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/node_modules/angular/angular.min.js
12 09 2016 22:35:54.458:DEBUG [middleware:source-files]: Requesting /absolute/tmp/02002698e6d413a542186462d3a0a6ce.browserify?f4c82dc0618d979f84c89967ea1c412e646a5fe5 /
12 09 2016 22:35:54.458:DEBUG [middleware:source-files]: Fetching /tmp/02002698e6d413a542186462d3a0a6ce.browserify
12 09 2016 22:35:54.458:DEBUG [middleware:source-files]: Requesting /base/js/main.js?41c850cecc07c24d7cd0421e914bd2420671e573 /
12 09 2016 22:35:54.459:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/js/main.js
12 09 2016 22:35:54.460:DEBUG [middleware:source-files]: Requesting /base/test/routes.spec.js?92b15bb7c24bc6ead636994fb1c737b91727d887 /
12 09 2016 22:35:54.461:DEBUG [middleware:source-files]: Fetching /home/matthew/Projects/mynewanimalfriend-app/test/routes.spec.js
12 09 2016 22:35:54.461:DEBUG [web-server]: serving (cached): /tmp/02002698e6d413a542186462d3a0a6ce.browserify
12 09 2016 22:35:54.496:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/js/main.js
12 09 2016 22:35:54.497:DEBUG [web-server]: serving (cached): /home/matthew/Projects/mynewanimalfriend-app/test/routes.spec.js
12 09 2016 22:35:54.497:DEBUG [web-server]: serving: /home/matthew/Projects/mynewanimalfriend-app/node_modules/karma/static/context.js
12 09 2016 22:35:54.582:DEBUG [phantomjs.launcher]: WARNING: Tried to load angular more than once.
PhantomJS 2.1.1 (Linux 0.0.0) LOG: 'WARNING: Tried to load angular more than once.'
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 SUCCESS (0.004 secs / 0.358 secs)
12 09 2016 22:35:55.003:DEBUG [karma]: Run complete, exiting.
12 09 2016 22:35:55.003:DEBUG [launcher]: Disconnecting all browsers
12 09 2016 22:35:55.003:DEBUG [framework.browserify]: cleaning up
12 09 2016 22:35:55.006:DEBUG [coverage]: Writing coverage to /home/matthew/Projects/mynewanimalfriend-app/coverage/report-html
12 09 2016 22:35:55.078:DEBUG [coverage]: Writing coverage to /home/matthew/Projects/mynewanimalfriend-app/coverage/report-cobertura
12 09 2016 22:35:55.082:DEBUG [launcher]: Process PhantomJS exited with code 0
12 09 2016 22:35:55.082:DEBUG [temp-dir]: Cleaning temp dir /tmp/karma-91342786
12 09 2016 22:35:55.085:DEBUG [launcher]: Finished all browsers

Our first test has passed. Let’s add tests for the other routes:

'use strict';
describe('Routes', function () {
beforeEach(angular.mock.module('mynewanimalfriend'));
it('should map default route to home controller', function () {
inject(function ($route) {
expect($route.routes['/'].controller).toBe('HomeCtrl');
expect($route.routes['/'].templateUrl).toEqual('templates/home.html');
});
});
it('should map login route to login controller', function () {
inject(function ($route) {
expect($route.routes['/login'].controller).toBe('LoginCtrl');
expect($route.routes['/login'].templateUrl).toEqual('templates/login.html');
});
});
it('should map logout route to logout controller', function () {
inject(function ($route) {
expect($route.routes['/logout'].controller).toBe('LogoutCtrl');
expect($route.routes['/logout'].templateUrl).toEqual('templates/login.html');
});
});
});

Note that the logout route uses the login template. This is because all it will do is redirect the user to the login form.

For the sake of brevity I won’t display the test output, but two of these tests should now fail. We can easily set up the new routes in js/main.js:

'use strict';
require('angular');
require('angular-route');
require('angular-animate');
require('angular-material');
angular.module('mynewanimalfriend', [
'ngRoute',
'ngAnimate',
'ngMaterial'
])
.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.when('/', {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
})
.when('/logout', {
templateUrl: 'templates/login.html',
controller: 'LogoutCtrl'
});
});

That’s looking good so far. But what if someone navigates to a URL that doesn’t exist? Our router should handle that. Add this to the test:

it('should redirect other or empty routes to the home controller', function () {
inject(function ($route) {
expect($route.routes[null].redirectTo).toEqual('/')
});
});

Once again, the test should fail. Fixing it is fairly straightforward - we’ll use the otherwise() method to define a fallback route:

'use strict';
require('angular');
require('angular-route');
require('angular-animate');
require('angular-material');
angular.module('mynewanimalfriend', [
'ngRoute',
'ngAnimate',
'ngMaterial'
])
.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.when('/', {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
})
.when('/logout', {
templateUrl: 'templates/login.html',
controller: 'LogoutCtrl'
})
.otherwise({
redirectTo: '/'
});
});

Now our routes are in place, we need to implement the three controllers we will need. However, as two of these controllers deal with authentication, we’ll first create some services to handle that, and they’ll need to be tested. Save this as test/services.spec.js:

'use strict';
describe('Services', function () {
beforeEach(function(){
jasmine.addMatchers({
toEqualData: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
beforeEach(angular.mock.module('mynewanimalfriend.services'));
describe('Token service', function () {
var mockBackend, Token;
beforeEach(inject(function (_Token_, _$httpBackend_) {
Token = _Token_;
mockBackend = _$httpBackend_;
}));
it('can create a new token', function () {
mockBackend.expectPOST('http://localhost:8000/api/authenticate', '{"email":"bob@example.com","password":"password"}').respond({token: 'mytoken'});
var token = new Token({
email: 'bob@example.com',
password: 'password'
});
token.$save(function (response) {
expect(response).toEqualData({token: 'mytoken'});
});
mockBackend.flush();
});
});
});

In this test we use the $httpBackend facility from ngMock to mock out our API endpoints. We already have a REST API capable of generating a token, and we set this test up to behave similarly. We specify that it should expect to receive a certain POST request, and should respond with the token mytoken. Run the test to make sure it fails, then save this as js/services.js:

'use strict';
require('angular');
require("angular-resource");
angular.module('mynewanimalfriend.services', ['ngResource'])
.factory('Token', function ($resource) {
return $resource('http://localhost:8000/api/authenticate/');
});

A little explanation is called for. In Angular, the $resource dependency represents an HTTP resource. By default it supports making HTTP requests to the denoted endpoint via GET, POST and DELETE, and it’s trivial to add support for PUT or PATCH methods. Using $resource, you can easily interface with a RESTful web service, and it’s one of my favourite things about Angular.

We also need to load services.js in our main.js file:

'use strict';
require('angular');
require('angular-route');
require('angular-animate');
require('angular-material');
require('./services');
angular.module('mynewanimalfriend', [
'ngRoute',
'ngAnimate',
'ngMaterial',
'mynewanimalfriend.services'
])
.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.when('/', {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
})
.when('/logout', {
templateUrl: 'templates/login.html',
controller: 'LogoutCtrl'
})
.otherwise({
redirectTo: '/'
});
});

Now, running the tests should show that they pass.

With that in place, we will also create an authentication service that lets the app determine if the user is logged in. Add this to test/services.spec.js:

describe('Auth service', function () {
var Auth;
beforeEach(inject(function (_Auth_) {
Auth = _Auth_;
}));
it('can set user', function () {
Auth.setUser('mytoken');
var token = localStorage.getItem('authHeader');
expect(token).toEqual('Bearer mytoken');
});
it('can return login status', function () {
localStorage.setItem('authHeader', 'Bearer mytoken');
expect(Auth.isLoggedIn()).toBeTruthy();
});
it('can log the user out', function () {
localStorage.setItem('authHeader', 'Bearer mytoken');
Auth.logUserOut();
expect(Auth.isLoggedIn()).toBeFalsy();
expect(localStorage.getItem('authHeader')).toBeFalsy();
});
});

This service is expected to do three things:

  • Set the current user’s details in local storage
  • Return whether the user is logged in
  • Log the user out

Make sure the test fails, then amend js/services.js as follows:

'use strict';
require('angular');
require("angular-resource");
angular.module('mynewanimalfriend.services', ['ngResource'])
.factory('Auth', function(){
return{
setUser : function (aUser) {
localStorage.setItem('authHeader', 'Bearer ' + aUser);
},
isLoggedIn: function () {
var user = localStorage.getItem('authHeader');
return(user)? user : false;
},
logUserOut: function () {
localStorage.removeItem('authHeader');
}
}
})
.factory('Token', function ($resource) {
return $resource('http://localhost:8000/api/authenticate/');
});

When the user is set, we store the authentication details we need in local storage. We can then use that to determine if they are logged in. When they log out, we simply clear local storage,

That should be enough to make these tests pass. Now we can move on to our controllers. We’ll do the login controller first. Save this as test/controllers.spec.js:

'use strict';
describe('Controllers', function () {
beforeEach(function(){
jasmine.addMatchers({
toEqualData: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
beforeEach(angular.mock.module('mynewanimalfriend.controllers'));
describe('Login Controller', function () {
var mockBackend, scope;
beforeEach(inject(function ($rootScope, $controller, _$httpBackend_) {
mockBackend = _$httpBackend_;
scope = $rootScope.$new();
$controller('LoginCtrl', {
$scope: scope
});
}));
// Test controller scope is defined
it('should define the scope', function () {
expect(scope).toBeDefined();
});
// Test doLogin is defined
it('should define the login method', function () {
expect(scope.doLogin).toBeDefined();
});
// Test doLogin works
it('should allow the user to log in', function () {
// Mock the backend
mockBackend.expectPOST('http://localhost:8000/api/authenticate', '{"email":"user@example.com","password":"password"}').respond({token: 123});
// Define login data
scope.credentials = {
email: 'user@example.com',
password: 'password'
};
// Submit the request
scope.doLogin();
// Flush the backend
mockBackend.flush();
// Check login complete
expect(localStorage.getItem('authHeader')).toEqual('Bearer 123');
});
});
});

We check that the scope and the doLogin() method are defined. We then mock the backend’s /api/authenticate route to respond with a dummy token when our credentials are provided. Then, we set the credentials in the variable $scope.credentials, call doLogin(), flush the backend, and check the authentication header has been set.

Once you’ve verified these tests fail, we can start making them pass. Save this as js/controllers.js:

'use strict';
require('angular');
require('angular-route');
require('./services');
angular.module('mynewanimalfriend.controllers', [
'mynewanimalfriend.services',
"ngMaterial"
])
.controller('LoginCtrl', function ($scope, $location, Token, Auth) {
$scope.doLogin = function () {
var token = new Token($scope.credentials);
token.$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');
});
};
});

The LoginCtrl controller accepts the scope, location, and our two services. When doLogin() is alled, it picks up the values in $scope.credentials, which we will set in our template later. It then makes a POST request to our endpoint including those credentials. Our API backend should return the new token in the response, and the token is stored using the Auth service. Otherwise, it raises an error.

Check the test now passes before moving onto the logout functionality. Add this to test/controllers.spec.js:

describe('Logout Controller', function () {
var scope;
beforeEach(inject(function ($rootScope, $controller, Auth) {
Auth.setUser('Blah');
scope = $rootScope.$new();
$controller('LogoutCtrl', {
$scope: scope
});
}));
// Test controller scope is defined
it('should define the scope', function () {
expect(scope).toBeDefined();
});
// Test session cleared
it('should clear the session', function () {
expect(localStorage.getItem('authHeader')).toEqual(null);
});
});

We want to ensure that when the user navigates to the route managed by the LogoutCtrl controller, the session is cleared, so we set up an existing session, call the controller, check it’s defined, and then check that local storage is empty.

Once you’ve verified that the test fails, amend the controllers as follows:

'use strict';
require('angular');
require('angular-route');
require('./services');
angular.module('mynewanimalfriend.controllers', [
'mynewanimalfriend.services',
"ngMaterial"
])
.controller('LoginCtrl', function ($scope, $location, Token, Auth) {
$scope.doLogin = function () {
var token = new Token($scope.credentials);
token.$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');
});
};
})
.controller('LogoutCtrl', function ($scope, $location, Auth) {
// Log user out
Auth.logUserOut();
// Redirect to login page
$location.path('/login');
});

Our LogoutCtrl controller is very simple - it just logs the user out and redirects them back to the login form. Our final controller is for the home page:

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

For now our home controller does nothing except define the scope, so it’s easy to implement:

'use strict';
require('angular');
require('angular-route');
require('./services');
angular.module('mynewanimalfriend.controllers', [
'mynewanimalfriend.services',
"ngMaterial"
])
.controller('LoginCtrl', function ($scope, $location, Token, Auth) {
$scope.doLogin = function () {
var token = new Token($scope.credentials);
token.$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');
});
};
})
.controller('LogoutCtrl', function ($scope, $location, Auth) {
// Log user out
Auth.logUserOut();
// Redirect to login page
$location.path('/login');
})
.controller('HomeCtrl', function ($scope) {
});

Verify that the tests pass, and our controllers are done for now. However, we still have some work to do to hook the various elements up. First, of all, our main.js unnecessarily loads our services - since we only use those services in our controllers, we don’t need them there. We also need to be able to keep users out of routes other than login when not logged in. Here’s what you main.js should look like:

'use strict';
require('angular');
require('angular-route');
require('angular-animate');
require('angular-material');
require('./controllers');
angular.module('mynewanimalfriend', [
'ngRoute',
'ngAnimate',
'ngMaterial',
'mynewanimalfriend.controllers'
])
.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function (event) {
if (!Auth.isLoggedIn()) {
if ($location.path() !== '/login') {
$location.path('/login');
}
}
});
}])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('sessionInjector');
$httpProvider.interceptors.push('authInterceptor');
}])
.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.when('/', {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
})
.when('/logout', {
templateUrl: 'templates/login.html',
controller: 'LogoutCtrl'
})
.otherwise({
redirectTo: '/'
});
});

Note that we set it up to intercept the HTTP request with the session injector and the auth interceptor. Next we need to create these in js/services.js:

'use strict';
require('angular');
require("angular-resource");
angular.module('mynewanimalfriend.services', ['ngResource'])
.factory('Auth', function(){
return{
setUser : function (aUser) {
localStorage.setItem('authHeader', 'Bearer ' + aUser);
},
isLoggedIn: function () {
var user = localStorage.getItem('authHeader');
return(user)? user : false;
},
logUserOut: function () {
localStorage.removeItem('authHeader');
}
}
})
.factory('Token', function ($resource) {
return $resource('http://localhost:8000/api/authenticate/');
})
.factory('sessionInjector', function (Auth) {
var sessionInjector = {
request: function (config) {
if (Auth.isLoggedIn()) {
config.headers.Authorization = Auth.isLoggedIn();
}
return config;
}
};
return sessionInjector;
})
.service('authInterceptor', function ($q, Auth, $location) {
var service = this;
service.responseError = function (response) {
if (response.status == 400) {
Auth.logUserOut();
$location.path('/login');
}
return $q.reject(response);
};
});

I’ll walk you through these. sessionInjector adds the authorization HTTP header to every request to the server if the user is logged in, so that it returns the right user’s details. authInterceptor catches any 400 errors, denoting that the user is not authenticated with a current JSON web token, and logs the user out. In this way we can handle the expiry of a user’s token.

Now the logic of our app is in place, but that’s no use without some content…

Angular templating

We have one very basic HTML template, but that’s just a boilerplate for inserting the rest of our content. For the rest of the HTML we’ll need to load templates dynamically, and we’ll use Angular Material to help us build a nice UI quickly. Run the following commands to create the files:

$ mkdir www/templates
$ touch www/templates/login.html
$ touch www/templates/home.html

We need to import the CSS for Angular Material. Add this to sass/style.scss:

// Angular Material
@import "node_modules/angular-material/angular-material.scss";

With that done, we need to configure theming in main.js:

'use strict';
require('angular');
require('angular-route');
require('angular-animate');
require('angular-material');
require('./controllers');
angular.module('mynewanimalfriend', [
'ngRoute',
'ngAnimate',
'ngMaterial',
'mynewanimalfriend.controllers'
])
.config(function ($mdThemingProvider) {
$mdThemingProvider.theme('default')
.primaryPalette('purple')
.accentPalette('cyan');
})
.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function (event) {
if (!Auth.isLoggedIn()) {
if ($location.path() !== '/login') {
$location.path('/login');
}
}
});
}])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('sessionInjector');
$httpProvider.interceptors.push('authInterceptor');
}])
.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.when('/', {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl'
})
.when('/logout', {
templateUrl: 'templates/login.html',
controller: 'LogoutCtrl'
})
.otherwise({
redirectTo: '/'
});
});

You may want to look at the documentation for Angular Material to choose your own theme options. Next, let’s create our login template at www/templates/login.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>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>
</div>
</md-content>

We’re using Angular Material’s input and button directives to make our inputs look a bit nicer. Note that the ng-click handler calls the doLogin() method of our controller, and that the ng-model attributes contain the credentials object that gets passed to the API. If you haven’t used Angular before, ng-model essentially lets you bind a variable to an element’s value so, for instance, when an input is changed, it can be easily accessed via the variable.

Next, we’ll implement a placeholder for our home page with a log out button. Save this as www/templates/home.html:

<md-toolbar>
<div class="md-toolbar-tools">
<md-button aria-label="Log out" href="#logout">
Log out
</md-button>
</div>
</md-toolbar>

That should be all we need to demonstrate logging in and out of our app. Let’s try it out. First run the Gulp task to show the app in the browser:

$ gulp

Then, in another shell session, switch to the directory with the backend and run the server for that:

$ php artisan serve

You should already have a user account set up and ready to use thanks to the seeder we wrote. The browser should show the login page by default, and if you fill in the login form and click the button you should see the home page. You should then be able to log out again.

Congratulations! We’ve got authentication working.

Switching to HTML5 routing

You may note that the URLs use hashes - they are in the format http://localhost:5000/#/login. Wouldn’t it be better if we didn’t use the hash? Fortunately modern browsers support this via the HTML5 pushState API, and Angular has built-in support for this.

To enable it, we first need to declare a base URL in www/index.html. Amend it as follows:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
<title>My New Animal Friend</title>
<link href="/css/style.css" rel="stylesheet" type="text/css">
<base href="/">
</head>
<body>
<div>
<div ng-app="mynewanimalfriend" ng-cloak>
<div ng-view></div>
</div>
</div>
</body>
<script language="javascript" type="text/javascript" src="/js/bundle.js"></script>
</html>

Here we’ve added the <base href="/"> tag to denote our base URL. Next we configure Angular to use HTML5 routing in main.js:

.config(function($locationProvider) {
$locationProvider.html5Mode(true);
})

And amend the URL in the home template:

<md-toolbar>
<div class="md-toolbar-tools">
<md-button aria-label="Log out" href="/logout">
Log out
</md-button>
</div>
</md-toolbar>

Now, we should be using HTML5 routing throughout.

With that done, we can finish for today. We’ve got our basic app skeleton and authentication system up and running, and we’ll be in a good place to continue developing the rest of the app next time. I’ve put the source code on Github, and you can find this lesson’s work under the lesson-2 tag.

Next time we’ll develop the app further, including implementing the pet search functionality.


Building a Phonegap app with Laravel and Angular - Part 3

$
0
0

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.

Creating an Azure storage adapter for Laravel

$
0
0

UPDATE: This post has now been superseded by this one as I’ve released this integration as a package.

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.

Building a Phonegap App with Laravel and Angular - Part 4

$
0
0

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.

Easy static asset versioning in PHP

$
0
0

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.

Testing Laravel Middleware

$
0
0

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.

Integrating Behat with Laravel

$
0
0

The Gherkin format used by tools like Cucumber is a really great way of specifying how your application will work. It’s easy for even non-technical stakeholders to understand, it makes it natural to break your tests into easily reusable steps, and it encourages you to think about the application from an end-user’s perspective. It’s also one of the easiest ways to get started writing automated tests when you first start out - it’s much more intuitive to a junior developer than lower-level unit tests, and is easier to add to a legacy project that may not have been built with testability in mind - if you can drive a browser, you can test it.

Behat is a PHP equivalent. Combined with Mink, it allows for easy automated acceptance tests of a PHP application. However, out of the box it doesn’t integrate well with Laravel. There is Jeffrey Way’s Behat Laravel extension, but it doesn’t seem to be actively maintained and seems to be overkill for this purpose. I wanted something that I could use to run integration tests using PHPUnit’s assertions and Laravel’s testing utilities, and crucially, I wanted to do so as quickly as possible. That meant running a web server and using an automated web browser wasn’t an option. Also, I often work on REST API’s, and browser testing isn’t appropriate for those - in API tests I’m more interested in setting up the fixtures, making a single request, and verifying that it does what it’s meant to do, as quickly as possible.

As it turns out, integrating Behat and Laravel isn’t that hard. When using Behat, your FeatureContext.php file must implement the Behat\Behat\Context\Context interface, but as this interface does not implement any methods, you can extend any existing class and declare that it implements that interface. That means we can just extend the existing Tests\TestCase class in Laravel 5.4 and gain access to all the same testing utilities we have in our regular Laravel tests.

Then, in the constructor we can set environment variables using putenv() so that we can set it up to use an in-memory SQLite database for faster tests. We also use the @BeforeScenario hook to migrate the database before each scenario, and the @AfterScenario hook to roll it back afterwards.

Here’s the finished example:

<?php
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Tests\TestCase;
use Behat\Behat\Tester\Exception\PendingException;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use App\User;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Illuminate\Contracts\Console\Kernel;
/**
* Defines application features from the specific context.
*/
class FeatureContext extends TestCase implements Context
{
use DatabaseMigrations;
protected $content;
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct()
{
putenv('DB_CONNECTION=sqlite');
putenv('DB_DATABASE=:memory:');
parent::setUp();
}
/** @BeforeScenario */
public function before(BeforeScenarioScope $scope)
{
$this->artisan('migrate');
$this->app[Kernel::class]->setArtisan(null);
}
/** @AfterScenario */
public function after(AfterScenarioScope $scope)
{
$this->artisan('migrate:rollback');
}
/**
* @Given I visit the path :path
*/
public function iVisitThePath($path)
{
$response = $this->get('/');
$this->assertEquals(200, $response->getStatusCode());
$this->content = $response->getContent();
}
/**
* @Then I should see the text :text
*/
public function iShouldSeeTheText($text)
{
$this->assertContains($text, $this->content);
}
/**
* @Given a user called :user exists
*/
public function aUserCalledExists($user)
{
$user = factory(App\User::class)->create([
'name' => $user,
]);
}
/**
* @Given I am logged in as :user
*/
public function iAmLoggedInAs($user)
{
$user = User::where('name', $user)->first();
$this->be($user);
}
}

Note that I’ve added a few basic example methods for our tests. As you can see, we can call the same methods we normally use in Laravel tests to make assertions and HTTP requests. If you’re using Dusk, you can also call that in the same way you usually would.

We might then write the following feature file to demonstrate our application at work:

Feature: Login
Background:
Given a user called "Alan" exists
And a user called "Bob" exists
And a user called "Clare" exists
And a user called "Derek" exists
And a user called "Eric" exists
Scenario: Log in as Alan
Given I am logged in as "Alan"
And I visit the path "/"
Then I should see the text "Laravel"
Scenario: Log in as Bob
Given I am logged in as "Bob"
And I visit the path "/"
Then I should see the text "Laravel"
Scenario: Log in as Clare
Given I am logged in as "Clare"
And I visit the path "/"
Then I should see the text "Laravel"
Scenario: Log in as Derek
Given I am logged in as "Derek"
And I visit the path "/"
Then I should see the text "Laravel"
Scenario: Log in as Eric
Given I am logged in as "Eric"
And I visit the path "/"
Then I should see the text "Laravel"

We can then run these tests with vendor/bin/behat:

$ vendor/bin/behat
Feature: Login
Background: # features/auth.feature:3
Given a user called "Alan" exists # FeatureContext::aUserCalledExists()
And a user called "Bob" exists # FeatureContext::aUserCalledExists()
And a user called "Clare" exists # FeatureContext::aUserCalledExists()
And a user called "Derek" exists # FeatureContext::aUserCalledExists()
And a user called "Eric" exists # FeatureContext::aUserCalledExists()
Scenario: Log in as Alan # features/auth.feature:10
Given I am logged in as "Alan" # FeatureContext::iAmLoggedInAs()
And I visit the path "/" # FeatureContext::iVisitThePath()
Then I should see the text "Laravel" # FeatureContext::iShouldSeeTheText()
Scenario: Log in as Bob # features/auth.feature:15
Given I am logged in as "Bob" # FeatureContext::iAmLoggedInAs()
And I visit the path "/" # FeatureContext::iVisitThePath()
Then I should see the text "Laravel" # FeatureContext::iShouldSeeTheText()
Scenario: Log in as Clare # features/auth.feature:20
Given I am logged in as "Clare" # FeatureContext::iAmLoggedInAs()
And I visit the path "/" # FeatureContext::iVisitThePath()
Then I should see the text "Laravel" # FeatureContext::iShouldSeeTheText()
Scenario: Log in as Derek # features/auth.feature:25
Given I am logged in as "Derek" # FeatureContext::iAmLoggedInAs()
And I visit the path "/" # FeatureContext::iVisitThePath()
Then I should see the text "Laravel" # FeatureContext::iShouldSeeTheText()
Scenario: Log in as Eric # features/auth.feature:30
Given I am logged in as "Eric" # FeatureContext::iAmLoggedInAs()
And I visit the path "/" # FeatureContext::iVisitThePath()
Then I should see the text "Laravel" # FeatureContext::iShouldSeeTheText()
5 scenarios (5 passed)
40 steps (40 passed)
0m0.50s (19.87Mb)

Higher level tests can get very tedious if you’re not careful - you wind up setting up the same fixtures and making the same requests many times over. By using Behat in this way, not only are you writing your tests in a way that is easy to understand, but you’re also breaking it down into logical, repeatable steps, and by passing arguments in each step you limit the amount of repetition. It’s also fast if you aren’t running browser-based tests, making it particularly well-suited to API testing.

My first Laravel package

$
0
0

For some time now I’ve had a Laravel middleware I use extensively to add ETags to HTTP requests. I often use it for work projects, but obviously copying and pasting it all the time was a pain. I always meant to create a package for it, but I didn’t want to do so until such time as I had some proper tests for it. Now I’ve finally figured out how to test middleware in isolation and I’ve got around to adding tests and creating a proper package for it.

It’s available on Github and Packagist if you want to use it.


Decorating Laravel repositories

$
0
0

As mentioned previously, when building any nontrivial Laravel application, it’s prudent to decouple our controllers from the Eloquent ORM (or any other ORM or data source we may be using) by creating an interface, and then writing a repository that implements that interface. We can then resolve the interface to our repository, and use the repository to interact with our data source. Should we need to switch to a different implementation, we then need only create the new repository and amend how Laravel resolves that interface.

The same principle applies when it comes to caching. Database queries are typically a major bottleneck in a web application, and so it’s prudent to implement some form of caching for your queries. However, it’s a bad idea to do so in your controllers, because just as with Eloquent models, you’re tying yourself to one particular implementation and won’t be able to switch without rewriting a good chunk of your controllers, as well as possibly having to maintain large amounts of duplicate code for when a query is made in several places.

Alternatively, you could implement caching within the methods of your repository, which might make sense for smaller projects. However, it means that your repository is now dependent on both the ORM and cache you chose. If you decide you want to change your ORM but retain the same caching system, or vice versa, you’re stuck with writing a new repository to handle both, duplicating work you’ve already done.

Fortunately, there’s a more elegant solution. Using the decorator pattern, we can create a second repository that implements the same interface and “wraps” the original repository. Each of its methods will call its counterpart in the original, and if appropriate cache the response. That way, our caching is implemented separately from our database interactions, and we can easily create a repository for a new data source without affecting the caching in the slightest.

Say we have the following interface for our User model:

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

And the following repository implements that interface:

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

We might implement the following repository class to handle caching:

<?php
namespace App\Repositories\Decorators;
use App\Repositories\Interfaces\UserRepositoryInterface;
use Illuminate\Contracts\Cache\Repository as Cache;
class CachingUserRepository implements UserRepositoryInterface {
protected $repository;
protected $cache;
public function __construct(UserRepositoryInterface $repository, Cache $cache)
{
$this->repository = $repository;
$this->cache = $cache;
}
public function all()
{
return $this->cache->tags('users')->remember('all', 60, function () {
return $this->repository->all();
});
}
public function findOrFail($id)
{
return $this->cache->tags('users')->remember($id, 60, function () use ($id) {
return $this->repository->findOrFail($id);
});
}
public function create($input)
{
$this->cache->tags('users')->flush();
return $this->repository->create($input);
}
}

Note how each method doesn’t actually do any querying. Instead, the constructor accepts an implementation of the same interface and the cache, and we defer all interactions with the database to that implementation. Each call that queries the database is wrapped in a callback so that it’s stored in Laravel’s cache when it’s returned, without touching the original implementation. When a user is created, the users tag is flushed from the cache so that stale results don’t get served.

To actually use this implementation, we need to update our service provider so that it resolves the interface to an implementation of our decorator:

<?php
namespace App\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->singleton('App\Repositories\Interfaces\UserRepositoryInterface', function () {
$baseRepo = new \App\Repositories\EloquentUserRepository(new \App\User);
$cachingRepo = new \App\Repositories\Decorators\CachingUserRepository($baseRepo, $this->app['cache.store']);
return $cachingRepo;
});
}
}

We instantiate the base repository, passing it the appropriate model. Then we instantiate the decorator, passing it the base repository and the cache, and return it. Now our controllers will start using the new decorator.

Testing the decorator

Now that we have a working decorator, how do we test it? Just as with the decorator itself, we want our tests to be completely decoupled from any particular implementation of the dependencies. If in future we’re asked to migrate the database to MongoDB, say, we’ll have plenty of work writing our new database repositories, so we don’t want to have to rewrite the tests for our decorator as well. Fortunately, using Mockery we can just mock the interface for the repository, and pass that mock into the constructor of the decorator in our test. That way we can have the mock return a known response and not involve either the database repository or the underlying models in any way.

We will also want to mock the cache itself, as this is a unit test and so as far as possible it should not be testing anything outside of the repository class. Here’s an example of how we might test the above decorator.

<?php
namespace Tests\Repositories\Decorators;
use Tests\TestCase;
use App\Repositories\Decorators\CachingUserRepository;
use Mockery as m;
class UserTest extends TestCase
{
/**
* Test fetching all items
*
* @return void
*/
public function testFetchingAll()
{
// Create mock of decorated repository
$repo = m::mock('App\Repositories\Interfaces\UserRepositoryInterface');
$repo->shouldReceive('all')->andReturn([]);
// Create mock of cache
$cache = m::mock('Illuminate\Contracts\Cache\Repository');
$cache->shouldReceive('tags')->with('users')->andReturn($cache);
$cache->shouldReceive('remember')->andReturn([]);
// Instantiate the repository
$repository = new CachingUserRepository($repo, $cache);
// Get all
$items = $repository->all();
$this->assertCount(0, $items);
}
/**
* Test fetching a single item
*
* @return void
*/
public function testFindOrFail()
{
// Create mock of decorated repository
$repo = m::mock('App\Repositories\Interfaces\UserRepositoryInterface');
$repo->shouldReceive('findOrFail')->with(1)->andReturn(null);
// Create mock of cache
$cache = m::mock('Illuminate\Contracts\Cache\Repository');
$cache->shouldReceive('tags')->with('users')->andReturn($cache);
$cache->shouldReceive('remember')->andReturn(null);
// Instantiate the repository
$repository = new CachingUserRepository($repo, $cache);
// Get all
$item = $repository->findOrFail(1);
$this->assertNull($item);
}
/**
* Test creating a single item
*
* @return void
*/
public function testCreate()
{
// Create mock of decorated repository
$repo = m::mock('App\Repositories\Interfaces\UserRepositoryInterface');
$repo->shouldReceive('create')->with(['email' => 'bob@example.com'])->andReturn(true);
// Create mock of cache
$cache = m::mock('Illuminate\Contracts\Cache\Repository');
$cache->shouldReceive('tags')->with('usersUser')->andReturn($cache);
$cache->shouldReceive('flush')->andReturn(true);
// Instantiate the repository
$repository = new CachingUserRepository($repo, $cache);
// Get all
$item = $repository->create(['email' => 'bob@example.com']);
$this->assertTrue($item);
}
public function tearDown()
{
m::close();
parent::tearDown();
}
}

As you can see, all we care about is that the underlying repository interface receives the correct method calls and arguments, nothing more. That way our test is fast and repository-agnositc.

Other applications

Here I’ve used this technique to cache the queries, but that’s not the only use case for decorating a repository. For instance, you could decorate a repository to fire events when certain methods are called, and write different decorators when reusing these repositories for different applications. You could create one to log interactions with the repository, or you could use an external library to cache your queries, all without touching your existing repository. Should we need to switch back to our base repository, it’s just a matter of amending the service provider accordingly as both the decorator and the repository implement the same interface.

Creating decorators does mean you have to implement all of the interface’s methods again, but if you have a base repository that your other ones inherit from, you can easily create a base decorator in a similar fashion that wraps methods common to all the repositories, and then just implement the additional methods for each decorator as required. Also, each method is likely to be fairly limited in scope so it’s not generally too onerous.

Enforcing a coding standard with PHP CodeSniffer

$
0
0

We all start new projects with the best of intentions - it’ll be clean, fully tested and work perfectly. Sadly as deadlines loom, it’s all too easy to find yourself neglecting your code quality, and once it starts to degrade, getting it back becomes much harder. Many development teams try to adhere to a coding standard, but it can be hard to enforce on other people - it puts you in the uncomfortable position of nagging others all the time.

Fortunately, there’s an easy solution that doesn’t force everyone to use the same IDE. PHP CodeSniffer is a useful package that lets you specify a coding standard and then validate your code against it. That way, you can set up continuous integration and use that to remind people of errors. Better still, it also allows many errors to be fixed automatically.

To use it on your PHP project, run the following command:

$ composer require --dev squizlabs/php_codesniffer

As this will only ever be used in development, you should use the --dev flag. We also need to specify the settings for our project. This example is for a module to be used with a Laravel application and should be saved as phpcs.xml:

<?xml version="1.0"?>
<ruleset name="PHP_CodeSniffer">
<description>The coding standard for our project.</description>
<file>app</file>
<file>tests</file>
<exclude-pattern>*/migrations/*</exclude-pattern>
<arg value="np"/>
<rule ref="PSR2"/>
</ruleset>

Note the <rule /> tag - this specifies that this project should be validated as PSR2. Also, note the <file /> and <exclude-pattern /> tags - these specify what files should and should not be checked.

With this in place, we’re ready to run PHP CodeSniffer:

$ vendor/bin/phpcs
......................
Time: 45ms; Memory: 6Mb

In this case, our code validated successfully. However, if it doesn’t, there’s an easy way to tidy it up. Just run this command:

$ vendor/bin/phpcbf

That will fix many of the most common problems, and any others should be straightforward to fix.

PHP CodeSniffer makes it extremely straightforward to enforce a coding style. You can write custom rulesets or just use an existing one as you prefer, and it’s easy to fix many common problems automatically. In fact, it makes it so easy that there’s very little excuse not to meet the coding standard.

Snapshot test your Vue components with Jest

$
0
0

At work I’ve recently started using Vue as my main front-end framework instead of Angular 1. It has a relatively shallow learning curve and has enough similarities with both React and Angular 1 that if you’re familiar with one or both of them it feels quite familiar. We’re a Laravel shop and Laravel comes out of the box with a basic scaffolding for using Vue, so not only is it the path of least resistance, but many of my colleagues knew it already and it’s used on some existing projects (one of which I’ve been helping out on this week), so it made sense to learn it. Add to that the fact that the main alternative is Angular 2, which I vehemently dislike, and learning Vue was a no-brainer.

Snapshot tests are a really useful way of making sure your user interface doesn’t change unexpectedly. Facebook introduced them to their Jest testing framework last year, and they’ve started to appear in other testing frameworks too. In their words…

A typical snapshot test case for a mobile app renders a UI component, takes a screenshot, then compares it to a reference image stored alongside the test. The test will fail if the two images do not match: either the change is unexpected, or the screenshot needs to be updated to the new version of the UI component.

This makes it easy to make sure than a UI component, such as a React or Vue component, does not unexpectedly change how it is rendered. In the event that it does change, it will fail the test, and it’s up to the developer to confirm whether or not that’s expected - if so they can generate a new version of the snapshot and be on their way. Without it, you’re stuck manually testing that the right HTML tags get generated, which is a chore.

Jest’s documentation is aimed pretty squarely at React, but it’s not hard to adapt it to work with Vue components. Here I’ll show you how I got it working with Vue.

Setting up a new project

I used the Vue CLI boilerplate generator to set up my initial dependencies for this project. I then had to install some further packages:

$ npm install --save-dev jest babel-jest jest-vue-preprocessor

After that, I had to configure Jest to work with Vue. The finished package.json looked like this:

{
"name": "myproject",
"version": "1.0.0",
"description": "A project",
"author": "Matthew Daly <matthew@matthewdaly.co.uk>",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js",
"lint": "eslint --ext .js,.vue src",
"test": "jest __test__/ --coverage"
},
"dependencies": {
"vue": "^2.3.3",
"vue-router": "^2.3.1"
},
"devDependencies": {
"autoprefixer": "^6.7.2",
"babel-core": "^6.22.1",
"babel-eslint": "^7.1.1",
"babel-jest": "^20.0.3",
"babel-loader": "^6.2.10",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"eslint": "^3.19.0",
"eslint-config-standard": "^6.2.1",
"eslint-friendly-formatter": "^2.0.7",
"eslint-loader": "^1.7.1",
"eslint-plugin-html": "^2.0.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^2.0.1",
"eventsource-polyfill": "^0.9.6",
"express": "^4.14.1",
"extract-text-webpack-plugin": "^2.0.0",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3",
"jest": "^20.0.4",
"jest-vue-preprocessor": "^1.0.1",
"opn": "^4.0.2",
"optimize-css-assets-webpack-plugin": "^1.3.0",
"ora": "^1.2.0",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"url-loader": "^0.5.8",
"vue-loader": "^12.1.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.3.3",
"webpack": "^2.6.1",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"jest": {
"testRegex": "spec.js$",
"moduleFileExtensions": [
"js",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/jest-vue-preprocessor"
}
}
}

I won’t include things like the Webpack config, because that’s all generated by Vue CLI. Note that we need to tell Jest what file extensions it should work with, including .vue, and we need to specify the appropriate transforms for different types of files. We use jest-vue-preprocessor for .vue files and babel-jest for .js files.

With that done, we can create a basic component. We’ll assume we’re writing a simple issue tracker here, and our first component will be at src/components/Issue.vue:

<template>
<div>
<h1>An Issue</h1>
</div>
</template>
<script>
export default {
data () {
return {}
}
}
</script>
<style scoped>
</style>

Next, we create a simple test for this component. Save this as __test__/components/issue.spec.js:

import Issue from '../../src/components/Issue.vue'
import Vue from 'vue'
const Constructor = Vue.extend(Issue)
const vm = new Constructor().$mount()
describe('Issue', () => {
it('should render', () => {
expect(vm.$el.querySelector('h1').textContent).toEqual('An Issue')
});
it('should match the snapshot', () => {
expect(vm.$el).toMatchSnapshot()
});
});

Constructor is what creates our Vue component, while vm is our actual newly-mounted Vue component. We can refer to the HTML inside the component through vm.$el, so we can then work with the virtual DOM easily.

In the first test we use the more traditional method of verifying our UI component has worked as expected - we fetch an HTML tag inside it and verify that the content inside is what we expect. This is fine for a small component, but as the components get larger we’ll find it more of a chore.

The second test is much simpler and more concise. We simply assert that it matches the snapshot. Not only is that easier, but it can scale to components of any size because we don’t have to check every little element.

Let’s run our tests:

$ npm test
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage
PASS __test__/components/issue.spec.js
Issue
✓ should render (46ms)
✓ should match the snapshot (14ms)
Snapshot Summary
› 1 snapshot written in 1 test suite.
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 added, 1 total
Time: 8.264s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.15 | 50 | 100 | 96 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Note this section:

Snapshot Summary
› 1 snapshot written in 1 test suite.

This tells us that the snapshot has been successfully written. If we run the tests again we should see that it checks against the existing snapshot:

$ npm test
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage
PASS __test__/components/issue.spec.js
Issue
✓ should render (40ms)
✓ should match the snapshot (12ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 passed, 1 total
Time: 3.554s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.15 | 50 | 100 | 96 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Great stuff. Now, if we make a minor change to our component, such as changing the text from An Issue to My Issue, does it pick that up?

$ npm test
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage
FAIL __test__/components/issue.spec.js (5.252s)
● Issue › should render
expect(received).toEqual(expected)
Expected value to equal:
"An Issue"
Received:
"My Issue"
at Object.<anonymous> (__test__/components/issue.spec.js:9:52)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
● Issue › should match the snapshot
expect(value).toMatchSnapshot()
Received value does not match stored snapshot 1.
- Snapshot
+ Received
<div>
<h1>
- An Issue
+ My Issue
</h1>
</div>
at Object.<anonymous> (__test__/components/issue.spec.js:13:20)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
Issue
✕ should render (48ms)
✕ should match the snapshot (25ms)
Snapshot Summary
› 1 snapshot test failed in 1 test suite. Inspect your code changes or run with `npm test -- -u` to update them.
Test Suites: 1 failed, 1 total
Tests: 2 failed, 2 total
Snapshots: 1 failed, 1 total
Time: 7.082s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.15 | 50 | 100 | 96 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Yes, we can see that it’s picked up on the change and thrown an error. Note this line:

 › 1 snapshot test failed in 1 test suite. Inspect your code changes or run with `npm test -- -u` to update them.

Jest is telling us that our snapshot has changed, but if we expect that, we can just run npm test -- -u to replace the existing one with our new one. Then, our tests will pass again.

Now, this component is pretty useless. It doesn’t accept any external input whatsoever, so the response is always going to be the same. How do we test a more dynamic component? Amend the component to look like this:

<template>
<div>
<h1>{{ issue.name }}</h1>
</div>
</template>
<script>
export default {
props: {
issue: Object
},
data () {
return {}
}
}
</script>
<style scoped>
</style>

We’re now passing the issue object into our component as a prop, and getting the name from that. That will break our test, so we need to amend it to pass through the props:

import Issue from '../../src/components/Issue.vue'
import Vue from 'vue'
const Constructor = Vue.extend(Issue)
const issue = {
name: 'My Issue'
}
const vm = new Constructor({
propsData: { issue: issue }
}).$mount()
describe('Issue', () => {
it('should render', () => {
expect(vm.$el.querySelector('h1').textContent).toEqual('My Issue')
});
it('should match the snapshot', () => {
expect(vm.$el).toMatchSnapshot()
});
});

Here we pass our prop into the constructor for the component. Now, let’s run the tests again:

$ npm test
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage
FAIL __test__/components/issue.spec.js
● Issue › should match the snapshot
expect(value).toMatchSnapshot()
Received value does not match stored snapshot 1.
- Snapshot
+ Received
<div>
<h1>
- An Issue
+ My Issue
</h1>
</div>
at Object.<anonymous> (__test__/components/issue.spec.js:18:20)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
Issue
✓ should render (39ms)
✕ should match the snapshot (25ms)
Snapshot Summary
› 1 snapshot test failed in 1 test suite. Inspect your code changes or run with `npm test -- -u` to update them.
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 1 failed, 1 total
Time: 3.717s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.3 | 50 | 100 | 96.15 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Jest has picked up on our changes and thrown an error. However, because we know the UI has changed, we’re happy with this situation, so we can tell Jest to replace the prior snapshot with npm test -- -u as mentioned earlier:

$ npm test -- -u
> myproject@1.0.0 test /home/matthew/Projects/myproject
> jest __test__/ --coverage "-u"
PASS __test__/components/issue.spec.js
Issue
✓ should render (39ms)
✓ should match the snapshot (14ms)
Snapshot Summary
› 1 snapshot updated in 1 test suite.
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 updated, 1 total
Time: 3.668s
Ran all test suites matching "__test__/".
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|
All files | 96.3 | 50 | 100 | 96.15 | |
root | 100 | 100 | 100 | 100 | |
unknown | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/__test__/components | 100 | 100 | 100 | 100 | |
issue.spec.js | 100 | 100 | 100 | 100 | |
root/home/matthew/Projects/myproject/src/components | 94.44 | 50 | 100 | 94.12 | |
Issue.vue | 94.44 | 50 | 100 | 94.12 | 39 |
-----------------------------------------------------------|----------|----------|----------|----------|----------------|

Great, we now have a passing test suite again! That’s all we need to make sure that any regressions in the generated HTML of a component get caught.

Of course, this won’t help with the actual functionality of the component. However, Jest is pretty easy to use to write tests for the actual functionality of the application. If you prefer another testing framework, it’s possible to do the same with them, although I will leave setting them up as an exercise for the reader.

Profiling your Laravel application with Clockwork

$
0
0

If you’re building any non-trivial application, it’s always a good idea to profile it to find performance problems. Laravel Debugbar is the usual solution for profiling Laravel web applications, but it isn’t really much use for REST API’s or single-page web apps that consume them.

Recently I was introduced to Clockwork, which is a server-side extension for profiling PHP applications. It’s made it a whole lot easier to track down issues like excessive numbers of queries when building an API, and as a result I’ve been able to dramatically improve the performance of an API I’ve been working on. Here I’ll show you how you can use it on a project.

Installing Clockwork

Clockwork is available via Composer:

$ composer require itsgoingd/clockwork

You also need to register the service provider in config/app.php:

   Clockwork\Support\Laravel\ClockworkServiceProvider::class,

And register the middleware globally in app/Http/Kernel.php:

protected $middleware = [
\Clockwork\Support\Laravel\ClockworkMiddleware::class,
]

Note that it only works when APP_DEBUG is set to true in your .env file. This means that you can keep it in your application without worrying about exposing too much data in production, as long as debug mode is not active on your production server (which it shouldn’t be anyway).

You will also need to install the Chrome extension in order to actually work with the returned data. Clockwork works by adding its own route to your Laravel application, and this extension makes sure that it makes the appropriate request on loading a page, and then displays the data in the dev tools.

Once it’s all installed and your application is running, open the dev tools and you should see the new Clockwork tab in there. On the left of this tab is a list of requests - if you make a request, you’ll see it added to the list. When you click on each request, you’ll see the following tabs, where applicable:

Request

Request tab

This is similar to Chrome’s network tab in that it shows all of the headers for a given request. It’s not anything you can’t get using Chrome’s existing dev tools, but because it doesn’t show any static content it’s arguably a bit easier to navigate.

Timeline

Timeline tab

This shows how long the response takes to respond, which can be helpful in identifying slower requests.

In addition, you can create your own events using the clock() helper, which will appear in the timeline, as in this example:

clock()->startEvent('email_sent', 'Email sent.');
clock()->endEvent('email_sent');

Log

Log tab

The log tab is only displayed if you use the clock() helper to log data. You can log text or JSON objects as appropriate:

clock('Message text.'); // 'Message text.' appears in Clockwork log tab
clock(['hello' => 'world']); // logs json representation of the array

This is arguably more convenient than using the Log facade to write to the application log, since it’s kept in the browser and you can easily see what request caused what message to be logged.

Database

Database tab

The database tab displays details of the queries made by a request. This is useful for identifying things such as:

  • Repeated queries that should be cached
  • The n+1 problem (which can be resolved by use of eager loading)
  • Slow queries that need to be optimised

Note that if a particular endpoint does not trigger a query, this tab will not be visible.

Cookies

Cookies tab

For a REST API, you shouldn’t really have much use for cookies, but if you do, this tab lets you view the cookies set on the request.

Session

Session tab

As with cookies, the session isn’t normally something you’d use for an API, but this tab lets you view it.

Views

Views tab

This tab shows the views used on the page, and all of the data passed to them.

Routes

Routes tab

This tab shows all of the routes defined within your application.

Clockwork isn’t limited to Laravel - you can also use it with Lumen, Slim 2, and CodeIgniter 2.1, and it’s possible to write your own integration for other frameworks. It’s still fundamentally browser-based, so it’s difficult to use it if your API doesn’t have at least some kind of web front end (whether that’s a single page web app or Phonegap app that consumes the API, or that the API is itself browseable and returns HTML in a web browser), but I’ve found it to be superior to Laravel Debugbar for most of what I do.

Run your tests locally with Sismo

$
0
0

Continuous integration is a veritable boon when working on any large software project. However, the popularity of distributed version control systems like Git over the older, more centralised ones like Subversion means that when you commit your changes, they don’t necessarily get pushed up to a remote repository immediately. While this is a good thing because it means you can commit at any stage without worrying about pushing up changes that break everyone else’s build, it has the downside that the tests aren’t automatically run on every commit, just every push, so if you get sloppy about running your tests before every commit you can more easily get caught out. In addition, a full CI server like Jenkins is a rather large piece of software that you don’t really want to run locally if you can help it, and has a lot of functionality you don’t need.

Sismo is a small, simple continuous integration server, implemented in PHP, that’s ideal for running locally. You can set it up to run your tests on every commit, and it has an easy-to-use web interface. Although it’s a PHP application, there’s no reason why you couldn’t use it to run tests for projects in other languages, and because it’s focused solely on running your test suite without many of the other features of more advanced CI solutions, it’s a good fit for local use. Here I’ll show you how I use it.

Setting up Sismo

Nowadays I don’t generally install a web server on a computer directly, preferring to use Vagrant or the dev server as appropriate, so Sismo generally doesn’t have to coexist with anything else. I normally install PHP7’s FastCGI implementation and Nginx, along with the SQLite bindings (which Sismo needs):

$ sudo apt-get install nginx php7.0-fpm php7.0-sqlite3

Then we can set up our Nginx config at /etc/nginx/sites-available/default:

server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
fastcgi_param HTTP_PROXY "";
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
root /var/www/html;
index sismo.php index.html index.htm;
server_name server_domain_or_IP;
location / {
try_files $uri $uri/ /sismo.php?$query_string;
}
location ~ \.php$ {
try_files $uri /sismo.php =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
fastcgi_index sismo.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SISMO_DATA_PATH "/home/matthew/.sismo/data";
fastcgi_param SISMO_CONFIG_PATH "/home/matthew/.sismo/config.php";
include fastcgi_params;
}
}

You’ll probably want to adjust the paths as appropriate. Then set up the required folders:

$ mkdir ~/.sismo
$ mkdir ~/.sismo/data
$ touch ~/.sismo/config.php
$ chmod -R a+w ~/.sismo/

Then, download Sismo and put it in your web root (here it’s at /var/www/html/sismo.php).

Now, say you have a project you want to test (I’m using my Laravel ETag middleware for this example). We need to specify the projects we want to test in ~/.sismo/config.php:

<?php
$projects = array();
$notifier = new Sismo\Notifier\DBusNotifier();
Sismo\Project::setDefaultCommand('if [ -f composer.json ]; then composer install; fi && vendor/bin/phpunit');
$projects[] = new Sismo\GithubProject('Laravel ETag Middleware', '/home/matthew/Projects/laravel-etag-middleware', $notifier);
return $projects;

Hopefully this shouldn’t be too difficult to understand. We create an array of projects, then specify a notifier (this is Linux-specific - refer to the documentation for using Growl on Mac OS). Next, we specify that by default the tests should run composer install followed by vendor/bin/phpunit. We then specify this project is a Github project - it also supports Bitbucket, or plain SSH, or the default Project, but in general it shouldn’t be a problem to use it with any repository as you can just run it against the local copy. Finally we return the list of projects.

Now, we should be able to run our tests as follows:

$ php /var/www/html/sismo.php build
Building Project "Laravel ETag Middleware" (into "68a087")

That should be working, but it doesn’t get us anything we don’t get by running the tests ourselves. To trigger the build, we need to set up a post-commit hook for our project in .git/hooks/post-commit:

#!/bin/sh
php /var/www/html/sismo.php --quiet --force build laravel-etag-middleware `git log -1 HEAD --pretty="%H"` &>/dev/null &

You should now be able to view your project in the Sismo web interface at http://localhost:

Sismo

Clicking on the project should take you through to its build history:

Sismo project page

From here on, it should be straightforward to add new projects as and when necessary. Because you can change the command on a per-project basis, you can quite happily use it to run tests for Python or Node.js projects as well as PHP ones, and it’s not hard to configure it.

I personally find it very useful to have something in place to run my tests on every commit like this, and while you could just use a post-commit hook for that, this approach is less obtrusive because it doesn’t force you to wait around for your test suite to finish.

Making internal requests with Laravel

$
0
0

Recently I’ve been working on a Phonegap app that needs to work offline. The nature of relational databases can often make this tricky if you’re dealing with related objects and you’re trying to retrofit it to something that wasn’t built with this use case in mind.

Originally my plan was to push each request that would have been made to a queue in WebSQL, and then on reconnect, make every request individually. It quickly became apparent, however, that this approach had a few problems:

  • If one request failed, the remaining requests had to be stopped from executing
  • It didn’t allow for storing the failed transactions in a way that made them easy to retrieve

Instead, I decided to create a single sync endpoint for the API that would accept an object containing all the requests that would be made, and then step through each one. If it failed, it would get the failed request and all subsequent ones in the object, and store them in the database. That way, even if the data didn’t sync correctly, it wasn’t lost, and if necessary it could be resolved manually.

Since the necessary API endpoints already existed, and were thoroughly tested, it was not a good idea to start duplicating that functionality. Instead, I implemented the functionality to carry out internal requests, and I thought I’d share how you can do this.

For any service you may build for your Laravel applications, it’s a good idea to create an interface for it first:

<?php
namespace App\Contracts;
interface MakesInternalRequests
{
/**
* Make an internal request
*
* @param string $action The HTTP verb to use.
* @param string $resource The API resource to look up.
* @param array $data The request body.
* @return \Illuminate\Http\Response
*/
public function request(string $action, string $resource, array $data = []);
}

That way you can resolve the service using dependency injection, making it trivial to replace it with a mock when testing.

Now, actually making an internal request is pretty easy. You get the app instance (you can do so by resolving it using dependency injection as I do below, or call the app() helper). Then you put together the request you want to make and pass it as an argument to the app’s handle() method:

<?php
namespace App\Services;
use Illuminate\Http\Request;
use App\Contracts\MakesInternalRequests;
use Illuminate\Foundation\Application;
use App\Exceptions\FailedInternalRequestException;
/**
* Internal request service
*/
class InternalRequest implements MakesInternalRequests
{
/**
* The app instance
*
* @var $app
*/
protected $app;
/**
* Constructor
*
* @param Application $app The app instance.
* @return void
*/
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* Make an internal request
*
* @param string $action The HTTP verb to use.
* @param string $resource The API resource to look up.
* @param array $data The request body.
* @throws FailedInternalRequestException Request could not be synced.
* @return \Illuminate\Http\Response
*/
public function request(string $action, string $resource, array $data = [])
{
// Create request
$request = Request::create('/api/' . $resource, $action, $data, [], [], [
'HTTP_Accept' => 'application/json',
]);
// Get response
$response = $this->app->handle($request);
if ($response->getStatusCode() >= 400) {
throw new FailedInternalRequestException($request, $response);
}
// Dispatch the request
return $response;
}
}

Also note that I’ve created a custom exception, called FailedInternalRequestException. This is fired if the status code returned from the internal requests is greater than or equal to 400 (thus denoting an error):

<?php
namespace App\Exceptions;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
/**
* Exception for when a bulk sync job fails
*/
class FailedInternalRequestException extends \Exception
{
/**
* Request instance
*
* @var $request
*/
protected $request;
/**
* Response instance
*
* @var $response
*/
protected $response;
/**
* Constructor
*
* @param Request $request The request object.
* @param Response $response The response object.
* @return void
*/
public function __construct(Request $request, Response $response)
{
parent::__construct();
$this->request = $request;
$this->response = $response;
}
/**
* Get request object
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Get response object
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
}

You can catch this exception in an appropriate place and handle it as you wish. Now, if you import the internal request class as $dispatcher, you can just call $dispatcher->request($action, $resource, $data), where $action is the HTTP verb, $resource is the API resource to send to, and $data is the data to send.

It’s actually quite rare to have to do this. In this case, because this was a REST API and every request made to it was changing the state of the application (there were no GET requests, only POST, PUT, PATCH and DELETE), it made sense to just break down the request body and do internal requests against the existing API, since otherwise I’d have to duplicate the existing functionality. I would not recommend this approach for something like fetching data to render a page on the server side, as there are more efficient ways of accomplishing it. In all honesty I can’t think of any other scenario where this would genuinely be the best option. However, it worked well for my use case and allowed me to implement this functionality quickly and simply.

Installing Nginx Unit on Ubuntu

$
0
0

Recently Nginx announced the release of the first beta of Unit, an application server that supports Python, PHP and Go, with support coming for Java, Node.js and Ruby.

The really interesting part is that not only does it support more than one language, but Unit can be configured by making HTTP requests, rather than by editing config files. This makes it potentially very interesting to web developers like myself who have worked in multiple languages - I could use it to serve a Python or PHP web app, simply by making different requests during the setup process. I can see this being a boon for SaaS providers - you could pick up the language from a file, much like the runtime.txt used by Heroku, and set up the application on the fly.

It’s currently in public beta, and there are packages for Ubuntu, so I decided to try it out. I’ve created the Ansible role below to set up Unit on an Ubuntu 16.04 server or VM:

---
- name: Install keys
apt_key: url=http://nginx.org/keys/nginx_signing.key state=present
- name: Setup main repo
apt_repository: repo='deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx' state=present
- name: Setup source rep
apt_repository: repo='deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx' state=present
- name: Update system
apt: upgrade=full update_cache=yes
- name: Install dependencies
apt: name={{ item }} state=present
with_items:
- nginx
- unit
- golang
- php-dev
- php7.0-dev
- libphp-embed
- libphp7.0-embed
- python-dev
- python3
- python3-dev
- php7.0-cli
- php7.0-mcrypt
- php7.0-pgsql
- php7.0-sqlite3
- php7.0-opcache
- php7.0-curl
- php7.0-mbstring
- php7.0-dom
- php7.0-xml
- php7.0-zip
- php7.0-bcmath
- name: Copy over Nginx configuration
copy: src=nginx.conf dest=/etc/nginx/sites-available/default owner=root group=root mode=0644

Note the section that copies over the Nginx config file. Here is that file:

upstream unit_backend {
server 127.0.0.1:8300;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
fastcgi_param HTTP_PROXY "";
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
root /var/www/public;
index index.php index.html index.htm;
server_name server_domain_or_IP;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri /index.php =404;
proxy_pass http://unit_backend;
proxy_set_header Host $host;
}
}

This setup proxies all dynamic requests to the Unit backed in a similar fashion to how it would normally pass it to PHP-FPM.

There were still a few little issues. It doesn’t exactly help that the Nginx package provided with this repository isn’t quite the same as the one in Ubuntu by default - not only is it the unstable version, but it doesn’t set up the sites-available and sites-enabled folders, so I had to do that manually. I also had an issue with Systemd starting the process (at /run/control.unit.sock) with permissions that didn’t allow Nginx to access it. I’m not that familiar with Systemd so I wound up just setting the permissions of the file manually, but that doesn’t persist between restarts. I expect this issue isn’t that big a deal to someone more familiar with Systemd, but I haven’t been able to resolve it yet.

I decided to try it out with a Laravel application. I created a new Laravel app and set it up with the web root at /var/www. I then saved the following configuration for it as app.json:

{
"listeners": {
"*:8300": {
"application": "myapp"
}
},
"applications": {
"myapp": {
"type": "php",
"workers": 20,
"user": "www-data",
"group": "www-data",
"root": "/var/www/public",
"index": "index.php"
}
}
}

This is fairly basic, but a good example of how you configure an application with Unit. The listener section maps a port to an application, while the applications section defines an application called myapp. In this case, we specify that the type should be php. Note that each platform has slightly different options - for instance, the Python type doesn’t have the index or root options, instead having the path option, which specifies the path to the wsgi.py file.

I then ran the following command to upload the file:

$ curl -X PUT -d @app.json --unix-socket /run/control.unit.sock http://localhost

Note that we send it direct to the Unix socket file - this way we don’t have to expose the API to the outside. After this was done, the Laravel app began working as expected.

We can then make a GET request to view the configured applications:

$ curl --unix-socket /run/control.unit.sock http://localhost/
{
"listeners": {
"*:8300": {
"application": "saas"
}
},
"applications": {
"saas": {
"type": "php",
"workers": 20,
"user": "www-data",
"group": "www-data",
"root": "/var/www/public",
"index": "index.php"
}
}
}

It’s also possible to update and delete existing applications via the API using PUT and DELETE requests.

Final thoughts

This is way too early to be seriously considering using Unit in production. It’s only just been released as a public beta, and it’s a bit fiddly to set up. However, it has an enormous amount of promise.

One thing I can’t really see right now is whether it’s possible to use a virtualenv with it for Python applications. In the Python community it’s standard practice to use Virtualenv to isolate the dependencies for individual applications, and it’s not clear how I’d be able to go about using this, if it is possible. For deploying Python applications, lack of virtualenv support would be a deal breaker, and I hope this gets clarified soon.

I’d also be curious to see benchmarks of how it compares to something like PHP-FPM. It’s quite possible that it may be less performant than other solutions. However, I will keep a close eye on this in future.


A generic PHP SMS library

$
0
0

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.

Simple fuzzy search with Laravel and PostgreSQL

$
0
0

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.

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.

Using phpiredis with Laravel

$
0
0

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.

An Azure Filesystem integration for Laravel

$
0
0

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.

Catching debug statements in PHP

$
0
0

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.

Viewing all 158 articles
Browse latest View live