Skip to content

Commit 8c48178

Browse files
committed
bug #379 More robust autoloader detection (GawainLynch)
This PR was squashed before being merged into the 1.0-dev branch (closes #379). Discussion ---------- More robust autoloader detection When the project, or dependent package, adds a custom autoloader, e.g. PhpStan, the added autoloader will be returned instead of the one containing the project's autoloader when `ComposerAutoloaderFinder` iterates the autoloader list leading to the mysterious error: ``` Could not determine where to locate the new class "App\Controller\DeliciousPuppyController", maybe try with a full namespace like "\My\Full\Namespace\DeliciousPuppyController" ``` Fixes #196 Fixes #231 Fixes #313 Related #378 Commits ------- 719979e Return objects from methods & switch fallback logic 22f5a3b Search for matching PSR-0 or PSR-4 namespaces, and default to component's autoloader 07830f9 Only return the autoloader when it contains the root namespace
2 parents c0dc197 + 719979e commit 8c48178

File tree

6 files changed

+106
-16
lines changed

6 files changed

+106
-16
lines changed

src/DependencyInjection/MakerExtension.php

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public function load(array $configs, ContainerBuilder $container)
3939

4040
$rootNamespace = trim($config['root_namespace'], '\\');
4141

42+
$autoloaderFinderDefinition = $container->getDefinition('maker.autoloader_finder');
43+
$autoloaderFinderDefinition->replaceArgument(0, $rootNamespace);
44+
4245
$makeCommandDefinition = $container->getDefinition('maker.generator');
4346
$makeCommandDefinition->replaceArgument(1, $rootNamespace);
4447

src/Resources/config/services.xml

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
<argument>%kernel.project_dir%</argument>
1414
</service>
1515

16-
<service id="maker.autoloader_finder" class="Symfony\Bundle\MakerBundle\Util\ComposerAutoloaderFinder" />
16+
<service id="maker.autoloader_finder" class="Symfony\Bundle\MakerBundle\Util\ComposerAutoloaderFinder" >
17+
<argument /> <!-- root namespace -->
18+
</service>
1719

1820
<service id="maker.autoloader_util" class="Symfony\Bundle\MakerBundle\Util\AutoloaderUtil">
1921
<argument type="service" id="maker.autoloader_finder" />

src/Util/ComposerAutoloaderFinder.php

+63-11
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,29 @@
1919
*/
2020
class ComposerAutoloaderFinder
2121
{
22+
private $rootNamespace;
23+
2224
/**
2325
* @var ClassLoader|null
2426
*/
2527
private $classLoader = null;
2628

29+
public function __construct(string $rootNamespace)
30+
{
31+
$this->rootNamespace = [
32+
'psr0' => rtrim($rootNamespace, '\\'),
33+
'psr4' => rtrim($rootNamespace, '\\').'\\',
34+
];
35+
}
36+
2737
public function getClassLoader(): ClassLoader
2838
{
2939
if (null === $this->classLoader) {
3040
$this->classLoader = $this->findComposerClassLoader();
3141
}
3242

3343
if (null === $this->classLoader) {
34-
throw new \Exception('Composer ClassLoader not found!');
44+
throw new \Exception("Could not find a Composer autoloader that autoloads from '{$this->rootNamespace['psr4']}'");
3545
}
3646

3747
return $this->classLoader;
@@ -45,19 +55,61 @@ private function findComposerClassLoader()
4555
$autoloadFunctions = spl_autoload_functions();
4656

4757
foreach ($autoloadFunctions as $autoloader) {
48-
if (\is_array($autoloader) && isset($autoloader[0]) && \is_object($autoloader[0])) {
49-
if ($autoloader[0] instanceof ClassLoader) {
50-
return $autoloader[0];
51-
}
52-
53-
if ($autoloader[0] instanceof DebugClassLoader
54-
&& \is_array($autoloader[0]->getClassLoader())
55-
&& $autoloader[0]->getClassLoader()[0] instanceof ClassLoader) {
56-
return $autoloader[0]->getClassLoader()[0];
57-
}
58+
$classLoader = $this->extractComposerClassLoader($autoloader);
59+
if (null === $classLoader) {
60+
continue;
61+
}
62+
63+
$finalClassLoader = $this->locateMatchingClassLoader($classLoader);
64+
if (null !== $finalClassLoader) {
65+
return $finalClassLoader;
66+
}
67+
}
68+
69+
return null;
70+
}
71+
72+
/**
73+
* @return ClassLoader|null
74+
*/
75+
private function extractComposerClassLoader(array $autoloader)
76+
{
77+
if (isset($autoloader[0]) && \is_object($autoloader[0])) {
78+
if ($autoloader[0] instanceof ClassLoader) {
79+
return $autoloader[0];
80+
}
81+
if ($autoloader[0] instanceof DebugClassLoader
82+
&& \is_array($autoloader[0]->getClassLoader())
83+
&& $autoloader[0]->getClassLoader()[0] instanceof ClassLoader) {
84+
return $autoloader[0]->getClassLoader()[0];
5885
}
5986
}
6087

6188
return null;
6289
}
90+
91+
/**
92+
* @return ClassLoader|null
93+
*/
94+
private function locateMatchingClassLoader(ClassLoader $classLoader)
95+
{
96+
$makerClassLoader = null;
97+
foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) {
98+
if ('Symfony\\Bundle\\MakerBundle\\' === $prefix) {
99+
$makerClassLoader = $classLoader;
100+
}
101+
if (0 === strpos($this->rootNamespace['psr4'], $prefix)) {
102+
return $classLoader;
103+
}
104+
}
105+
106+
foreach ($classLoader->getPrefixes() as $prefix => $paths) {
107+
if (0 === strpos($this->rootNamespace['psr0'], $prefix)) {
108+
return $classLoader;
109+
}
110+
}
111+
112+
// We can default to using the autoloader containing this component if none are matching.
113+
return $makerClassLoader ?: null;
114+
}
63115
}

tests/Maker/FunctionalTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public function getCommandTests()
205205
])
206206
->addExtraDependencies('orm')
207207
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeFormForEntity')
208-
];
208+
];
209209

210210
yield 'form_for_non_entity_dto' => [MakerTestDetails::createTest(
211211
$this->getMakerInstance(MakeForm::class),

tests/Util/AutoloaderUtilTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private function createComposerAutoloaderFinder(array $composerJsonParams = null
8787
/** @var \PHPUnit_Framework_MockObject_MockObject|ComposerAutoloaderFinder $finder */
8888
$finder = $this
8989
->getMockBuilder(ComposerAutoloaderFinder::class)
90+
->setConstructorArgs(['App\\'])
9091
->getMock();
9192

9293
$finder

tests/Util/ComposerAutoloaderFinderTest.php

+35-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class ComposerAutoloaderFinderTest extends TestCase
1010
{
1111
public static $getSplAutoloadFunctions = 'spl_autoload_functions';
1212

13+
private static $rootNamespace = 'Fake\\It\\Till\\You\\Make\\It\\';
14+
1315
/**
1416
* @after
1517
*/
@@ -18,9 +20,20 @@ public function resetAutoloadFunction()
1820
self::$getSplAutoloadFunctions = 'spl_autoload_functions';
1921
}
2022

21-
public function testGetClassLoader()
23+
public function providerNamespaces(): \Generator
2224
{
23-
$loader = (new ComposerAutoloaderFinder())->getClassLoader();
25+
yield 'Configured PSR-0' => [rtrim(static::$rootNamespace, '\\'), null];
26+
yield 'Configured PSR-4' => [null, static::$rootNamespace];
27+
yield 'Fallback default' => [null, 'Symfony\\Bundle\\MakerBundle\\'];
28+
}
29+
30+
/**
31+
* @dataProvider providerNamespaces
32+
*/
33+
public function testGetClassLoader($psr0, $psr4)
34+
{
35+
$this->setupAutoloadFunctions($psr0, $psr4);
36+
$loader = (new ComposerAutoloaderFinder(static::$rootNamespace))->getClassLoader();
2437

2538
$this->assertInstanceOf(ClassLoader::class, $loader, 'Wrong ClassLoader found');
2639
}
@@ -35,7 +48,26 @@ public function testGetClassLoaderWhenItIsEmpty()
3548
};
3649

3750
// throws \Exception
38-
(new ComposerAutoloaderFinder())->getClassLoader();
51+
(new ComposerAutoloaderFinder(static::$rootNamespace))->getClassLoader();
52+
}
53+
54+
/**
55+
* @param string|null $psr0
56+
* @param string|null $psr4
57+
*/
58+
private function setupAutoloadFunctions($psr0, $psr4)
59+
{
60+
self::$getSplAutoloadFunctions = function () use ($psr0, $psr4) {
61+
$loader = new ClassLoader();
62+
if ($psr0) {
63+
$loader->add($psr0, __DIR__);
64+
}
65+
if ($psr4) {
66+
$loader->addPsr4($psr4, __DIR__);
67+
}
68+
69+
return [[$loader, 'loadClass']];
70+
};
3971
}
4072
}
4173

0 commit comments

Comments
 (0)