Legacy to Laravel: How to Modernize an Aging PHP Application

The folks at Tighten:

In this post, I’ll present some strategies we use at Tighten to convert legacy apps to Laravel gradually over time, without needing to perform a full rewrite. Whether you’re upgrading from an old framework or moving to a framework for the first time, following these steps will let you start benefitting from all that Laravel has to offer right away.

What they basically do is set up a new Laravel project and have it catch the parts that they changed/added. Using a catchall rule, all other requests are routed to the legacy code. Clever.

Route::any('{path}', 'LegacyController@index')->where('path', '.*');

From then on they start migrating to Eloquent models, introduce some helper functions, etc.

Legacy to Laravel: How to Modernize an Aging PHP Application →

PHP: Convert a Geolocation (Latitude / Longitude Coordinates) to a Timezone identifier

Part of a PHP project I’m working contains a list of sites/buildings. For each site/building we monitor some data, for example its energy usage.

We decided that we wanted to generate a daily/weekly/monthly reports of the data, by aggregating the datapoints. As our sites/buildings are spread across the globe – and thus timezones – we can’t simply select data between 00:00:00 UTC and 23:59:59 UTC but have to use its geographical location’s “day window” to do our calculations.

Unfortunately we don’t didn’t store the timezone for a site/building, but since we do keep track of its geographical location – using a WGS84 latitude-longitude pair – it should be possible to derive its timezone, right?

Right! On StackOverflow I found this little snippet that does the job:

function get_nearest_timezone($cur_lat, $cur_long, $country_code = '') {
    $timezone_ids = ($country_code) ? DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $country_code)
                                    : DateTimeZone::listIdentifiers();

    if($timezone_ids && is_array($timezone_ids) && isset($timezone_ids[0])) {

        $time_zone = '';
        $tz_distance = 0;

        //only one identifier?
        if (count($timezone_ids) == 1) {
            $time_zone = $timezone_ids[0];
        } else {

            foreach($timezone_ids as $timezone_id) {
                $timezone = new DateTimeZone($timezone_id);
                $location = $timezone->getLocation();
                $tz_lat   = $location['latitude'];
                $tz_long  = $location['longitude'];

                $theta    = $cur_long - $tz_long;
                $distance = (sin(deg2rad($cur_lat)) * sin(deg2rad($tz_lat))) 
                + (cos(deg2rad($cur_lat)) * cos(deg2rad($tz_lat)) * cos(deg2rad($theta)));
                $distance = acos($distance);
                $distance = abs(rad2deg($distance));
                // echo '<br />'.$timezone_id.' '.$distance; 

                if (!$time_zone || $tz_distance > $distance) {
                    $time_zone   = $timezone_id;
                    $tz_distance = $distance;
                } 

            }
        }
        return  $time_zone;
    }
    return 'unknown';
}

Usage is as follows:

// Timezone for one NY coordinate
echo get_nearest_timezone(40.772222,-74.164581);
// ~> America/New_York

// Timezone for one Belgian coordinate
echo get_nearest_timezone(51.0162167, 3.7338451);
// ~> Europe/Brussels

// More faster and accurate if you can pass the country code 
echo get_nearest_timezone(40.772222, -74.164581, 'US');
// ~> America/New_York

With this timezone identifier now being available, we can include it in our queries and generate our daily/weekly/monthly reports 🙂

🍻 Here’s to copying-and-pasting from StackOverflow!

On a related note: Falsehoods programmers believe about time and time zones is worth a read, especially if you’ve already dealt with time and timezones.

PHP 8 in 8 Code Blocks

Brent has been following the PHP releases and featured up close. In this post he highlights 8 of the features that will make it into PHP 8

  1. Attributes (aka Annotations)
  2. Union types
  3. The static return type
  4. The just-in-time Compiler
  5. throw in Expressions
  6. Non-capturing catches
  7. Trailing commas in parameter lists
  8. New str_* functions

As a bonus he also adds the new Stringable interface and the ability to call ::class directly on objects.

PHP 8 in 8 Code Blocks →

PHPUnit: A Security Risk?

The author of PHPUnit was a bit surprised when he received a mail stating that PHPUnit was a security risk and hackers could remotely execute PHP code through a file named eval-stdin.php that ships used to ship with PHPUnit.

// eval-stdin.php
eval ('?>'. \file_get_contents('php://input'));

Even though the eval-stdin.php file itself indeed was vulnerable, it never should have been actively exploitable because:

  • PHPUnit is a dev dependency, and should never be installed in production.
  • One should never make their vendor folder publicly accessible. If it is placed in the wwwroot, use .htaccess or the like to prevent direct access to it.

Eventually a fix landed in PHPUnit, accompanied by this nice commit message:

This check should not be required ... yet here it is.

If you upload PHPUnit to a production webserver then your deployment process is broken.

If your vendor/ directory is publicly accessible on your webserver then your deployment process is broken.

*sigh*

PHPUnit: A Security Risk? →

Sharing PHP-CS-Fixer Rules Across Projects and Teams

Tim MacDonald shares a way to sharing PHP-CS-Fixer rules across your projects. It involves in setting up on repo/package that contains the rules and a little helper class that takes in a PhpCsFixer\Finder instance (along with optional extra rules).

<?php

namespace TiMacDonald;

use PhpCsFixer\Config;
use PhpCsFixer\Finder; 

function styles(Finder $finder, array $rules = []): Config {
  $rules = array_merge(require __DIR__.'/rules.php', $rules);

  return Config::create()
    ->setFinder($finder)
    ->setRiskyAllowed(true)
    ->setRules($rules);
}

Once required in your actual project – using Composer – you have that project’s .php_cs.dist call the styles function:

<?php

$finder = PhpCsFixer\Finder::create()
  ->in([
    __DIR__.'/app',
    __DIR__.'/config',
    __DIR__.'/database',
    __DIR__.'/routes',
    __DIR__.'/tests',
  ]);

return TiMacDonald\styles($finder);

Nice!

Sharing PHP-CS-Fixer Rules Across Projects and Teams →

Composer Inline Aliases: Fake a PHP Package Version by Aliasing a Specific Commit to it

Great tip by Mattias:

You can alias directly when requiring a package:

composer require monolog/monolog:"dev-bugfix as 1.0.x-dev"

Surely comes in handy when locally adjusting a library. More info on Inline Aliases in the Composer Documentation.

Laravel Valet not picking up php.ini changes, a fix

If you have Laravel Valet not picking up php.ini changes, try this:

Demystifying Dependency Injection Containers

Video from Kai Sassnowski’s talk at Laracon EU 2019:

The goal of this talk is to explain how dependency containers work by building our own. We start out by building the simplest DI container possible to demonstrate the underlying concept. Most people will be surprised about how little code this actually takes (3-4 effective lines of code). From there, we gradually add more sophisticated features like autowiring to create a container that more closely resembles what we are familiar with from Laravel. After listening to this talk, you will know how DI containers work at their core. You will also know how autowiring works and why it doesn’t work under certain circumstances.

PHP Performance Tip: Use fully-qualified function calls when working with namespaces

TIL: When working with namespaced files in PHP it’s a huge performance win when using fully-qualified function calls.

~

If you’re calling is_null in your code, PHP will first check for the function’s existence in the current namespace. If not found there, it will look for the function in the global namespace. This extra check is quite a drag, as detailed in this issue for the SCSS PHP Project.

If you do want to target PHP’s built-in is_null (or any other global function), it’s more performant to refer to it using it’s fully-qualified name, e.g. \is_null

❌ Slow:
is_null();

✅ Fast:
\is_null();

Alternatively you can also import the function first through a use statement.

✅ Fast:
use function is_null;
is_null();

~

In the case of SCSS PHP the change from is_null to \is_null lead to a 28% speed increase!. If you’re looking for more benchmarks, Toon Verwerft has done some benching in the past.

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Via this tweet that sparked my quest into knowing more about this.

Laravel 6 From Scratch Free Video Course

In this series, step by step, I’ll show you how to build web applications with Laravel 6. We’ll start with the basics and incrementally dig deeper and deeper, as we review real-life examples. Once complete, you should have all the tools you need. Let’s get to work!

All 68 videos in this course, totalling 8:59:43 hrs of material, are free to watch.

Laravel 6 From Scratch →