Open coder-at-heart opened 3 years ago
What you're doing should work without issue. Strauss uses Composer's own tooling to create its autoload.php
. But what you might prefer to do...
You can add the strauss
directory to your Composer autoloader's classmap
entry:
{
"autoload": {
"psr-4": {
"MyPlugin\\": "src/php"
},
"classmap": ["strauss"]
}
}
Then run composer dump-autoload
and Composer will scan that directory and add the files to its autoloader.
A little problem now is you still have the old files in your project, so you can have Strauss delete them as it runs by setting delete_vendor_files
to true
in the extra
/strauss
key in your composer.json
:
"extra": {
"strauss": {
"delete_vendor_files": true
}
}
I'll leave this issue open until I add some verbose output to the end of vendor/bin/strauss
to check if users have the output directory in their autoload
key.
I forgot to add, if you have your Strauss output directory already in your composer.json
autoload
key, then Strauss won't bother to create its autoload.php
.
This won't handle files
autoloaders (which is a minority of cases). I'll think about communicating that to users after the process is complete too.
Thanks Brian.
Here's my composer.json
{
"name": "lnk7/tracked-links",
"description": "A Tracked Link Plugin",
"keywords": [
"framework",
"wordpress",
"plugin"
],
"type": "project",
"license": "MIT",
"require": {
"geniepress/framework": "^1.0",
"hashids/hashids": "^4.0",
"jakeasmith/http_build_url": "^1"
},
"autoload": {
"psr-4": {
"TrackedLinks\\": "src/php"
},
"classmap": [
"strauss"
]
},
"extra": {
"strauss": {
"delete_vendor_files": true
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"strauss": [
"@php -d memory_limit=-1 strauss.phar"
],
"post-install-cmd": [
"@strauss",
"composer dumpautoload"
],
"post-update-cmd": [
"@strauss",
"composer dumpautoload"
]
}
}
I'm using the composer autoload...
include_once('vendor/autoload.php');
however I get
Warning: require(/Users/....../trackedlinks.local/wp-content/plugins/tracked-links/vendor/composer/../symfony/polyfill-php80/bootstrap.php): failed to open stream: No such file or directory in /Users/....../trackedlinks.local/wp-content/plugins/tracked-links/vendor/composer/autoload_real.php on line 71
It looks like the delete is removing some of the code that composer needs.
Any help appreciated.
I guess this is a bug. The files from the files
autoloaders are being moved, modified and the originals deleted (by design). When composer dump-autoload
is run, it scans for classes, but doesn't refresh the entries from file
autoloaders that it created before Strauss was run.
As I searched for a solution, I saw this comment from one of the Composer maintainers!
files autoloading was seen as more of a hack initially https://github.com/composer/composer/issues/10024#issuecomment-888270023
I have a solution for you for today. A script that reads two of Composer's PHP files and does a string replace with the updated path. Save this in your project folder as update-files-autoload-directory.php
<?php
/**
* @see https://github.com/BrianHenryIE/strauss/issues/34
*/
$straussOutputDir = 'strauss';
$autoloadFilesDotPhpTxt = file_get_contents( __DIR__ . '/vendor/composer/autoload_files.php' );
preg_match_all('/(\'\w*\' => )(\$vendorDir \. \')(.*)/', $autoloadFilesDotPhpTxt, $autoloadFilesMatches);
$autoloadStaticDotPhpTxt = file_get_contents( __DIR__ . '/vendor/composer/autoload_static.php' );
preg_match_all('/(\'\w*\' => )(__DIR__ \. \'\/\.\.\' \. \')(.*)/', $autoloadStaticDotPhpTxt, $autoloadStaticMatches);
$autoloadStaticReplacements = array();
foreach( $autoloadStaticMatches[0] as $index => $staticFileMatches ) {
// `autoload_static.php` contains `files` autoloaders and more. ONLY operate on the files.
// Check is it in the autoload_files file.
if( in_array( $autoloadStaticMatches[3][$index], $autoloadFilesMatches[3] ) ) {
$autoloadStaticReplacements[$staticFileMatches] = $autoloadStaticMatches[1][$index] . "__DIR__ .'/../../$straussOutputDir" . $autoloadStaticMatches[3][$index];
}
}
$autoloadFilesReplacements = array();
foreach( $autoloadFilesMatches[0] as $index => $match ) {
$autoloadFilesReplacements[$match] = $autoloadFilesMatches[1][$index] . "__DIR__ .'/../../$straussOutputDir" . $autoloadFilesMatches[3][$index];
}
// Make the changes:
foreach ($autoloadStaticReplacements as $search => $replace ) {
$autoloadStaticDotPhpTxt = str_replace( $search, $replace, $autoloadStaticDotPhpTxt );
}
file_put_contents(__DIR__ . '/vendor/composer/autoload_static.php', $autoloadStaticDotPhpTxt);
foreach ($autoloadFilesReplacements as $search => $replace ) {
$autoloadFilesDotPhpTxt = str_replace( $search, $replace, $autoloadFilesDotPhpTxt );
}
file_put_contents(__DIR__ . '/vendor/composer/autoload_files.php', $autoloadFilesDotPhpTxt);
I was testing this on geniepress/plugin. For some reason (another bug to investigate!) it needed these packages explicitly required:
composer require symfony/polyfill-php73
composer require react/promise
Then this should work
rm -rf strauss;
rm -rf vendor;
mkdir strauss;
composer install;
vendor/bin/strauss;
composer dump-autoload;
php update-files-autoload-directory.php;
And confirm no errors with:
php vendor/autoload.php
I'll have to think about how to get that working seamlessly. It looks like I can register Strauss as Composer Plugin, hook into pre-install-cmd
or pre-autoload-dump
and maybe manipulate the autoload keys there.
Another, more straightforward solution!
I've created a branch in-situ
for Strauss to edit the files inside the vendor
folder without copying them first.
Your autoload
classmap
key needs vendor
in it now, and Strauss config needs target_directory
set to vendor
:
"autoload": {
"psr-4": {
"TrackedLinks\\": "src/php"
},
"classmap": [
"vendor"
]
},
"extra": {
"strauss": {
"target_directory": "vendor"
}
},
Then tell Composer to download Strauss from the new branch and reinstall everything:
composer require --dev brianhenryie/strauss:dev-in-situ;
rm -rf strauss;
rm -rf vendor;
composer install;
vendor/bin/strauss;
composer dump-autoload;
php vendor/autoload.php;
This has just briefly been tested on geniepress/plugin
, but it's been on my mind for a while to try. There's no reason it shouldn't work.
I'll test it myself over the next few weeks and get it merged and released.
Hi Brian - sorry I missed these update - This is great . I managed to build my own autoloader for my own classes ( 5 lines of code) and include the strauss autoloader. works perfectly.
Hi Brian,
Did this get merged? What is the proper way to do this in 2022? I'm having similar troubles as the OP (at least to my untrained eye, it could be something else)
Something to note if you use this approach, make sure to enable Authoritative Classmaps in composer. Otherwise, you may get "class already declared" errors if another plugin tries to load an unprefixed version of a class you also require. The classmap won't find the prefixed version, but the PSR autoloading rules will load the namespaced file. If your plugin loads first, then you'll end up with a duplicate classname error.
Just wanted to leave an update for others who find this issue: this branch works brilliantly đ
As @TimothyBJacobs says you need Authorative classmaps and I've included an example below. You also need to run composer dump-autoload
after composer install
(the @strauss
calls in your composer.json
scripts section aren't enough).
My composer.json
{
"require": {
"...insert your packages here...": "as usual. delete this line obviously"
},
"autoload": {
"psr-4": {
"PeterHas\\": "src/"
},
"classmap": [
"vendor"
]
},
"require-dev": {
"brianhenryie/strauss": "dev-in-situ"
},
"scripts": {
"strauss": [
"vendor/bin/strauss"
],
"post-install-cmd": [
"@strauss"
],
"post-update-cmd": [
"@strauss"
]
},
"extra": {
"strauss": {
"target_directory": "vendor",
"namespace_prefix": "PeterHas\\Vendor\\",
"classmap_prefix": "PeterHas_Vendor_",
"constant_prefix": "PHV_",
"exclude_from_prefix": {
"packages": [
],
"namespaces": [
],
"file_patterns": [
]
}
}
},
"config": {
"classmap-authoritative": true,
"optimize-autoloader": true,
"sort-packages": true
}
}
And then
composer install
composer dump-autoload
Thanks @BrianHenryIE for this superb tool!
I've encountered a bug (I think) using the configuration above if I install packages more than once.
{
"require": {
"monolog/monolog": "^3.2",
"ruflin/elastica": "^7.3",
"elasticsearch/elasticsearch": "^7.17",
"symfony/console": "^5.4"
},
"autoload": {
"psr-4": {
"PicturingHistory\\": "src/"
},
"classmap": [
"vendor"
]
},
"require-dev": {
"brianhenryie/strauss": "dev-in-situ"
},
"scripts": {
"strauss": [
"vendor/bin/strauss"
],
"post-install-cmd": [
"@strauss"
],
"post-update-cmd": [
"@strauss"
]
},
"extra": {
"strauss": {
"target_directory": "vendor",
"namespace_prefix": "PeterHas\\Vendor\\",
"classmap_prefix": "PeterHas_Vendor_",
"constant_prefix": "PHV_",
"exclude_from_prefix": {
"packages": [
],
"namespaces": [
],
"file_patterns": [
]
}
}
},
"config": {
"classmap-authoritative": true,
"optimize-autoloader": true,
"sort-packages": true
}
}
On the first run:
$ composer install && composer dump-autoload
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Package operations: 42 installs, 0 updates, 0 removals
- Installing symfony/polyfill-php80 (v1.27.0): Extracting archive
- Installing symfony/deprecation-contracts (v3.2.0): Extracting archive
- Installing symfony/finder (v5.4.17): Extracting archive
- Installing symfony/polyfill-mbstring (v1.27.0): Extracting archive
- Installing symfony/polyfill-intl-normalizer (v1.27.0): Extracting archive
- Installing symfony/polyfill-intl-grapheme (v1.27.0): Extracting archive
- Installing symfony/polyfill-ctype (v1.27.0): Extracting archive
- Installing symfony/string (v6.2.2): Extracting archive
- Installing psr/container (2.0.2): Extracting archive
- Installing symfony/service-contracts (v3.2.0): Extracting archive
- Installing symfony/polyfill-php73 (v1.27.0): Extracting archive
- Installing symfony/console (v5.4.17): Extracting archive
- Installing league/mime-type-detection (1.11.0): Extracting archive
- Installing league/flysystem (1.1.10): Extracting archive
- Installing psr/simple-cache (1.0.1): Extracting archive
- Installing psr/log (2.0.0): Extracting archive
- Installing nikic/php-parser (v4.15.3): Extracting archive
- Installing myclabs/php-enum (1.8.4): Extracting archive
- Installing json-mapper/json-mapper (2.14.3): Extracting archive
- Installing symfony/process (v6.2.0): Extracting archive
- Installing symfony/polyfill-php81 (v1.27.0): Extracting archive
- Installing symfony/filesystem (v6.2.0): Extracting archive
- Installing seld/signal-handler (2.0.1): Extracting archive
- Installing seld/phar-utils (1.2.1): Extracting archive
- Installing seld/jsonlint (1.9.0): Extracting archive
- Installing react/promise (v2.9.0): Extracting archive
- Installing justinrainbow/json-schema (5.2.12): Extracting archive
- Installing composer/pcre (3.1.0): Extracting archive
- Installing composer/xdebug-handler (3.0.3): Extracting archive
- Installing composer/spdx-licenses (1.5.7): Extracting archive
- Installing composer/semver (3.3.2): Extracting archive
- Installing composer/metadata-minifier (1.0.0): Extracting archive
- Installing composer/class-map-generator (1.0.0): Extracting archive
- Installing composer/ca-bundle (1.3.5): Extracting archive
- Installing composer/composer (2.5.1): Extracting archive
- Installing brianhenryie/strauss (dev-in-situ 82d0568): Extracting archive
- Installing ezimuel/guzzlestreams (3.1.0): Extracting archive
- Installing ezimuel/ringphp (1.2.2): Extracting archive
- Installing monolog/monolog (3.2.0): Extracting archive
- Installing nyholm/dsn (2.0.1): Extracting archive
- Installing elasticsearch/elasticsearch (v7.17.1): Extracting archive
- Installing ruflin/elastica (7.3.0): Extracting archive
Generating optimized autoload files
Warning: Ambiguous class resolution, "Composer\InstalledVersions" was found in both "/www/vendor/composer/composer/src/Composer/InstalledVersions.php" and "/www/vendor/composer/InstalledVersions.php", the first will be used.
29 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> vendor/bin/strauss
Generating optimized autoload files (authoritative)
Warning: Ambiguous class resolution, "Composer\Autoload\ClassLoader" was found in both "/www/vendor/composer/ClassLoader.php" and "/www/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php", the first will be used.
Warning: Ambiguous class resolution, "Composer\InstalledVersions" was found in both "/www/vendor/composer/composer/src/Composer/InstalledVersions.php" and "/www/vendor/composer/InstalledVersions.php", the first will be used.
Generated optimized autoload files (authoritative) containing 2074 classes
So far so good. Let's run the command again:
$ composer install && composer dump-autoload
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating optimized autoload files
Warning: Ambiguous class resolution, "Composer\Autoload\ClassLoader" was found in both "/www/vendor/composer/ClassLoader.php" and "/www/vendor/composer/composer/src/Composer/Autoload/ClassLoader.php", the first will be used.
Warning: Ambiguous class resolution, "Composer\InstalledVersions" was found in both "/www/vendor/composer/composer/src/Composer/InstalledVersions.php" and "/www/vendor/composer/InstalledVersions.php", the first will be used.
29 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> vendor/bin/strauss
PHP Fatal error: Uncaught Error: Class "Symfony\Component\Console\Application" not found in /www/vendor/brianhenryie/strauss/src/Console/Application.php:8
Stack trace:
#0 /www/vendor/composer/ClassLoader.php(582): include()
#1 /www/vendor/composer/ClassLoader.php(433): Composer\Autoload\{closure}('/var/www/html/h...')
#2 /www/vendor/brianhenryie/strauss/bin/strauss(27): Composer\Autoload\ClassLoader->loadClass('BrianHenryIE\\St...')
#3 /www/vendor/brianhenryie/strauss/bin/strauss(29): {closure}('0.8.1')
#4 /www/vendor/bin/strauss(120): include('/var/www/html/h...')
#5 {main}
thrown in /www/vendor/brianhenryie/strauss/src/Console/Application.php on line 8
Fatal error: Uncaught Error: Class "Symfony\Component\Console\Application" not found in /www/vendor/brianhenryie/strauss/src/Console/Application.php:8
Stack trace:
#0 /www/vendor/composer/ClassLoader.php(582): include()
#1 /www/vendor/composer/ClassLoader.php(433): Composer\Autoload\{closure}('/var/www/html/h...')
#2 /www/vendor/brianhenryie/strauss/bin/strauss(27): Composer\Autoload\ClassLoader->loadClass('BrianHenryIE\\St...')
#3 /www/vendor/brianhenryie/strauss/bin/strauss(29): {closure}('0.8.1')
#4 /www/vendor/bin/strauss(120): include('/var/www/html/h...')
#5 {main}
thrown in /www/vendor/brianhenryie/strauss/src/Console/Application.php on line 8
Script vendor/bin/strauss handling the strauss event returned with error code 255
Script @strauss was called via post-install-cmd
My understanding is that the paths within Strauss are not being rewritten this time, so it can't find the packages. However a diff shows nothing wrong:
$ git diff --no-index good bad
diff --git a/good/composer/installed.php b/bad/composer/installed.php
index 984933607..a2ea3573f 100644
--- a/good/composer/installed.php
+++ b/bad/composer/installed.php
@@ -241,8 +241,8 @@
'psr/log-implementation' => array(
'dev_requirement' => false,
'provided' => array(
- 0 => '1.0|2.0',
- 1 => '3.0.0',
+ 0 => '3.0.0',
+ 1 => '1.0|2.0',
),
),
'psr/simple-cache' => array(
Any ideas?
@BrianHenryIE I've updated my comment with my composer.json
Hey, here's half a fix. Basically, build the phar from this branch yourself and run it against a --no-dev
Composer install.
git clone https://github.com/BrianHenryIE/strauss.git;
cd strauss;
git checkout in-situ;
composer install;
wget -O phar-composer.phar https://github.com/clue/phar-composer/releases/download/v1.2.0/phar-composer-1.2.0.phar
mkdir build
mv vendor build/vendor
mv src build/src
mv bin build/bin
mv composer.json build
php -d phar.readonly=off phar-composer.phar build ./build/
# Move the new .phar to your actual project directory
# Update your Composer script to run "@php strauss.phar"
composer install --no-dev
But...
When you run this twice, the namespaces get prefixed twice. I'll mark this as a bug and look into it later.
This additional configuration should work but isn't:
"namespace_replacement_patterns": {
"~(PeterHas\\\\Vendor\\\\)(\\1)*?~": "PeterHas\\Vendor\\"
}
I think the match is correct, I'm not 100% sure why it's not working.
I've merged the in-situ
branch into master since it shouldn't impact anyone who doesn't explicitly set their target directory to vendor
. I.e. the .phar on master should work for this now. The duplicating prefix presumably is still a bug.
I would like proper tests around this, particularly since it's doing some deep dive edits to Composer's own work, which I think most of us don't ever read. Example composer.json
s with error messages would be great.
It may be possible to hook into Composer's plugin actions and make this a very seamless setup.
Thanks @BrianHenryIE. I'm back on this project today and tested using the Phar from the master branch which works great - but as you say on second run duplicates the namespace prefix. I haven't made a regex fix for that work yet either.
Edit: I got the regex to match by removing the doubling of the slashes on the first part:
"namespace_replacement_patterns": {
"~(PeterHas\\Vendor\\)(\\1)*?~": "PeterHas\\Vendor\\"
}
Unfortunately whatever regex I try the resulting statement in the vendor files is namespace ;
Hello, I did not understand if there is a configuration that lets me autoload the prefixed classes and my classes (using psr4), but not the unprefixed ones. I mean, using the master branch and possibly not in a hacky way.
@BrianHenryIE Have you found any solution for the duplicate namespace problem? I've tried the following, it works correctly the first time, but the second time it duplicates it, and the third time it removes the duplicated:
"namespace_replacement_patterns": {
"~(Arjen\\\\Vendor\\\\)(\\1)*~": "Arjen\\Vendor\\"
}
@arjen-mediasoep try the latest release, please, 0.16.0
The code I had written earlier had a stupid mistake where I got the argument order wrong in str_starts_with()
! https://github.com/BrianHenryIE/strauss/commit/ccac6afa6d46b278911872e5e4c00c5b6004675f
@BrianHenryIE It works! You're my hero!
@TimothyBJacobs Thanks for the tip with the authoritative class maps. I also had the same problem with the newer version 0.16.0
. Can anybody in this issue explain to me why this is necessary? I read the code in the autoloader files from composer, with and without authoritative class maps, and I don't understand why not using authoritative class maps creates âclass already declaredâ errors.
@TimothyBJacobs I'd like to use this with normal class maps (not authoritative). Do you know why this is causing âclass already declaredâ errors? I'd be willing to help fix this, but I first need to understand why this is happening.
After adding strauss, it seems that I need to use both the composer autoload and the strauss autoload.
The staruss process doesn't generate autoload data for my own classes / namespace.
Is there a setting I need add so that I only need to use one autoloader? Or am I missing something ?
Thanks