Migrating Legacy Web Applications to Laravel

I gave a talk on migrating legacy web applications to Laravel at last year’s Laravel Live UK conference in London. Those that rated it gave it high marks which is always brilliant feedback to receive.

2018 was Laravel Live UK’s inaugural conference and it was a packed house. They’ve just announced the dates for 2019 and I would strongly recommend attending for anyone using or interested in use Laravel.

Following the conference, I wrote up the talk as an article and it has just been published in the March 2019 edition of php[architect]. This is an excellent magazine which I’ve subscribed to for a few years now – the digital edition is very reasonable and comes as a DRM-free PDF onto an app on your phone/pad or downloaded to your computer.

As it happens, they chose this article as the teaser for this issue and so it is freely available online here and downloadable as a PDF here. But seriously, if you are a PHP developer, you need to subscribe to this magazine.

Lastly, if you are interested in the slide deck from the conference, you can download them here – but the article is a much better way to understand this material.

Switching Between PHP Versions on MacOS

When switching between different PHP projects (or different branches of one PHP project), you often need to switch PHP versions. Particularly as older libraries will not run on newer versions of PHP. After losing patience with how the otherwise excellent Homebrew handles this, I stumbled upon this super-useful tool: switch-php.

This seems to have been written for PHP developers and integrates brilliantly with Laravel Valet. Here’s an example of switching from PHP 7.3 to 7.2:

$ php -v 
PHP 7.3.0 (cli) (built: Dec  7 2018 11:00:11) ( NTS ) 
Copyright (c) 1997-2018 The PHP Group 

$ switch-php 7.2 
Valet stopped ✔ 
PHP switched ✔ 
Valet started ✔ 

You are now using PHP 7.2.13 

$ php -v 
PHP 7.2.13 (cli) (built: Dec  7 2018 10:41:23) ( NTS ) 
Copyright (c) 1997-2018 The PHP Group

UI Tests with Laravel Dusk for IXP Manager

We use standard PHPUnit tests for IXP Manager for some mission critical aspects. These take data from a test database filled with known sample data (representing a range of different member configurations). The tests then use this information to generate configurations and compare these against known-good configurations.

This way, we know that for a given set of data, we will get a predictable output so long as we haven’t accidentally broken anything in development.

But, as an end user, how do you know that what you stick in a web-based form gets put into the database correctly? And conversely, how do you know that form represents the database data correctly when editing?

This is an issue we ran into recently around some checkbox logic and a dropdown showing the wrong selected element. These issues are every bit as dangerous to mission critical elements as the output tests we do with PHPUnit.

To test the frontend, we turn to Laravel Duskan expressive, easy-to-use browser automation and testing API. What this actually means is that we can right code like this:

$this->browse( function( $browser ) use ( $user ) {
    $browser->visit('/login')
        ->type( 'username', $user->email )
        ->type( 'password', 'secret' )
        ->press('Login')
        ->assertPathIs('/home')
        ->assertSee( 'Welcome to IXP Manager ' . $user->name );

We have now added Dusk tests for UI elements that involve adding, editing and deleting all aspects of a member interface and all aspects of adding, editing and delete a router object. Here’s an example of the latter:

Laravel Dusk - Animated Gif Example

Doctrine2 with GROUP_CONCAT and non-related JOIN

Doctrine2 ORM is a fantastic and powerful object relational mapper (ORM) for PHP. We use it for IXP Manager to great effect and we only support MySQL so our hands are not tied to pure Doctrine2 DQL supported functions. We also use the excellent Laravel Doctrine project with the Berberlei extensions.

Sometimes time is against you as a developer and the documentation (and StackOverflow!) lacks the obvious solutions you need and you end up solving what could be a single elegant query very inefficiently in code with iterative database queries. Yuck. 

I spent a bit of time last night trying to unravel one very bad example of this where the solution would require DQL that could:

  1. group / concatenate multiple column results from a one-to-many relationship;
  2. join a table without a relationship;
  3. ensure the joining of the table without the relationship would not exclude results where the joint table had no matches;
  4. provide a default value for (3).

Each of these was solved as follows:

  1. via MySQL’s GROUP_CONCAT() aggregator. The specific example here is that when a MAC address associated with a virtual interface can be visible in multiple switch ports. We want to present the switch ports to the user and GROUP_CONCAT() allows us to aggregate these as a comma separated concatenated string (e.g. "Ethernet1,Ethernet8,Ethernet9").
  2. Normally with Doctrine2, all relationships would be well-defined with foreign keys. This is not always practical and sometimes we need to join tables on the result of some equality test. We can do this using a DQL construct such as: JOIN Entities\OUI o WITH SUBSTRING( m.mac, 1, 6 ) = o.oui.
  3. This is as simple as ensuring you LEFT JOIN.
  4. The COALESCE() function is used for this: COALESCE( o.organisation, 'Unknown' ) AS organisation.

We have not yet pushed the updated code into IXP Manager mainline but the above referenced function / code is not replaced with the DQL query:

SELECT m.mac AS mac, vi.id AS viid, m.id AS id, 
    m.firstseen AS firstseen, m.lastseen AS lastseen,  
    c.id AS customerid, c.abbreviatedName AS customer,
    s.name AS switchname, 
    GROUP_CONCAT( sp.name ) AS switchport, 
    GROUP_CONCAT( DISTINCT ipv4.address ) AS ip4, 
    GROUP_CONCAT( DISTINCT ipv6.address ) AS ip6,
    COALESCE( o.organisation, 'Unknown' ) AS organisation

FROM Entities\\MACAddress m
    JOIN m.VirtualInterface vi
    JOIN vi.VlanInterfaces vli
    LEFT JOIN vli.IPv4Address ipv4
    LEFT JOIN vli.IPv6Address ipv6
    JOIN vi.Customer c
    LEFT JOIN vi.PhysicalInterfaces pi
    LEFT JOIN pi.SwitchPort sp
    LEFT JOIN sp.Switcher s
    LEFT JOIN Entities\\OUI o WITH SUBSTRING( m.mac, 1, 6 ) = o.oui

GROUP BY m.mac, vi.id, m.id, m.firstseen, m.lastseen, 
    c.id, c.abbreviatedName, s.name, o.organisation

ORDER BY c.abbreviatedName ASC

 

PhpStorm and Xdebug – macOS / Homebrew

After many years of Sublime Text and, latterly, Atom, I’ve decided to give an integrated IDE another look – this time PhpStorm. I’ve always dropped them in the past as they tended to crash (hello Zend Studio) and were slow as hell (hello again Zend Studio). But so far so good – I’m only a couple days into an evaluation license but it’s fast (admittedly I have fast laptops – Intel i7’s with four cores, PCI SSD and 16GB RAM) and it’s yet to crash.

One of the key advantages of IDE’s is integrated debugging. This was ridiculously easy with PhpStorm. I use Homebrew for PHP:

$ brew ls --full-name
 homebrew/completions/composer-completion
 homebrew/php/composer
 homebrew/php/php71
 homebrew/php/php71-intl
 homebrew/php/php71-mcrypt
 homebrew/php/php71-xdebug

I’ve then configured xdebug as follows:

$ cat /usr/local/etc/php/7.1/conf.d/ext-xdebug.ini
[xdebug]
zend_extension="/usr/local/opt/php71-xdebug/xdebug.so"
xdebug.remote_enable=1
xdebug.remote_host=localhost
xdebug.remote_port=9001
xdebug.remote_autostart=1
xdebug.idekey=PHPSTORM

If you’re not using Laravel’s Valet for local development then you should check it out immediately: https://laravel.com/docs/5.3/valet. If you are using it, issue a valet restart.

Port 9001 was chosen above as Valet tends to use 9000 also. We now need to reconfigure PhpStorm to list on this port. Open preferences and type xdebug into the search box. Then find Languages & Frameworks -> PHP -> Debug on the left hand navigation pane and change the port to 9001.

That’s pretty much it for PhpStorm. They really mean zero-configuration debugging. When editing a project in the IDE, there’s a Start Listening for PHP Debug Connections toggle icon in the top left – it looks like a phone. Just turn it on.

The last thing we need to do is have an easy way to enable Xdebug when we want it when testing our applications in the browser. Chrome has a very useful plugin for this: Xdebug-helper. Just install it and edit its options and change the IDE form Eclipse to PhpStorm. You can now use this to start a debug session from within Chrome to your listening PhpStorm IDE.

Oh, just found this useful resource also covering similar topics with a CGI/CLI xdebug split.