Skip to content

Commit 108305e

Browse files
Allow pinning the preferred implementations in composer.json
1 parent d35947f commit 108305e

File tree

14 files changed

+159
-17
lines changed

14 files changed

+159
-17
lines changed

.github/workflows/checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313

1414
steps:
1515
- name: Checkout code
16-
uses: actions/checkout@v2
16+
uses: actions/checkout@v3
1717

1818
- name: Roave BC Check
1919
uses: "docker://nyholm/roave-bc-check-ga"

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616

1717
steps:
1818
- name: Checkout code
19-
uses: actions/checkout@v2
19+
uses: actions/checkout@v3
2020

2121
- name: Setup PHP
2222
uses: shivammathur/setup-php@v2
@@ -42,7 +42,7 @@ jobs:
4242

4343
steps:
4444
- name: Checkout code
45-
uses: actions/checkout@v2
45+
uses: actions/checkout@v3
4646

4747
- name: Setup PHP
4848
uses: shivammathur/setup-php@v2
@@ -67,7 +67,7 @@ jobs:
6767

6868
steps:
6969
- name: Checkout code
70-
uses: actions/checkout@v2
70+
uses: actions/checkout@v3
7171

7272
- name: Setup PHP
7373
uses: shivammathur/setup-php@v2

.github/workflows/installation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979

8080
steps:
8181
- name: Checkout code
82-
uses: actions/checkout@v2
82+
uses: actions/checkout@v3
8383

8484
- name: Setup PHP
8585
uses: shivammathur/setup-php@v2

.github/workflows/plugin.yml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,28 @@ jobs:
1818

1919
steps:
2020
- name: Checkout code
21-
uses: actions/checkout@v2
21+
uses: actions/checkout@v3
2222

2323
- name: Setup PHP
2424
uses: shivammathur/setup-php@v2
2525
with:
2626
php-version: 7.1
2727
tools: composer:${{ matrix.composer }}
2828

29-
- name: Check Plugin
29+
- name: Check Auto-install
3030
run: |
31-
mkdir /tmp/plugin
31+
mkdir /tmp/plugin-auto-install
3232
# replace the relative path for the repository url with an absolute path for composer v1 compatibility
33-
jq '.repositories[0].url="'$(pwd)'"' tests/plugin/composer.json > /tmp/plugin/composer.json
34-
cd /tmp/plugin
33+
jq '.repositories[0].url="'$(pwd)'"' tests/plugin/auto-install/composer.json > /tmp/plugin-auto-install/composer.json
34+
cd /tmp/plugin-auto-install
3535
composer update
3636
composer show http-interop/http-factory-guzzle -q
37+
38+
- name: Check Pinning
39+
run: |
40+
cp -a tests/plugin/pinning /tmp/plugin-pinning
41+
# replace the relative path for the repository url with an absolute path for composer v1 compatibility
42+
jq '.repositories[0].url="'$(pwd)'"' tests/plugin/pinning/composer.json > /tmp/plugin-pinning/composer.json
43+
cd /tmp/plugin-pinning
44+
composer update
45+
[ 'Slim\Psr7\Factory\RequestFactory' == $(php test.php) ]

.github/workflows/static.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313

1414
steps:
1515
- name: Checkout code
16-
uses: actions/checkout@v2
16+
uses: actions/checkout@v3
1717

1818
- name: PHP-CS-Fixer
1919
uses: docker://oskarstark/php-cs-fixer-ga

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## 1.17.0 - 2023-XX-XX
4+
5+
- [#232](https://github.com/php-http/discovery/pull/232) - Allow pinning the preferred implementations in composer.json
6+
37
## 1.16.0 - 2023-04-26
48

59
- [#225](https://github.com/php-http/discovery/pull/225) - Remove support for the abandoned Zend Diactoros which has been replaced with Laminas Diactoros; marked the zend library as conflict in composer.json to avoid confusion

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
"autoload": {
3434
"psr-4": {
3535
"Http\\Discovery\\": "src/"
36-
}
36+
},
37+
"exclude-from-classmap": [
38+
"src/Composer/Plugin.php"
39+
]
3740
},
3841
"autoload-dev": {
3942
"psr-4": {

src/ClassDiscovery.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ abstract class ClassDiscovery
2222
* @var array
2323
*/
2424
private static $strategies = [
25+
Strategy\GeneratedDiscoveryStrategy::class,
2526
Strategy\CommonClassesStrategy::class,
2627
Strategy\CommonPsr17ClassesStrategy::class,
2728
Strategy\PuliBetaStrategy::class,
@@ -54,10 +55,17 @@ protected static function findOneByType($type)
5455
return $class;
5556
}
5657

58+
static $skipStrategy;
59+
$skipStrategy ?? $skipStrategy = self::safeClassExists(Strategy\GeneratedDiscoveryStrategy::class) ? false : Strategy\GeneratedDiscoveryStrategy::class;
60+
5761
$exceptions = [];
5862
foreach (self::$strategies as $strategy) {
63+
if ($skipStrategy === $strategy) {
64+
continue;
65+
}
66+
5967
try {
60-
$candidates = call_user_func($strategy.'::getCandidates', $type);
68+
$candidates = $strategy::getCandidates($type);
6169
} catch (StrategyUnavailableException $e) {
6270
if (!isset(self::$deprecatedStrategies[$strategy])) {
6371
$exceptions[] = $e;

src/Composer/Plugin.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Composer\Repository\RepositorySet;
1919
use Composer\Script\Event;
2020
use Composer\Script\ScriptEvents;
21+
use Composer\Util\Filesystem;
2122
use Http\Discovery\ClassDiscovery;
2223

2324
/**
@@ -98,9 +99,30 @@ class Plugin implements PluginInterface, EventSubscriberInterface
9899
'http-interop/http-factory-slim' => 'slim/slim:^3',
99100
];
100101

102+
private const INTERFACE_MAP = [
103+
'php-http/async-client-implementation' => [
104+
'Http\Client\HttpAsyncClient',
105+
],
106+
'php-http/client-implementation' => [
107+
'Http\Client\HttpClient',
108+
],
109+
'psr/http-client-implementation' => [
110+
'Psr\Http\Client\ClientInterface',
111+
],
112+
'psr/http-factory-implementation' => [
113+
'Psr\Http\Message\RequestFactoryInterface',
114+
'Psr\Http\Message\ResponseFactoryInterface',
115+
'Psr\Http\Message\ServerRequestFactoryInterface',
116+
'Psr\Http\Message\StreamFactoryInterface',
117+
'Psr\Http\Message\UploadedFileFactoryInterface',
118+
'Psr\Http\Message\UriFactoryInterface',
119+
],
120+
];
121+
101122
public static function getSubscribedEvents(): array
102123
{
103124
return [
125+
ScriptEvents::PRE_AUTOLOAD_DUMP => 'preAutoloadDump',
104126
ScriptEvents::POST_UPDATE_CMD => 'postUpdate',
105127
];
106128
}
@@ -334,6 +356,65 @@ public function getMissingRequires(InstalledRepositoryInterface $repo, array $re
334356
return $missingRequires;
335357
}
336358

359+
public function preAutoloadDump(Event $event)
360+
{
361+
$filesystem = new Filesystem();
362+
// Double realpath() on purpose, see https://bugs.php.net/72738
363+
$vendorDir = $filesystem->normalizePath(realpath(realpath($event->getComposer()->getConfig()->get('vendor-dir'))));
364+
$filesystem->ensureDirectoryExists($vendorDir.'/composer');
365+
$pinned = $event->getComposer()->getPackage()->getExtra()['discovery'] ?? [];
366+
$candidates = [];
367+
368+
$allInterfaces = array_merge(...array_values(self::INTERFACE_MAP));
369+
foreach ($pinned as $abstraction => $class) {
370+
if (isset(self::INTERFACE_MAP[$abstraction])) {
371+
$interfaces = self::INTERFACE_MAP[$abstraction];
372+
} elseif (false !== $k = array_search($abstraction, $allInterfaces, true)) {
373+
$interfaces = [$allInterfaces[$k]];
374+
} else {
375+
throw new \UnexpectedValueException(sprintf('Invalid "extra.discovery" pinned in composer.json: "%s" is not one of ["%s"].', $abstraction, implode('", "', array_keys(self::INTERFACE_MAP))));
376+
}
377+
378+
foreach ($interfaces as $interface) {
379+
$candidates[] = sprintf("case %s: return [['class' => %s]];\n", var_export($interface, true), var_export($class, true));
380+
}
381+
}
382+
383+
if (!$candidates) {
384+
return;
385+
}
386+
387+
$candidates = implode(' ', $candidates);
388+
$code = <<<EOPHP
389+
<?php
390+
391+
namespace Http\Discovery\Strategy;
392+
393+
class GeneratedDiscoveryStrategy implements DiscoveryStrategy
394+
{
395+
public static function getCandidates(\$type)
396+
{
397+
switch (\$type) {
398+
$candidates
399+
default: return [];
400+
}
401+
}
402+
}
403+
404+
EOPHP
405+
;
406+
$file = $vendorDir.'/composer/GeneratedDiscoveryStrategy.php';
407+
408+
if (!file_exists($file) || $code !== file_get_contents($file)) {
409+
file_put_contents($file, $code);
410+
}
411+
412+
$rootPackage = $event->getComposer()->getPackage();
413+
$autoload = $rootPackage->getAutoload();
414+
$autoload['classmap'][] = $vendorDir.'/composer/GeneratedDiscoveryStrategy.php';
415+
$rootPackage->setAutoload($autoload);
416+
}
417+
337418
private function updateComposerJson(array $missingRequires, bool $sortPackages)
338419
{
339420
$file = Factory::getComposerFile();

tests/Composer/PluginTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ public static function provideMissingRequires()
8181
yield 'move-to-require' => [$expected, $repo, $rootRequires, []];
8282

8383
$package = new Package('symfony/symfony', '1.0.0.0', '1.0');
84-
$package->setReplaces([new Link('symfony/symfony', 'symfony/http-client', new Constraint(Constraint::STR_OP_GE, '1'))]);
84+
$package->setReplaces([
85+
'symfony/http-client' => new Link('symfony/symfony', 'symfony/http-client', new Constraint(Constraint::STR_OP_GE, '1'))
86+
]);
8587

8688
$repo = new InstalledArrayRepository([
8789
'php-http/discovery' => new Package('php-http/discovery', '1.0.0.0', '1.0'),

tests/plugin/.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
/vendor
2-
/composer.lock
1+
vendor
2+
composer.lock

tests/plugin/composer.json renamed to tests/plugin/auto-install/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"repositories": [
33
{
44
"type": "path",
5-
"url": "../..",
5+
"url": "../../..",
66
"options": {
77
"versions": {
88
"php-http/discovery": "99.99.x-dev"

tests/plugin/pinning/composer.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"repositories": [
3+
{
4+
"type": "path",
5+
"url": "../../..",
6+
"options": {
7+
"versions": {
8+
"php-http/discovery": "99.99.x-dev"
9+
}
10+
}
11+
}
12+
],
13+
"require": {
14+
"nyholm/psr7": "*",
15+
"php-http/discovery": "99.99.x-dev",
16+
"slim/psr7": "*"
17+
},
18+
"config": {
19+
"allow-plugins": {
20+
"php-http/discovery": true
21+
}
22+
},
23+
"extra": {
24+
"discovery": {
25+
"Psr\\Http\\Message\\RequestFactoryInterface": "Slim\\Psr7\\Factory\\RequestFactory"
26+
}
27+
}
28+
}

tests/plugin/pinning/test.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
use Http\Discovery\Psr17FactoryDiscovery;
4+
5+
require __DIR__.'/vendor/autoload.php';
6+
7+
echo get_class(Psr17FactoryDiscovery::findRequestFactory())."\n";

0 commit comments

Comments
 (0)