If you are running a Laravel application in the 5.*'s, don't have any tests, are running a legacy application bootstrapped inside Laravel, or are staring in silent terror at just how long ago your server's PHP install stopped receiving security updates, this post is for you!
In this post, we'll be focusing on applications that are already running Laravel. If you're running a legacy PHP application that isn't bootstrapped inside of Laravel, take a look at another post on this blog titled Converting a Legacy App to Laravel; Andrew put together an incredible step-by-step guide for taking those first big steps.
It's easy to feel locked down and overwhelmed by the seemingly endless, overlapping responsibilities of updating an old application. So, let me tell you a few things, human-to-human:
It's easy to freeze or get lost in the endless bunny-trails of things that need to be done. Let's break down this big task into triage levels.
First, we want to audit your site to make sure you don't have any potential security issues. This is, obviously, more important than any other part of the upgrade process—missing features is one thing, but actual vulnerabilities trump anything else.
Perform an audit to determine which of your technologies have active vulnerability advisories, are scheduled to drop from security support, aren't receiving security fixes, or are EOL (end-of-life). Check out LaravelVersions and PHPReleases, and the GitHub Security Advisories for your project, to get started. Take that list and order them by severity. Pick the first item off of that list and start experimenting to discover the minimum number of technologies you need to update to support this change. Use this list to guide your priorities for the rest of this post.
Your goal: curate an informed set of instructions regarding the minimum number of upgrades needed to get your application out of the security danger zone. Move quickly and loosely to find and document blockers. Your focus right now should be documenting these blockers and security issues, not necessarily resolving them right at that moment. In most cases, I think this discovery and note-taking process should take a day or a few days, but not weeks.
If you're running a local or staging environment, try upgrading those first. Take notes about what it takes to run these upgrades, and your solutions to random gotchas as you go. You might find yourself in a situation where you need to restart the entire upgrade process, and it'll be a LOT easier if you document what worked.
Now that you have a list of blockers and the steps needed to upgrade, take some time to think about how you can split this work up into as many separate Pull Requests (PRs) as possible. Even if you opt to release them all at the same time, breaking your code up into individual chunks makes it easier to keep on track and avoid scope creep (and overwhelm).
In my experience, in most cases an application can be updated in two releases:
You may find that there is only a little extra work necessary to get your application up to current releases. In other cases, there may be a significant data or code overhaul needed before you can even start upgrading dependencies.
Once you settle on an intended scope and start working, you'll probably find yourself deep in a quagmire of errors. I find it helpful to reframe this as errors being my guide on the path forward, rather than the bane of my existence. 🙃
When you discover an error, don't be afraid to move forward and dig deep down the stack, or try approaching a specific error from a different direction; oftentimes there may only be a line or two of code that's blocking you from that version upgrade... the magic is in finding which lines.
In some situations, I've saved myself from needing to upgrade more technologies/versions by tweaking my application code. Consider moving some logic outside of a package when a specific method of that package calls a deprecated/end-of-lifed method (or one that requires a higher version of PHP). Build your own wrapper for the package so you can isolate the offending code from use. Sometimes you can extend a class of that package and leverage Laravel's IoC binding capabilities to load your patched version of that class instead of the one that throws the error. In really tight spots, you can use Composer and PSR-4 to override specific classes of a package with ones inside of your repository.
Every application can benefit from a balance of automated testing and manual QA. These processes ensure your users will have a consistent experience through the upgrade process. The more effective your automated testing, the less you'll need to rely on manual QA to ensure the application is behaving correctly at each step.
Balance the cost of whatever QA system you employ against the user confidence undermined by bugs introduced by your upgrade. If your app doesn't have meaningful test coverage, it's worth evaluating whether it's more affordable to pay for programmers to write more tests or for QAs to cover the majority of the app during the upgrade process. You also may find that your users are more or less tolerant of bugs depending on your industry and your relationship with them, so be sure to include that in your calculations.
If you've already developed a strong testing suite, the process of upgrading and then running your tests will likely give you an incredible amount of guidance in the form of all of those
Fs. Remember, errors are your guide! It might be a slog but at least it's structured; take a block of time and work through them, reminding yourself to take breaks when you tire or can't figure out how to solve a problem.
I love that once a test is written, I can use it to assert a specific behavior until the end of time (that is, when business logic changes and a new timeline is created in the multiverse). Writing a good test is an investment in your future happiness as a programmer. Computer cycles are cheap compared to human ones. Luckily, the computers I work with do not dread repetition.
If you've been spending a long time maintaining this legacy application, you may not be up-to-date on the best practices of writing new code in this tech stack. So, before you set out to write this feature again from scratch, it may be worth taking a pause to catch up on tutorials, write a similar project in a greenfield (brand new) app, or whatever else helps you get up your confidence.
One great part about this part of an upgrade: since you already know this feature will be a necessary part of your upgrade, consider doing this work in an earlier PR, before you start on your upgrade. You can write it, test it, QA it, and merge it, and now you've made the upgrade that much easier.
Most of the time we upgrade, we're primarily focusing on keeping the exact same existing feature working. But when something breaks during an upgrade, or even if nothing breaks but it takes time and money to upgrade with no visible progress, users and stakeholders can often get frustrated.
It can be challenging to empathize with humans when they submit a support request like "This thing has worked for me for the last 5 years and I logged in today to do some work with a deadline of yesterday and everything is broken!" They have no idea how much time and effort you put into that update that accidentally broke something for them, and you don't know how much pressure they're under.
Addressing this issue is easier for customer relations when you can point to improvements you released with that update. When you're upgrading, especially if you're having to re-write a feature from scratch as a part of the upgrade, add a few niceties everyone's been waiting on, or speed the page up a bit with youre new code. If users or stakeholders do come at you frustrated, be sure to point out the clearer design you made for a heavily trafficked page, or a tweak of application behaviour that saves the user a couple of clicks or keystrokes. Often in upgrades, you'll have made significant improvements in some page load times. Keep a list of improvements handy to reference in your customer service requests.
If you run a small application (under 25 routes), it may be easier and faster to spin up a fresh Laravel application and rebuild your application using the existing app as a reference.
I hope this post offers you a bit of guidance and structure to move towards starting to make the moves to get your application upgraded. It might be a monstrous task, but I believe in you!