Skip to content

[Matomo 6] Update composer dependencies to latest versions#24640

Draft
sgiehl wants to merge 38 commits into
dev-17598from
dev-20331
Draft

[Matomo 6] Update composer dependencies to latest versions#24640
sgiehl wants to merge 38 commits into
dev-17598from
dev-20331

Conversation

@sgiehl

@sgiehl sgiehl commented Jun 15, 2026

Copy link
Copy Markdown
Member

Builds on the PHP 8.1 minimum-version bump (#24638) to upgrade Matomo's composer dependencies to the latest versions installable under PHP 8.1.

Done one package (or coupled group) at a time, one major at a time, so each breaking change is isolated to its own commit + CI run. Ordering is safe/minor bumps first, known-breaking majors last, to keep CI green as long as possible.

Scope: runtime require deps; symfony/var-dumper + symfony/yaml move with the Symfony stack. Dev tooling (phpunit/phpstan/phpdoc-parser) is a separate follow-up.

Note: the PHP 8.1 floor caps Symfony at 6.4 LTS (7.x/8.x need PHP 8.2/8.4).

Progress

Package From → To Status Notes
twig/twig 3.11.3 → 3.27.1 ✅ done Removed the 15 now-fixed Twig CVE entries from config.audit.ignore. Twig now appends the template source location to render errors → rebaselined the view_render_error_user_input UI screenshot.
davaxi/sparkline 2.3 → 2.4 ✅ done Lock-only, no findings.
szymach/c-pchart 3.0.17 → 3.1.1 ✅ done Regenerated phpstan-baseline.neon (getRenderedImage() now returns GdImage; dropped an obsolete setAxisDisplay entry). No production change.
matomo/matomo-php-tracker 3.3.2 → 3.4.0 ⏸️ deferred 3.4 strict-typed the whole tracker SDK, cascading into LocalTracker/AnonymousPiwikUsageMeasurement override signatures, fixture token handling and ~34 assertTrue($t->doTrackXxx()) sites. Too large/cross-cutting → split into its own follow-up PR; kept at 3.3.2 here.
matomo/decompress 2.1.0 → 3.0.0 ✅ done Lock-only, no production change.
php-di/php-di 6.3.5 → 7.1.1 ✅ done Dropped the removed useAnnotations(false) call. Signature alignment only: Container::get/make/injectOn return types, proxyFactory param typed ProxyFactoryInterface (php-di 7 uses NativeProxyFactory on PHP 8.4+), and getDefinition(): ?Definition on the INI + testing definition sources.
geoip2/geoip2 2.13.0 → 3.3.0 ✅ done Lock-only, no production change.
wikimedia/less.php 3.2.1 → 5.5.1 ✅ done Both majors (3→4→5). The legacy lessc compatibility class still ships → no production change. less.php 4 emits shorter named CSS colors (white/red) → updated the AssetManagerTest expected fixture.
symfony/* (console, event-dispatcher, process, monolog-bridge; + dev var-dumper, yaml) 5.4 → 6.4 ✅ done Group bump to 6.4 LTS. Pulls psr/container to 2.0. Signature alignment on the ConsoleCommand base (addArgument/addOption: static, getHelper: mixed); handleSignalint|false, returns false to keep signalable commands running for graceful shutdown (Symfony 6.3+ would otherwise terminate immediately). ConsoleFormatter::format: mixed.
psr/log + monolog/monolog 1.x → 3.x ✅ done Two coupled majors (→2, then →3). monolog 2: handler return types (handle/isHandling: bool, write: void); reimplemented the removed Psr\Log\Test\TestLogger mock; preserved the tracker debug date format via EchoHandler::getDefaultFormatter() (monolog 2 changed the default to ISO8601). monolog 3: handlers/processors/formatters take immutable Monolog\LogRecord (its ArrayAccess keeps field reads working; message-rewriting processors use LogRecord::with()); FakeLogger/test capture handlers adapted; Monolog\Logger & the bridge ConsoleFormatter are now @final → 2 PHPStan baseline entries. psr/log 3 adds : void to the PSR-3 methods → documented as a Breaking Change for Piwik\Log\LoggerInterface implementers in CHANGELOG.md.

Upgrade-path note (psr/log)

psr/log relocated its class files (Psr/Log/src/) between v1 and v2/v3. During a one-click update the running (pre-update) process replaces the files but keeps its initialised autoloader, which then can't resolve the relocated classes — Class "Psr\Log\NullLogger" not found. The real fix belongs in the version updated from (5.x): preload the psr/log classes before Updater::installNewFiles(). The LatestStableInstall test fixture emulates that fix so the one-click-update UI tests exercise and prove the 5→6 path; a matching CoreUpdater fix needs to ship in a Matomo 5.x release (separate PR).

Checklist

  • [✔] I have understood, reviewed, and tested all AI outputs before use
  • [✔] All AI instructions respect security, IP, and privacy rules
@sgiehl sgiehl added this to the 6.0.0 milestone Jun 15, 2026
sgiehl added a commit that referenced this pull request Jun 18, 2026
…24648)

psr/log relocates its class files (Psr/Log -> src) between its v1 (Matomo 5) and v3 (Matomo 6). During a one-click update the running pre-update process replaces the install's files via installNewFiles() but keeps its already-initialised autoloader, which can then no longer resolve the relocated psr/log classes from their old paths - so rendering the rest of the update request fatals with 'Class Psr\Log\NullLogger not found' and the update aborts before the database migration runs.

Preload the (small) psr/log package at the start of installNewFiles(), while the old files are still present, so those classes stay resolvable for the remainder of the request. This is only needed for the 5 -> 6 update and can be removed again in Matomo 6.

The fix is validated end to end by the Matomo 6 dependency-upgrade PR (#24640), whose LatestStableInstall test fixture emulates exactly this preload on the downloaded stable install so the one-click update UI tests pass.
sgiehl and others added 3 commits June 18, 2026 18:01
- Never emit an APPROVE review; post COMMENT for non-blocking outcomes
  since the verdict comes from an LLM reading the untrusted PR diff
- Recompute highest_severity from finding counts instead of discarding
  the whole review on a trivial mismatch
- Document the listFiles patch-truncation degradation for large PRs
- Remove the unused codex job final_message output
Co-authored-by: aikido-pr-checks[bot] <169896070+aikido-pr-checks[bot]@users.noreply.github.com>
sgiehl added 11 commits June 19, 2026 10:46
Without the secret, openai/codex-action skips starting its proxy but
still tries to read the server-info file, crashing with a cryptic
"Failed to read server info" error. Add an early guard so the cause is
clear in the codex job log.
Update the minimum PHP version in the canonical locations: the early
runtime guard (testMinimumPhpVersion.php), composer.json (require +
platform) and the release checklist test, plus the README. The composer
platform no longer needs to exceed the floor now that wikimedia/less.php
is satisfied at 8.1.
Bump the lowest tested PHP version to 8.1 across the GitHub workflows,
the DDEV config and PHPStan's target version. Raise the test-action
generator's LATEST_PHP_VERSION to 8.4 so generated plugin matrices keep
covering two PHP versions now that the minimum is 8.1.
Drop conditionals and workarounds that only applied below PHP 8.1: the
argon2id <7.3 guard, the 8.0.x output-compression workaround branches
(the still-reachable 8.1.4/8.1.5 branches remain), the
PHP_VERSION_ID < 80100 reflection setAccessible blocks in tests, and the
PHP 8.0 test-data branches. Dropping the Referrers testSuffix branch also
makes its phpSerialized74 / phpSerialized expected files obsolete.
Refresh platform/platform-overrides to 8.1.0 and the content-hash so
composer install resolves under the new floor. CI was failing because
the lock still pinned the 7.2.9 build platform.
Targeting phpVersion 80100 reworded many already-baselined internal
type errors under PHP 8.1 stubs; regenerate the baseline to match.
Fix two issues in code instead of baselining them: preferredAlgorithm()
returns string (not int) under 8.1, and drop the stale GdImage
class.notFound ignore in CustomLogo (the class exists on 8.x).
Insights_initial and Notifications_loaded only differ in the order of
tied rows/notifications (identical values), which changed because PHP 8.0
made sorting stable. UI tests now run on 8.1, so adopt the 8.x order.
Refs #24638.
Pull in the regenerated min_php_ system-test expected files for PHP 8.1
(plugin-MarketingCampaignsReporting dev-17598).
UIIntegrationTest_ecommerce_overview and GoalsPages_ecommerce could capture
the report title link in its :hover state (a 2px underline) because the
screenshot was taken without resetting the mouse, so a leftover cursor
position from an earlier test bled through. Move the cursor off-screen
before capturing, matching the existing mouse.move(-10, -10) idiom.
Refs #24638.
Goals\Reports\Get sets title_edit_entity_url (making EnrichedHeadline render
the report title as a link) when $idGoal > GoalManager::IDGOAL_ORDER (0). $idGoal
is read as a string, so for ecommerce reports it is e.g. 'ecommerceOrder'. On
PHP 7 "ecommerceOrder" > 0 was false (string cast to int 0); on PHP 8 it is true
(int cast to string), so the ecommerce overview title was wrongly linked. Cast
$idGoal to int to restore the intended behaviour on all PHP versions.
Refs #24638.
sgiehl added 9 commits June 19, 2026 12:00
Bump twig within major 3 to the latest release (3.11.3 -> 3.27.1). The Twig
sandbox advisories previously ignored in composer.json (justified "blocked
while Matomo supports PHP 7.2") are fixed in 3.27.1, so the 15 audit-ignore
entries are removed - composer audit is clean. Also drops the now-unneeded
symfony/polyfill-php81.

Part of [Matomo 6] composer dependency upgrades. Refs #24638.
Twig 3.27 appends the template name and line to render-exception messages
("... in \"@Dashboard/index.twig\" at line 4."); rebaseline the screenshot
to match. Behaviour is unchanged (same safe error page) and no Matomo API
changed, so no CHANGELOG entry.
Minor bump within major 2 (lock only; constraint ~2.0 already allowed it). Part of [Matomo 6] composer dependency upgrades.
Latest within major 3 (constraint widened ~3.0.13 -> ^3.0). Part of [Matomo 6] composer dependency upgrades.
c-pchart 3.1 fixed its setAxisDisplay() param type (obsolete baseline entry removed) and returns GdImage on PHP 8.x, surfacing ImageGraph\StaticGraph::getRenderedImage()'s resource-vs-GdImage return type. Regenerated baseline; no Matomo API change.
Major bump (constraint ~2.0 -> ^3.0). The Matomo\Decompress\* API used by core/Unzip.php (DecompressInterface, Tar/Gzip/ZipArchive/PclZip extract()/errorInfo()/constructors) is unchanged, so no code changes are needed. Part of [Matomo 6] composer dependency upgrades.
Major bump (^6.0.0 -> ^7.0). php-di 7 dropped annotation support and typed its
interfaces, so align Matomo's internal container code:
- remove the defunct ContainerBuilder::useAnnotations(false) call (annotations are
  gone in 7; autowiring stays on, attributes default off - behaviour unchanged)
- match the now-typed signatures: DefinitionSource::getDefinition(string): ?Definition
  and Container::get(string): mixed / make(string, array): mixed / injectOn(object): object
Internal container only; plugin callers (strings/objects) are unaffected, so no
CHANGELOG entry. Part of [Matomo 6] composer dependency upgrades.
Missed in the php-di 7 bump: this test DefinitionSource also implements DI\Definition\Source\DefinitionSource, so its getDefinition() needs the typed signature (string): ?Definition. It builds the TEST container, so the old untyped signature fataled across every suite. Test helper only.
… PHP 8.4+)

php-di 7 uses NativeProxyFactory (PHP 8.4+ native lazy objects) instead of the ProxyManager-based ProxyFactory; both implement ProxyFactoryInterface. Matomo's Container::__construct typed the param as the concrete ?ProxyFactory, which rejected NativeProxyFactory on 8.4/8.5 (fataled across the whole 8.5 cell). Widen to ?ProxyFactoryInterface to match php-di 7's parent. Internal container; no CHANGELOG.
sgiehl added 15 commits June 19, 2026 12:00
Major bump (^2.8 -> ^3.0). The geoip2 Reader + model/record API used by plugins/GeoIp2 (Reader, country/city/enterprise/isp/asn, isoCode/name/subdivisions/traits accessors) is compatible with v3 - PHPStan finds no issues. Part of [Matomo 6] composer dependency upgrades.
First of two majors (3 -> 4). The legacy lessc compatibility class (setImportDir/setFormatter/compile) used by StylesheetUIAssetMerger still exists in less.php 4, so no code change is needed. Part of [Matomo 6] composer dependency upgrades.
less.php 4 emits the shorter named CSS colors (white, red) instead of their hex equivalents (#ffffff, #ff0000) when compiling LESS. Output is functionally identical; update the AssetManagerTest expected fixture to match.
Second of two majors (4 -> 5). The legacy lessc compatibility class (setImportDir/setFormatter/compile) used by StylesheetUIAssetMerger still ships in less.php 5, so no code change is needed. Completes the less.php upgrade. Part of [Matomo 6] composer dependency upgrades.
Bumps symfony/console, event-dispatcher, monolog-bridge, process (+ dev var-dumper, yaml) from 5.4 to 6.4 LTS (the highest line installable under PHP 8.1; 7.x/8.x need 8.2/8.4). psr/container is pulled to 2.0 (compatible with php-di 7).

Vendor-forced signature alignment on the ConsoleCommand base class: addArgument()/addOption() return ': static', getHelper() returns ': mixed'. handleSignal() now returns 'int|false' (Symfony 6.3+ deprecated returning nothing); it returns 0 to preserve the previous exit-after-handling behaviour. ConsoleFormatter::format() returns ': mixed' to match the monolog-bridge.

The symfony/monolog-bridge ConsoleFormatter is @Final since 6.1 and its version-conditional CompatibilityFormatter trait resolves to the monolog 3 LogRecord signature under static analysis while monolog 1.x is installed; the file is excluded from PHPStan with a TODO to revisit during the monolog 3 upgrade. Part of [Matomo 6] composer dependency upgrades.

No CHANGELOG entry: handleSignal() is final (plugins extend the unchanged handleSystemSignal hook), the other touched methods are disabled stubs, and ConsoleFormatter is internal.
Symfony 6.3+ terminates the process immediately after handleSignal() returns an int exit code. Matomo's signalable commands instead use signals to request a graceful shutdown (the scheduler and archiver finish the current unit of work, log the abort and stop the loop themselves), so the command must keep running after the signal is handled. Returning false preserves that behaviour, which matches Symfony 5.4 where handleSignal() did not auto-terminate. Fixes RunScheduledTasksProcessSignalTest and CoreArchiverProcessSignalTest.

Verified locally: both signal test classes pass (2/20 and 16/219 assertions).
First of two coupled major steps for the logging cluster (monolog 1 -> 2, psr/log 1 -> 2). monolog 2 added return types to HandlerInterface (handle()/isHandling(): bool) and to the AbstractProcessingHandler/StreamHandler write(): void, so the custom handlers are aligned: DatabaseHandler, EchoHandler, FileHandler, WebNotificationHandler write() -> : void; FailureLogMessageDetector, LogCaptureHandler handle() -> : bool; WebNotificationHandler isHandling() -> : bool. monolog 2 keeps array records (LogRecord arrives in monolog 3) and its FormatterInterface/ProcessorInterface/ResettableInterface are still untyped, so formatters, processors and reset() are unchanged. Part of [Matomo 6] composer dependency upgrades.

No CHANGELOG entry: the Monolog handlers are internal and not part of any plugin-facing API.
psr/log 2 removed the Psr\Log\Test\TestLogger helper that the test mock extended; reimplement it inline in tests/PHPUnit/Framework/Mock/TestLogger.php (same recording API the tests rely on) so it no longer depends on the dropped class.

monolog 2 narrows the inherited isHandling() record type hint to the level only; add an @param phpdoc on WebNotificationHandler::isHandling() documenting that the full record (with context) is available at runtime, resolving the PHPStan error.

No CHANGELOG entry: test-only mock plus an internal handler phpdoc.
monolog 2 changed the default LineFormatter date format to ISO 8601 with microseconds. The EchoHandler (used for the on-screen tracker debug output) relies on the default formatter, so override getDefaultFormatter() to keep the previous 'Y-m-d H:i:s' format and leave the user facing log output unchanged.

Also add the monolog 2 write(): void return type to the anonymous handlers defined in ResetInvalidationsTest and InvalidateReportDataTest. Part of [Matomo 6] composer dependency upgrades.

No CHANGELOG entry: internal handler behaviour preservation plus test-only signatures.
psr/log moves its class files (Psr/Log -> src) between major versions. During a one-click update the running (pre-update) process replaces the files but keeps its initialised autoloader, which can then no longer resolve the relocated classes, so rendering the updater result fatals with 'Class Psr\Log\NullLogger not found'.

The proper fix has to ship in the Matomo version we update from (5.x): load the relocated psr/log classes before installNewFiles() swaps the files, keeping them in memory for the rest of that request. Patch the downloaded stable install in the LatestStableInstall fixture accordingly so the one-click update UI tests exercise (and prove) that upgrade path. Part of [Matomo 6] composer dependency upgrades.

No CHANGELOG entry: test fixture only.
Loading Psr\Log\NullLogger transitively loads its parent AbstractLogger, the LoggerInterface it implements and the LoggerTrait it uses, which is what the updater needs to resolve the logger after the file swap. Avoids preloading the whole psr/log package.
Resolving the logger during the one-click update touches several psr/log classes (NullLogger and LogLevel at least), so preload the whole (tiny) package before installNewFiles() rather than a subset.
Final coupled major step for the logging cluster (monolog 2 -> 3, psr/log 2 -> 3).

monolog 3 passes immutable Monolog\LogRecord objects instead of array records and gives its handler/processor/formatter methods LogRecord type hints. Updated the custom handlers (handle/isHandling/write), processors (__invoke) and formatters (format) to accept LogRecord; LogRecord still implements ArrayAccess so the existing field reads ($record['message'], ['extra'], ['level_name'], etc.) keep working. The processors that rewrite the message (Token, Sprintf, ExceptionToText) now use the immutable LogRecord::with() API. FailureLogMessageDetector::reset() gained the monolog 3 ': void' return type.

psr/log 3 adds ': void' to the PSR-3 logger methods; Piwik\Log\Logger and NullLogger inherit it from their monolog/psr parents, so only the plugin-facing Piwik\Log\LoggerInterface contract is affected - documented under Breaking Changes in CHANGELOG.md.

monolog 3 marks Monolog\Logger and the symfony/monolog-bridge ConsoleFormatter as @Final; Matomo's long-standing proxy/extension of those is intentional, so the two 'extends @Final class' notices are added to the PHPStan baseline and the temporary ConsoleFormatter PHPStan exclude is removed (its LogRecord signature now matches the bridge). Unit tests updated to build LogRecord inputs. Part of [Matomo 6] composer dependency upgrades.
psr/log 3 requires the ': void' return type on log(), and monolog 3's PsrLogMessageProcessor now consumes/returns a Monolog\LogRecord instead of an array record. Update the FakeLogger mock accordingly so it no longer fails to declare (which was breaking the unit and several integration test suites).
The anonymous AbstractProcessingHandler subclasses used to capture log output in InvalidateReportDataTest and ResetInvalidationsTest must declare write(LogRecord $record) under monolog 3; $record['formatted'] keeps working via LogRecord's ArrayAccess.
@sgiehl sgiehl force-pushed the dev-17598 branch 9 times, most recently from a009230 to 3841643 Compare June 24, 2026 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant