When updating Laravel to a major version, it might be straightforward but more often than not, it is not. I personally always struggle with to get it done quickly and usually spend far more time on the task, than expected. This is especially true in larger projects where there might me multiple dependencies, as each other of them can prolong the upgrade process.

Most of the time the error message boils down to something similar to the following excerpt:

Running composer update laravel/framework --with-all-dependencies
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Root composer.json requires laravel/framework *, found laravel/framework[x.x.x] but these were not loaded, likely because it conflicts with another require.

The error is not very verbose, and in Composer before version 2.0, it was probably even less so.

Dealing with dependencies

The above error is usually longer and contains some hints about the conflicting package, but usually somewhere deeper. Pay attention to its entire output. Most probably, the currently locked version of some package is not available for the Laravel version you are trying to install.

To find out what version is actually installed, what I usually do is:

composer show | grep <package>

What I do next is I visit https://packagist.org/ and look for the lowest version of the conflicting package that supports what I am trying to install. Let's illustrate the above with the laravel-fractal package. Example for the current installed version:

Version 5.8.1 of laravel-fractal supports lower version of illuminate

The next major version correctly supports our target:

Version 6.0.0 of laravel-fractal supports higher version of illuminate

Why lowest, you might ask? Because sometimes, in larger projects, things are not as bleeding edge as they could be, and in the meantime, the newest release version of your conflicting package might be also out of bounds, but from the another side of the spectrum.

Note: Please check also php version required by the package, as this is also a common source of package upgrade conflicts, reported by composer.

No package version supports my target?

But what if no package version shown on packagist has constrains that fits my target requirements? Well, that happens too. In that case the update is not that straightforward. What I usually do is to replace the remote package with the local, forked one, where I manually update nothing else but requirements in composer of said package.

This is however usually a major pain and outside of the scope of this article, but will keep you going if done right. The next useful thing I do is to actually open a Pull Request upstream with just the single change in the composer.json I made. Keep in mind it is well worth to include updates to README.md in that Pull Request too. If nothing else needs changing, the maintainer might accept the request so soon that you might not even need to force composer to accept the local package in the first place (depending on how time constrained you are).

How to upgrade

Once I know the target version I want the package to be at, the next major problem is that usually multiple packages need to be upgraded at once. They simply do not really want to be upgraded one-by-one, due to tight constrains, partly illustrated in the screenshots above.

What I usually do is to set target versions into composer for each individual package first without updating like this:

composer require --no-update spatie/laravel-fractal "^6"
composer require --no-update laravel/framework "^9"

Note: the double-quotes around version number are not even needed on bash, however, depending on configuration of your shell, they might be necessary. For instance, zsh can be configured via setopt EXTENDED_GLOB to treat the caret ^ character differently.

Then I try to update the update the whole thing:

composer update --with-all-dependencies spatie/laravel-fractal laravel/framework

Such command might pass or might output conflicts, but this this way it is easy to iterate in small steps, methodically adjusting dependencies to match the desired output.

What about dev dependencies?

Sometimes, during such a major upgrade, I need to adjust the locked dev dependencies versions as well. Dev dependencies are located under require-dev in composer.json. This might prove a little bit tricky. For example:

phpunit/phpunit is currently present in the require-dev key and you ran the command without the --dev flag, which will move it to the require key.
Do you want to move this requirement? [no]? yes
./composer.json has been updated

As you see, I typed yes into prompt to confirm. It is easier to move some of the dev dependencies into normal dependencies, for the sake of the upgrade. Now the project could be upgraded like this:

composer update --with-all-dependencies spatie/laravel-fractal laravel/framework phpunit/phpunit

After the upgrade, I can move all the dev dependencies back like this:

composer reqiure --dev phpunit/phpunit

Confirm by typing yes again.

Note: One should avoid updating composer.json manually.

The reason for this back-and-forth package shuffling is that Composer seems to either update dependencies or the dev dependencies, not both. Enjoy!