Do you have tests for all of it?
How to upgrade very old Laravel gradually
The company I'm working for have a very outdated Laravel 3 application still in production. They want to upgrade Laravel to the latest version.
They use a normal release process where all devs create feature branches => pull requests => reviews => merge into main => automatic deployment.
I'm seeking advice on how to gradually perform this upgrade. It's not possible to put everything else on hold and just focus on upgrading Laravel. The team still has to work on new features for the existing app to some degree.
Our first idea was to create a new Laravel installation from scratch hosted in a subfolder of the repo and then gradually move logic to the new app. This would allow us to continue pulling in changes from the main branch but would result in the mother of all merges at some point.
Our second idea was to have a new Laravel installation "in front of" the old one and have a wildcard route at the bottom of web.php that somehow forwards the request to the old app if the new one hasn't implemented it yet. We don't know if that's at all possible.
We are extremely thankful for any ideas on how to tackle this problem.
I'm afraid there's zero tests (I know). This company is on a journey to modernize their entire dev team and their overall processes working with development.
@glennutter Ouch that makes it a lot harder. My suggestion was to make an upgrade branch. Then upgrade 1 version fully. Then rebase from main, and update the changes done since the branch was made. I did this myself on a quite huge project. But there was tests for almost everything, so I was sure it all worked before merging back into master.
@Sinnbeck Yes no tests makes it a bit sketchy since it's a quite large application. That's why the team wants to gradually ease into the upgrade so changes can be somewhat scoped.
@glennutter not sure of the best path then. And did you say laravel 3? I don't even think there exists an upgrade guide for laravel 3 to 4
@Sinnbeck Yes Laravel 3, which means that stuff like LaravelShift isn't an option. We haven't found any official documentation, just a couple of blog posts talking about upgrading smaller applications to 4.
@glennutter Ok. Maybe the old laravel 3 can be the one in a sub folder? I dont exactly know how it works in laravel 3. And can you run php 8 in laravel 3 (I would assume not)
Unless that is possible I dont think there is any way around my original suggestion of upgrading everything in a branch, and then later merge the newest back in
Our second idea was to have a new Laravel installation "in front of" the old one and have a wildcard route at the bottom of web.php that somehow forwards the request to the old app if the new one hasn't implemented it yet. We don't know if that's at all possible.
This is essentially what we do as we still have some very old PHP monolithic files that need to be cleaned up (it's a work in progress).
We have a catchall route that fowards request to a LegacyController that then handles the legacy code.
if (is_legacy_request()) {
Route::any('{catchall}', [LegacyController::class, 'handle'])
->where('catchall', '^(?!nova.*$).*')
->name('catchall');
}
@cwhite interesting. How do you handle this with two different laravel versions? Multiple composer files and vendor directories?
@cwhite I think a automated upgrade might be worth the money
@Sinnbeck, so it's just a single Laravel install (and we're actually on 9.x) so pretty up-to-date. The legacy stuff is still served on .php pages through the LegacyController.
@Tray2, thanks, I've looked at that, but the legacy stuff is ancient monolithic php pages so Shift isn't going to be able to magically MVC those pages
@cwhite has this conversation been hijacked?
@cwhite The catch all route is the easy part, but how would one pass the request over to the other laravel application? Can't simply require index.php because it will throw an error complaining that constants like LARAVEL_START has already been defined etc because the "outer" Laravel application is still up and running.
@cwhite Dont think this would work either as they are going to have to require two vendor folders (two laravel versions) and perhaps even two different php versions
@Sinnbeck The idea was to go for Laravel 8 that has support for PHP 7.4 which is the current version the system is using. Laravel 3 doesn't use composer, so there's no vendor folder in the old app. The Laravel core files is included in the repo of the entire app.
@glennutter Ah ok that might make it a bit easier then! Does it have a public folder or is the index.php just in the root ?
@Sinnbeck I does have a public folder which contains index.php
@glennutter Ok. Might need a bit of "fixes" then. I dont have a laravel 3 install so I cannot really test. But the way I would go about it then, would be to copy the laravel 3 app into a laravel 8 app in a subfolder. Then try and wire it up like @cwhite suggested. Might take a bit of rewriting of the old laravel 3 core to get working, but as it isnt in vendor, that should be possible.
I dont suppose you have the installation files for laravel 3 lying around ?
@Sinnbeck I did a poor attempt to do this last night.
I added this catch all route at the bottom of web.php in Laravel 8: Route::any('{path}', Laravel3Controller::class)->where('path', '.*');
And in the Laravel3Controller I added an __invoke() method to catch the request. But I'm not sure on how to fire up Laravel 3 from here. I required it's public/index.php but got lots of errors about constants like LARAVEL_START already being defined etc.
Is there a better way to fire up Laravel 3 from my Laravel3Controller, or should I follow down the path of tweaking it until it starts?
@glennutter Thats the problem, I have never seen the laravel 3 codebase, so I am having a hard time giving any ideas on how to fix it. I dont suppose you have the original installation files for it ?
@Sinnbeck I understand and thank you for your input. I don't have the original files, but I can strip out business logic from the existing app and provide you with it?
@glennutter It would be fun to take a look. But just be sure to leave in a route I can actually test (a page that says "hello world" is fine) :)
@Sinnbeck No problem. I'll get back to you in a while.
@Sinnbeck I've got a stripped down Laravel 3 ready. How would you like access to it?
@glennutter can you either provide a download link or even better put it on github?
@Sinnbeck A co-worker has invited you to a repo
@glennutter Ill give it a shot. No promises though as I dont know laravel 3 at all :)
And in the Laravel3Controller I added an __invoke() method to catch the request. But I'm not sure on how to fire up Laravel 3 from here. I required it's public/index.php but got lots of errors about constants like LARAVEL_START already being defined etc.
I would think you could add most of the stuff in the Laravel3 index.php into the Laravel3Controller::__invoke() method, then you have no need for the Laravel3 index.php file.
Eg. (note that I'm using the Laravel 9 index.php as an example because I don't know how Laravel 3 operates)
<?php
namespace <>;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class Laravel3Controller extends Controller
{
public function __invoke(Request $request): void
{
$kernel = $app->make(Kernel::class);
$response = $kernel->handle($request)->send();
$kernel->terminate($request, $response);
}
}
@Sinnbeck Much obliged, sir!
@glennutter OK have a gotten a small start. But the big issue is that there are naming collssions in laravels helpers.php. They exist in both, and the names clash: So the laravel3 version needs to have all of its helpers renamed :/
@cwhite This is what index.php looks like from Laravel 3:
define('LARAVEL_START', microtime(true));
$web = true;
require '../paths.php';
unset($web);
require path('sys').'laravel.php';
Pulling that into the Laravel3Controller produces errors that constants and functions has already been defined.
@glennutter yeah those was easy to fix. The big one is renaming all helper functions
Ok here is what I did to get it started
Put all files in /laravel3
Make a new file in that folder named app.php
$web = true;
require 'paths.php';
unset($web);
require path('sys').'laravel.php';
Then in a legacy controller
require base_path('laravel3/app.php');
Ok easy fix. Add namespace Laravel; at the top of the helpers.php. And next remove most aliases in config/app.php
Got it working 👍 i removed all aliases and just added the missing use function Laravel/array_get; where it complained
@Sinnbeck You are amazing. Removed Aliases from Laravel 8 or 3? When I remove from 3 I get:
Undefined class constant 'loader'
From:
Event::listen(View::loader, function ($bundle, $view) {
in start.php
@glennutter no from laravel 8. They will break laravel 3. So /config/app.php
@Sinnbeck You are a genius sir. Thank you VERY much for your help. I had to add some null checks in Cookie::get and somewhere else but now the application boots. THANK YOU!
@glennutter ah yes forgot about that one. Did it in the train on my way home, so typed the explanation on my phone 😅
Happy to have helped 👍
@Tray2 I was about to suggest this myself as it takes aot o tbe pain away from upgrading legacy code. My approach would be coding up tests for the current working code then upgrade using Laravel Shift and rerun the tests, fixing any issue before merging and deploying to production.
@glennutter no matter how you proceed back up your data first because if you go doing migrations wrong you could lose all your data, it's happened to I don't know how many people on this forum.
You should be able to piggyback an old application inside a new application working on changing code over a little at a time, I have done this in the past.
@jlrdw The old Laravel 3 app isn't using migrations, that's something we're looking to add to the project once it's been upgraded.
Not sure what you mean by piggybacking, but a lot of the written code refers to facades and methods on facades that are no longer available in newer Laravel, so it's impossible to just lift in the "business logic" into a newer app without rewriting large chunks of it. That's why we - in a dream world - would be able to run both instances of Laravel at the same time for a while.
I did it in the last year (from 3 to 8). I started with this "guide" https://yetanotherprogrammingblog.com/content/upgrading-from-laravel-3-to-laravel-4 and then I upgraded each new version at the time
It will be hard to do, but still it is possible.
Every Laravel version has an "Upgrade guide" chapter, so you can check, if you miss any changes. The best way to do this is to upgrade from version to version (3 -> 3.1 -> 3.2 -> 4.0 -> 4.1 -> 4.2 -> 5.0 -> ... -> 8 -> 9 -> 10) to update the project according to all breaking changes and check if everything is still working.
The hardest thing will be the PHP version update: the latest Laravel 10 version requires PHP 8.1. So, you will need to update the whole application to replace all deprecations and outdated code (from PHP 5.3 to 8.1)
Or you can just rewrite your project on a latest version almost from scratch.
Please or to participate in this conversation.