Depend on composer.json as loading order for extensions
In order to reduce the gap between Composer-Mode TYPO3 installations and Non-Composer-Mode ("Classic way"), there are two obstacles in the way (there a few more, but let's concentrate on the most important ones).
1. Core requires ext_emconf.php
TYPO3 Core depends on ext_emconf.php in a few places for the following reasons to ensure the system runs. The main / important parts are:
- Extension Path
- Extension Key (computed from the path of an extension (the last part of the directory is the extension key)
- Extension Name / Title
- Dependencies / Suggest parts (relevant for the loading order of extensions)
While most of the functionality can nowadays be loaded via the Composer 2 Runtime API, we first must ensure that all existing extensions of a running TYPO3 installation contain all the information in their composer manifest or from Composer 2 Runtime API.
Currently TYPO3 nor the extensions does not know the extension key in its code, even in composer-mode installations, by guessing and checking the extension path, although it is possible to enforce the path "typo3conf/ext/benni_test" in the composer.json in the "extra" section.
2. Core creates PackageStates.php
This file currently contains all ACTIVE extensions in their defined loading order, as well as their paths. This file is basically un-needed in proper composer-based installations, but TYPO3 Core still needs them.
For Classic-Mode TYPO3 installations the following approach is in place (which can be divided into two parts):
1. Ensure all extensions in typo3conf/ext/ contain a composer manifest file (= composer.json - which is currently optional), which contains the extension-name (defined in the extra-section, currently optional), and the dependencies of the composer-package names, not the extension names, to derive the loading order from it.
Once every extension has a composer.json file, TYPO3 Core can slowly migrate into reading the composer-manifest for fetching package information instead of depending on ext_emconf.php and PackageStates.php.
We strive for maximum compatibility than for perfectionized composer.json files with their dependencies. Example: Autoload information for extensions that do NOT have a composer.json defined, is just added with "classmap: [*]" to ensure maximum compatibility.
Necessary changes for creating composer.json files for all extensions¶
In general: Check for "extra[typo3-cms][extension-name]" if a composer.json exists, and add the extension key in the existing composer.json.
Composer Mode: Nothing needed directly, except for extensions which aren't configured as separate packages, but just added into typo3conf/ext/ and provided autoload information in the composer.json.
Since extensions allow underscores (e.g. "benni_export") but composer packages need a vendor name and a package name without a "_" but a "-", we add a migration to add a composer.json for all extensions that do not have a composer.json file.
1. For TER-based extensions that contain a composer.json file, nothing needs to be adapted.
2. For extensions that are TER-based that do not have a composer.json file, all files and dependencies of this extension can be created by TER directly. We have created an entry point (POST https://extensions.typo3.dev/composerize/my_extension with the ext_emconf contents as POST data) to create a composer.json as return value. A fallback vendor name "typo3-local" is used.
3. Also: For extensions that are not registered in TER, the dependencies are also resolved in the TER endpoint, and the "typo3-local" vendor name is created.
An upgrade wizard checks all extensions and sends relevant extension key + ext_emconf.php information to TER to get a computed composer.json file.
With this upgrade wizard in place, the PackageStates.php can be created with all information from composer.json and the Composer API, and not the ext_emconf.php files anymore.
A system report in the reports module would show if there are extensions contain composer.json files with a extension-key listed.
When using the Extension manager, it needs to be checked if the composer.json exists, otherwise this should be created as well.
Phasing out PackageStates.php¶
PackageStates at its core is a "cache" file containing information of all active installed, including their loading order and their paths. The PackageManager API class currently checks during runtime if the file exists, and loads it. If it doesn't a Failsafe PackageManager kicks in to ensure the minimum system is loaded.
The downsides of this approach (which was built for the main reason of keeping compatibility with Classic-Mode TYPO3 installations) are:
- Limited approach of iterating through the Packages (only done through PackageManager and the Package.php objects)
- Several information needs to be loaded from the ext_emconf.php (version)
- Hard-coded path of the file path (inside the document root)
- Fragile location (if deleted, what would happen?)
- Chicken-Egg problem when building the Core cache and loading the file => the paths - e.g. project and public path need to be calculated first)
Composer-Mode installations, which treat PackageStates as a read-only path during runtime, could benefit from a separate approach, that is: Creating a "ActiveExtensions" PHP class, which is a iterable, and internally containing the array. The PHP class could be added to composers' class loader and would be available directly at the first line of code in a TYPO3-based installation.
Composer-Mode installations could create the file during build-time, and avoiding a lot of execution time in production on every request.
Classic-Mode Installations would dynamically (re-)create such a PHP class, and load the class via the Autoloader. This way, the path to the file is completely irrelevant then.
When activating / de-activating an extension in Classic Mode, the PHP class would be re-generated.
Main limitation: Composer-Mode installations would not be able to dynamically activate / deactivate an extension. That means: composer-based installations would have all found extensions activated at any time, and that all extensions that are not listed in the projects' composer.json would not be considered for loading.
The PackageStates.php would be kept in sync with the ActiveExtensions PHP class for v11, and be available until the new API has been proven stable and used everywhere. The ActiveExtensions PHP class could be added to the TYPO3 Composer Plugin and create the file even for older TYPO3 versions, preparing composer-based installations with weird setups for this transitions.Main benefits:
- Extension Key guessing is over
- composer.json is used for extension loading order at any time (no need for ext_emconf.php in this regard)
- Hacks such as $_EXTKEY would not be needed anymore
- All information about loaded extensions would finally be in one place (programatically)