Skip to content

[BUGFIX] Improve package name resolving#704

Merged
sbuerk merged 3 commits into
TYPO3:mainfrom
IchHabRecht:bugfix/improve-package-name-resolving
Jul 23, 2026
Merged

[BUGFIX] Improve package name resolving#704
sbuerk merged 3 commits into
TYPO3:mainfrom
IchHabRecht:bugfix/improve-package-name-resolving

Conversation

@IchHabRecht

@IchHabRecht IchHabRecht commented Feb 22, 2026

Copy link
Copy Markdown
Contributor

Resolving a value to a composer package went through
ComposerPackageManager::resolvePackageName(), which strips a list of known
path prefixes before the lookup is done. One of those prefixes was the plain
base name of the project root folder.

That entry is a relative single segment prefix, so it also matches the vendor
segment of a composer package name. For a project checked out into a folder
named typo3, the value typo3/testing-framework was reduced to
testing-framework and typo3/sysext/core to sysext/core. Both lookups then
fail, and functional tests abort with a method call on null:

Error: Call to a member function getRealPath() on null
vendor/typo3/testing-framework/Classes/Core/Testbase.php:369

This is reproducible with the TYPO3 core itself:

git clone https://github.com/typo3/typo3
cd typo3
Build/Scripts/runTests.sh -s composerInstall
Build/Scripts/runTests.sh -s functional typo3/sysext/core/Tests/Functional/Authentication/AbstractUserAuthenticationTest.php

The same mechanism hits any project whose root folder is named like one of its
composer vendors.

A second, independent defect surfaced while verifying this:
Testbase::linkTestExtensionsToInstance() determines the name of a test
extension with getPackageInfoWithFallback() and symlinks it into the test
instance under the resolved extension key, while
Testbase::setUpPackageStates() determines it with getPackageInfo(), which
only handles composer package names and extension keys. For a local extension
that is not required by the root composer.json that lookup fails and the code
falls back to the directory base name. If the directory name differs from the
extension key, PackageStates.php points to a path that was never created:

TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException: Tried to
instantiate a package object for package "client_clientname" with a
non-existing package path ".../typo3conf/ext/client_clientname/".

Changes

The three commits are kept separate and are bisectable:

  1. Remove obsolete basename removePrefixPath — drops the project root folder
    base name from the prefix list in removePrefixPaths(). The entry had been
    added together with the relative path handling in
    getPackageFromPathFallback(), which resolves ../<root>/… values by
    prefixing the project root real path and canonicalizing the result; the base
    name entry is not needed for that. Resolves core functional tests does not work if checkout directory is called 'typo3' #665.
  2. Prevent resolving existing package namesgetPackageInfo() now looks up
    the handed over value first and only falls back to the resolved name if it is
    not a known composer package. A value that already is a known package name
    does not need normalization at all. Resolves core functional tests does not work if checkout directory is called 'typo3' #665.
  3. Resolve test extension paths consistentlysetUpPackageStates() uses
    getPackageInfoWithFallback(), the same way linkTestExtensionsToInstance()
    already does, so both call sites derive the extension key identically.
    Resolves Streamline ComposerPackageManager->getPackageInfo() usage to support extensions with none matching folder name #683.

Tests

Each commit carries its own regression test:

  • ComposerPackageManagerTest::prepareResolvePackageNameKeepsProjectRootFolderNamePrefix()
    asserts that <project-root-folder-name>/some-package is not rewritten.
  • ComposerPackageManagerTest::knownPackageNameIsNotModifiedBeforeLookup()
    registers a fixture extension and looks it up by its composer package name.
  • TestbaseTest::setUpPackageStatesUsesExtensionKeyOfTestExtensionAndNotItsFolderName()
    builds a minimal test instance, symlinks a fixture extension whose folder name
    differs from its extension key, and asserts the written PackageStates.php
    entry. Without commit 3 this test fails with the
    InvalidPackagePathException quoted above.

All three tests fail against main and pass with the respective commit applied.

Verification

Run locally on PHP 8.2 (podman):

Build/Scripts/runTests.sh -s composerUpdate   SUCCESS
Build/Scripts/runTests.sh -s cgl -n           SUCCESS
Build/Scripts/runTests.sh -s lint             SUCCESS
Build/Scripts/runTests.sh -s phpstan          SUCCESS
Build/Scripts/runTests.sh -s unit             OK (127 tests, 337 assertions)

Additionally verified against a TYPO3 project checkout placed in a folder named
typo3 (issue #665) and against a project with a local, non-required extension
package whose folder name differs from its extension key (issue #683).

Resolves: #665
Resolves: #683
Releases: main, 9, 8

@oliverklee

Copy link
Copy Markdown
Contributor

I propose this goes to: Releases: main, 9, 8

Comment thread Classes/Composer/ComposerPackageManager.php
IchHabRecht and others added 3 commits July 23, 2026 11:29
ComposerPackageManager::removePrefixPaths() strips a list of known
path prefixes from a value before that value is looked up as a
composer package name or as an extension key. The list contained the
plain base name of the project root folder, for example `typo3` for a
project checked out into a folder named `typo3`.

That entry is a relative single segment prefix, so it also matches
the vendor segment of a composer package name. With a project root
folder named `typo3` the lookup value `typo3/testing-framework` is
reduced to `testing-framework` and `typo3/sysext/core` is reduced to
`sysext/core`. Both lookups fail, and functional tests abort with a
method call on null, for example in
Testbase::linkFrameworkExtensionsToInstance().

The entry had been added together with the relative path handling in
getPackageFromPathFallback(), which resolves `../<root>/...` values
by prefixing the project root real path and canonicalizing the
result. The base name entry is not needed for that, so remove it.

Resolves: TYPO3#665
Releases: main, 9, 8
ComposerPackageManager::getPackageInfo() passed every handed over
value through resolvePackageName(), which removes known path
prefixes and classic mode notation prefixes before the lookup is
done. A value that already is a known composer package name does not
need that normalization at all, and the normalization can turn it
into an unknown value when the vendor segment of the package name
matches one of the removed prefixes.

Look up the handed over value first and only fall back to the
resolved name if it is not a known composer package.

Resolves: TYPO3#665
Releases: main, 9, 8
Testbase::linkTestExtensionsToInstance() resolves a configured test
extension path with getPackageInfoWithFallback() and symlinks the
extension into the test instance using the resolved extension key.
Testbase::setUpPackageStates() resolves the very same value with
getPackageInfo() instead, which only handles composer package names
and extension keys. For a local extension that is not required by
the root composer.json that lookup fails, and the code falls back
to the directory basename.

If the directory name differs from the extension key, the symlink
and the PackageStates.php entry end up with different names, and
instantiating the package fails with an InvalidPackagePathException.

Use getPackageInfoWithFallback() in setUpPackageStates(), so both
call sites derive the extension key the same way.

Resolves: TYPO3#683
Releases: main, 9, 8
@sbuerk
sbuerk force-pushed the bugfix/improve-package-name-resolving branch from e580518 to c223b05 Compare July 23, 2026 09:37
@sbuerk

sbuerk commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Agreed, the footers are Releases: main, 9, 8 now.

Branch 7 carries the identical removePrefixPaths() including the basename
entry as well, so it would be portable there too. Leaving it out for now.

@sbuerk
sbuerk merged commit 9dc6a2a into TYPO3:main Jul 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants