Update path for Mezzio (composer.json)

Hi,

Just a question about how to manage dependencies versions in composer.json for Mezzio (when major version module released).

As a example a created a website ~4 years ago (zend expressive) and used the skeleton and migrate to mezzio later. Until now i updated sometime my composer dependencies via composer update and apply in my hosting with composer install. I see by example that laminas-form still in 2.17.1 (of course i have ^2.11 in composer.json).

So my question is what is the best way to be up to date with the latest versions (major) ? Do i have to change manually the version of each packages in composer.json ?

By example, with laminas-form i have 2.17.1 and it is not compatible with PHP 8.1. Do i have to replace laminas/laminas-form": “^2.11”, by laminas/laminas-form": “^3.1”, ?

Part of my composer.json:

...
"require": {
        "php": "^7.1",
        "roave/security-advisories": "dev-master",
        "laminas/laminas-component-installer": "^2.1",
        "laminas/laminas-config-aggregator": "^1.0",
        "laminas/laminas-diactoros": "^1.7.1",
        "mezzio/mezzio": "^3.0",
        "mezzio/mezzio-csrf": "^1.0",
        "mezzio/mezzio-fastroute": "^3.0",
        "mezzio/mezzio-helpers": "^5.0",
        "mezzio/mezzio-session-ext": "^1.0",
        "mezzio/mezzio-laminasviewrenderer": "^2.0",
        "laminas/laminas-form": "^2.11",
        "laminas/laminas-i18n": "^2.7",
        "laminas/laminas-mail": "^2.9",
        "laminas/laminas-servicemanager": "^3.3",
        "laminas/laminas-stdlib": "^3.1",
        "laminas/laminas-dependency-plugin": "^1.0"
    },
...

Thanks!

I wrote a small scripts that will

  1. read composer.json for required PHP version and all dependencies (dev and non-dev)
  2. delete composer.lock and vendor/
  3. require all dependencies while specifying required PHP version

That will essentially upgrade everything across all major versions. So it is very breaking, and it will modify your composer.* without any confirmation. But it should get the job done.

If you are unsure, you can comment out the execution of commands, near RUNNING, and just see what commands it would run instead.

Here it is:

#! /usr/bin/env php
<?php

class Updater
{
    private function toRemove(array $packages): string
    {
        $packages = $this->filterExcluded($packages);

        return $this->toArgs(array_keys($packages));
    }

    private function toRequire(array $packages): string
    {
        $packages = $this->filterExcluded($packages);

        // Keep @stable version
        $packageWithOptionalStable = [];
        foreach ($packages as $package => $version) {
            if ($version === '@stable') {
                $package = $package . ':' . $version;
            }

            $packageWithOptionalStable[] = $package;
        }

        return $this->toArgs($packageWithOptionalStable);
    }

    private function toArgs(array $args): string
    {
        $escaped = [];
        foreach ($args as $arg) {
            $escaped[] = escapeshellarg($arg);
        }

        return implode(' ', $escaped);
    }

    private function filterExcluded(array $packages): array
    {
        ksort($packages);

        return array_filter($packages, function (string $version, string $package) {
            $isPhp = preg_match('~^(php|ext)(-.*)?$~', $package);
            $isCrossMajor = strpos($version, '||') !== false;

            return !$isPhp && !$isCrossMajor;
        }, ARRAY_FILTER_USE_BOTH);
    }

    public function majorUpdate(): void
    {
        $data = json_decode(file_get_contents('composer.json'), true);
        $deps = $data['require'] ?? [];
        $devDeps = $data['require-dev'] ?? [];

        preg_match('~\d\.\d~', $data['require']['php'] ?? $data['require']['php-64bit'], $m);
        $php = 'php' . $m[0];
        $composerPath = trim(`which composer`);
        $composer = "$php $composerPath --ansi --no-interaction";

        $argsToRemove = $this->toRemove($deps);
        $devArgsToRemove = $this->toRemove($devDeps);
        $argsToRequire = $this->toRequire($deps);
        $devArgsToRequire = $this->toRequire($devDeps);

        $cmds = [];
        if ($argsToRemove) {
            $cmds[] = "$composer remove --no-update $argsToRemove";
        }
        if ($devArgsToRemove) {
            $cmds[] = "$composer remove --no-update --dev $devArgsToRemove";
        }

        $cmds[] = 'rm -rf composer.lock vendor/';
        $cmds[] = "$composer update";

        if ($argsToRequire) {
            $cmds[] = "$composer require $argsToRequire";
        }
        if ($devArgsToRequire) {
            $cmds[] = "$composer require --dev $devArgsToRequire";
        }

        echo 'COMMAND LIST:' . PHP_EOL;
        foreach ($cmds as $cmd) {
            echo $cmd . PHP_EOL;
        }

        echo PHP_EOL . 'RUNNING:' . PHP_EOL;
        foreach ($cmds as $cmd) {
            echo $cmd . PHP_EOL;

            $return = 0;
            passthru($cmd, $return);
            if ($return) {
                die($return);
            }
        }
    }
}

$updater = new Updater();
$updater->majorUpdate();