Marathon Laravel Update from 5.6 to 9.0

I recently bit the bullet and decided to update a Laravel app that has been running on Laravel 5.6 since early 2018. Laravel 9 was released a week ago, and I started to feel left behind.

Updating from one major version to the next can be a challenge with any software. To make things even more challenging, Laravel has a strict PHP version dependency. My local PHP dev environment has been on php 8.1 for awhile, but Laravel 5.6 required PHP 7.x. This requirement even made it difficult to clone the repository to make quick fixes, which is the reason I decided to make the upgrade in the first place.

What I did wrong

I made a first attempt at a couple of weeks ago, trying to make the jump directly from 5.6 to 8.0. That turned out to be not such a great idea. The sheer number and depth of changes required made it impossible, and I gave up after an hour or so.

For my next (and successful) attempt, I decided to upgrade incrementally, from one major version to the next, and had much better luck at it.

^5.6 to ^6.0

This turned out to be the most challenging of the steps, as there were major changes between 5.6 and 6.0, particularly the removal of a number of helper functions: str_slug(), str_random(), etc. In Laravel 6, these are all available via Facade: Str::slug(), Str::random(), etc. As well, the Input facade was missing, replaced by Request. This involved a fair amount editing my own code, in addition to updating the framework itself.

As well, Composer 2 seemed to have difficulties with this update, and kept spewing errors after downloading dependencies. I ended up having to temporarily rollback to Composer 1 in order to successfully make the initial update:

composer self-update --1

Because both 5.6 and 6.0 had a dependency on PHP 7, I also had to use my secondary PHP binary when running both composer and artisan. I have the binary aliased, so it wasn’t too difficult, but it took some effort to keep remembering to use it:

php7 ~/bin/composer update

php7 artisan serve

All in all, the update from 5.6 to 5.8 to 6.0 took about three hours, including code editing and testing.

^6.0 to ^7.0

The next step wasn’t too difficult at all, and took less than an hour. Laravel 7 still depends on PHP ^7.2, so I continued to use Composer 1 and the php7 binary. The real challenge here was that Laravel 7.0 introduced the laravel/ui package, which needs to be installed before any of the earlier Auth functionality would work.

In addition, there were many changes in the Symfony components, which required editing multiple config files. That said, I didn’t have to edit any of my own code, which made things go quickly.

^7.0 to ^8.0

A very fast update with minimal changes required; this one took about 15 minutes. There are potentially some high impact changes, depending on the Laravel features you use: seeder and factories, events, and more. My app did not use those, however, so this update was easy. Although Laravel 8.0 reintroduces the App\Models folder and namespace, Laravel works perfectly without it; Laravel doesn’t really care where your model classes are stored. I found that Composer 2 worked flawlessly now, though I still had to use the php7 binary to meet Laravel 8’s PHP ^7.3 dependency.

^8.0 to ^9.0

For me, this was an easy update. Laravel 9 depends on PHP ^8.0.2, so this may increase the time of your update if you need to get PHP 8 installed and configured. Since I already had PHP 8.1.2 as my default dev environment, I could now stop specifying the PHP binary. There were some minor changes to config files, providers, but more because of third party dependencies than because of Laravel itself.

composer update

php artisan serve

Two tips for doing updates

So, after this marathon update that took, in total, about 5 hours, here are some tips I feel confident in passing on.

Keep Laravel updated!

Don’t do what I did. Update your Laravel apps as soon as possible when a new version appears. You’ll be more in touch with your code, and the update process will be much easier (and shorter).

RTFM!

Laravel has amazing documentation. Each version comes with its own Upgrade Guide. On early attempts, I simply read the intro to this guide where the actual changes to composer.json are outlined. I soon learned that this approach will only make life difficult. Read the entire Upgrade Guide, from start to finish, and make the required changes to composer.json, and to your code. It works.

Upgrade Incrementally

If you happen to miss an update or two, don’t try to jump a version while updating. This may work sometimes, but it will be more difficult, and you are likely to miss some important steps. These were my steps for each update.

  1. composer update – just to get your current install up to date with the latest Laravel minor versions and dependencies.

  2. Do the upgrade to the next major version, editing composer.json as the Upgrade Guide instructs, finally running composer update when you are ready.

  3. Read the entire Upgrade Guide for each update, and make all required changes to your code, including updating any third-party dependencies you have installed.

  4. Test. Test. Test.

And then, deployment

Overjoyed by my successful upgrade to Laravel 9, I’d made the mistake of ignoring the actual deployment environment! The app was hosted on a server used for a handful of other apps… all running perfectly and (it turned out) depending on PHP 7.x!

This required a rebuild of the server, moving from an Apache/mod_php setup to Apache with fastCGI and php-fpm, running multiple versions of PHP. But that’s another blog post.

p.s

On a side note, I updated two more projects the next day, one from Laravel ^6.0 to ^9.0, the other from Laravel 5.4 to ^9.0. Combined, they took less than half the time I spent on the marathon upgrade yesterday… I guess I figured out the process pretty well. The 5.4 to ^9.0 upgrade was a little hairy to start with; back then, the point upgrades were major, similar to the full number upgrades today.