The Journey to ISO27001 and ISO9001 : part 1

Over a series of posts, I’ll attempt to document how I managed to get our development workflow independently audited to ISO27001 (Information Security) and ISO9001 (Quality).

To appreciate the scale of the task, I have to cast my mind back to 2011. I was looking for a new job and saw a role as PHP developer advertised. At the time I was nothing more than a bedroom PHP coder, I’d written a few simple web applications using PHP but these were just for fun and certainly nothing to be proud of. In my previous role, I done a bit of web programming using Visual Basic, but I was certainly no expert.

Anyway, I was invited for interview and a coding test. I felt rather out of my depth, seeing professional PHP developers working on applications that a multi-million pound business depended on.

The coding test made me shudder – not because I couldn’t do it, but because of what I was asked to do. I was given a page of tasks to perform and a laptop. I was told to write the code and then to FTP the code to the company’s one and only production server, where I could examine its outputs. So, here I was, someone with no professional PHP experience been asked to write code on the hoof and then upload it to a production server to see if it worked. One of the tests also involved running MySQL queries against a production database.

Anyway, I got the job and started on the long journey to getting the web development processes up to the standards required by ISO9001 and ISO27001.

It became apparent early on that the workflow adopted by the team was to edit code on the production server and see what happened. There was no local development environments on the developers’ PCs, and no test environment. I lost count of the number of times someone tried running an experimental query and we had to then call Rackspace and ask them to stop it running as it was killing the database server.

The first task was to have a dedicated and isolated test environment set up. An old PC was found in the server room and was quickly turned into a basic Centos LAMP stack. Unfortunately, all of the production code had been written so that it only could run on the production server – just copying the production code onto the test server did not work. We had to spend ages setting up reverse proxies as well as changing code so that it was not coupled to the environment where it would run.

We also installed XAMPP on all the developers’ PCs so they could at least run code locally before it reached production.

In summary – production servers are sacred, and cannot be used for testing anything. Get a test environment!

Top Ten Behat Tip

Using Behat for BDD with Symfony is always a good idea. Here are my top ten hints for getting the most out of this useful tool;

  1. The feature files should be understandable by a non-techical audience – so don’t use IDs of form elements and such like. Mink can find input boxes by label, name or placeholder. Every input on the web page must have something that identifies its function to the user so use that. What is easier to understand; When I enter “Fred” for “Username” – or – When I enter “Fred” for “#uname”
  2. Use the Behatch library to get step definitions that might be useful (
  3. Tag the scenarios to group them into sensible sub-sets. Such as “@security”, “@routing”, “@profile” – then you can just run a subset of the tests if you need to
  4. Ensure the database is in a known state before the tests start to run. This is probably the most important tip – you and your tests need to know exactly what is in the database before the tests start. There is a useful Doctrine class called the Purger which can empty database tables between tests.
  5. The Behat tests should only test outcomes that a regular user could verify, so try to avoid testing to see if an entity is updated in the database for example. If a user updates an entity for example, there should be some way in the user interface for them to verify that the change has been made – not by poking around in the  database.
  6. The cheat sheet is useful – if you’re old school, print it out and keep it next to you. Otherwise, open it in your second screen
  7. If you must insist on checking in the database for a property of an entity, bear in mind that Doctrine might have cached the entity. So, your Feature Context might have loaded the entity from the database and, in running the test, Symfony might have modified the entity and saved the change to the database. If the test then tries to verify that an attribute has changed, its default position is to check the cached version of the entity – which, of course, will be exactly the same as it was when it loaded before the test ran. You’ll have to refresh the entity before doing the assertion – this forces Doctrine to reload the entity and to ignore any cached version.
  8. Make sure each Scenario has a unique and descriptive description – so when it fails in Jenkins you know where to start looking
  9. Don’t assume that the features will be tested in a certain order – so if a test changes something in the database, another test further down the line might fail as it could be assuming that the database is in another state (see 4)
  10. Don’t waste time writing steps that already exist – don’t write steps like “I should see the text” – use “I should see” – a built-in step from Mink. do “behat -dl” to see all the steps that are already written and available to use.

Cross Browser Functionality – how our Government does it

Anyone who works in the field of web-development will be well aware of the problems of the inconsistencies between the ways different browsers work. All too often, we find ourselves saying such things as “Well, it worked fine in Firefox/Chrome/Opera, why won’t it work in Internet Explorer 6/7/8/9/10”.

The latest issue of Private Eye magazine led me to this site; – an online portal for claiming Disability Living Allowance. Obviously, the current Government is committed to making it as hard as possible for anyone to claim any sort of benefit, but this site takes it a little too far;

Operating systems and browsers

The service does not work properly with Macs or other Unix-based systems even though you may be able to input information.

You are likely to have problems if you use Internet Explorer 7, 8, 9 and 10, Windows Vista or a smartphone. Clearing temporary internet files may help but you may wish to claim in another way.

There is also a high risk that if you use browsers not listed below, including Chrome, Safari or Firefox, the service will not display all the questions you need to answer. This is likely to prevent you from successfully completing or submitting the form. You may wish to claim in another way.

In short – you can only claim for this benefit on-line if you haven’t updated your computer or browser for ten years.

Using multiple entity managers in Symfony2

When using a single Symfony2 install to manage multiple web-apps, each with its own database, you can set up multiple entity managers and tell each bundle which manager to use.

One thing that stumped me, and has seemed to stump others judging by a quick Google, is how to inject a non-default entity manager into a service.

Turns out, the way to do it use [name]_entity_manager, instead of entity_manager in the arguments line of services.yml. So, if you have an entity manager called ‘other’, use this:

         class: Me\MyBundle\Form\Selector\ProjectSelectorType
         arguments: ["@doctrine.orm.other_entity_manager"]


Using PHPass with CodeIgniter

Storing passwords in a database is a necessary evil for most web-applications. No-one in their right mind would save plain-text passwords, and MD5 hashes aren’t much better. A useful library called PHPass exists to hash passwords in a more secure manner. Using this in CodeIgniter should be straightfoward, but it is a bit fiddly.

Using third-party libraries in CodeIgniter is simple – just make sure the class you wish to use is the appliction/libraries directory and then use


But, when CodeIgniter loads the library, it also instantiates an object using the class definition in the library file. If the class constructor requires some parameters, you can pass them by adding an array to the $this->load->library() function;


In this case, $params must be an array.

The PHPass class definition, however, requires two parameters. So, the easy way round this is to adapt the PHPass class constructor to accept an array rather than two single parameters;

function PasswordHash($params)
        $iteration_count_log2 = $params[0];
        $portable_hashes = $params[1];
        $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

        if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
            $iteration_count_log2 = 8;
        $this->iteration_count_log2 = $iteration_count_log2;

        $this->portable_hashes = $portable_hashes;

        $this->random_state = microtime();
        if (function_exists('getmypid'))
            $this->random_state .= getmypid();

Football Results Database – part 1

In order to store the results of the NFL games, we’re going to need some tables. My initial design is as follows;

  • a table to store details of the teams in the league. For simplicity, we’ll assume that we only want to store results for a single season (for now), so the teams won’t change divisions. The ‘teams’ table just needs to store a ‘short-name’ for each team (which might as well act as the Primary Key for the table), the full name of the team, which conference it plays in, and which division. That should do for now.
  • a table to store details of the two Conferences. We might as well use AFC and NFC as the Primary Key for this table
  • a table to store details of each game; the week number, the date, maybe the location (maybe we’ll add this later) and whether the game went into overtime
  • a table to sore the results. a result will take up two rows of the table, one row for each team. I’ve added a ‘home’ field, which will be 1 if that row contains the score of the home team.

So, here is my basic table schema for MySQL;

CREATE TABLE `conferences` (
  `short` char(3) NOT NULL,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`short`)
insert  into conferences values 
('AFC', 'American Football Conference'), 
('NFC', 'National Football Conference');

CREATE TABLE `games` (
  `game_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `week` int(11) NOT NULL,
  `year` int(11) NOT NULL,
  `date` date NOT NULL,
  `overtime` tinyint(4) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`game_id`)

CREATE TABLE `results` (
  `game_id` int(11) unsigned NOT NULL,
  `team` char(2) NOT NULL,
  `points` int(11) NOT NULL,
  `home` tinyint(4) NOT NULL DEFAULT '0',
  KEY `game_id` (`game_id`),
  KEY `team_fk` (`team`),

CREATE TABLE `teams` (
  `short` char(2) NOT NULL,
  `name` varchar(100) NOT NULL,
  `conference` char(3) NOT NULL,
  `division` varchar(5) NOT NULL,
  PRIMARY KEY (`short`),
  KEY `conf` (`conference`) USING BTREE,

('AC', 'Arizona Cardinals', 'NFC', 'West'), 
('AF', 'Atlanta Falcons', 'NFC', 'South'), 
('BB', 'Buffalo Bills', 'AFC', 'East'), 
('BR', 'Baltimore Ravens', 'AFC', 'North'), 
('CB', 'Chicago Bears', 'NFC', 'North'), 
('CL', 'Cleveland Browns', 'AFC', 'North'), 
('CN', 'Cincinnati Bengals', 'AFC', 'North'), 
('CP', 'Carolina Panthers', 'NFC', 'South'), 
('DB', 'Denver Broncos', 'AFC', 'West'), 
('DC', 'Dallas Cowboys', 'NFC', 'East'), 
('DL', 'Detroit Lions', 'NFC', 'North'), 
('GB', 'Green Bay Packers', 'NFC', 'North'), 
('HT', 'Houston Texans', 'AFC', 'South'), 
('IC', 'Indianapolis Colts', 'AFC', 'South'), 
('JJ', 'Jacksonville Jaguars', 'AFC', 'South'), 
('KC', 'Kansas City Chiefs', 'AFC', 'West'), 
('MD', 'Miami Dolphins', 'AFC', 'East'), 
('MV', 'Minnesota Vikings', 'NFC', 'North'), 
('NE', 'New England Patriots', 'AFC', 'East'), 
('NG', 'New York Giants', 'NFC', 'East'), 
('NJ', 'New York Jets', 'AFC', 'East'), 
('NO', 'New Orleans Saints', 'NFC', 'South'), 
('OR', 'Oakland Raiders', 'AFC', 'West'), 
('PE', 'Philadelphia Eagles', 'NFC', 'East'), 
('PS', 'Pittsburg Steelers', 'AFC', 'North'), 
('SD', 'San Diego Chargers', 'AFC', 'West'), 
('SF', 'San Francisco 49ers', 'NFC', 'West'), 
('SL', 'St. Louise Rams', 'NFC', 'West'), 
('SS', 'Seattle Seahawks', 'NFC', 'West'), 
('TB', 'Tampa Bay Buccaneers', 'NFC', 'South'), 
('TT', 'Tennessee Titans', 'AFC', 'South'), 
('WR', 'Washington Redskins', 'NFC', 'East');