Skip to content

Commit 758a2c9

Browse files
author
Sergey Rabochiy
committed
Refactor AutoloaderUtil
1 parent 5423250 commit 758a2c9

File tree

5 files changed

+217
-58
lines changed

5 files changed

+217
-58
lines changed

src/Resources/config/services.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
<argument>%kernel.project_dir%</argument>
1414
</service>
1515

16-
<service id="maker.autoloader_util" class="Symfony\Bundle\MakerBundle\Util\AutoloaderUtil" />
16+
<service id="maker.autoloader_finder" class="Symfony\Bundle\MakerBundle\Util\ComposerAutoloaderFinder" />
17+
18+
<service id="maker.autoloader_util" class="Symfony\Bundle\MakerBundle\Util\AutoloaderUtil">
19+
<argument type="service" id="maker.autoloader_finder" />
20+
</service>
1721

1822
<service id="maker.event_registry" class="Symfony\Bundle\MakerBundle\EventRegistry">
1923
<argument type="service" id="event_dispatcher" />

src/Util/AutoloaderUtil.php

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Bundle\MakerBundle\Util;
1313

1414
use Composer\Autoload\ClassLoader;
15-
use Symfony\Component\Debug\DebugClassLoader;
1615

1716
/**
1817
* @author Ryan Weaver <[email protected]>
@@ -22,9 +21,14 @@
2221
class AutoloaderUtil
2322
{
2423
/**
25-
* @var ClassLoader
24+
* @var ComposerAutoloaderFinder
2625
*/
27-
private $classLoader;
26+
private $autoloaderFinder;
27+
28+
public function __construct(ComposerAutoloaderFinder $autoloaderFinder)
29+
{
30+
$this->autoloaderFinder = $autoloaderFinder;
31+
}
2832

2933
/**
3034
* Returns the relative path to where a new class should live.
@@ -37,25 +41,27 @@ class AutoloaderUtil
3741
*/
3842
public function getPathForFutureClass(string $className)
3943
{
44+
$classLoader = $this->getClassLoader();
45+
4046
// lookup is obviously modeled off of Composer's autoload logic
41-
foreach ($this->getClassLoader()->getPrefixesPsr4() as $prefix => $paths) {
47+
foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) {
4248
if (0 === strpos($className, $prefix)) {
4349
return $paths[0].'/'.str_replace('\\', '/', str_replace($prefix, '', $className)).'.php';
4450
}
4551
}
4652

47-
foreach ($this->getClassLoader()->getPrefixes() as $prefix => $paths) {
53+
foreach ($classLoader->getPrefixes() as $prefix => $paths) {
4854
if (0 === strpos($className, $prefix)) {
4955
return $paths[0].'/'.str_replace('\\', '/', $className).'.php';
5056
}
5157
}
5258

53-
if ($this->getClassLoader()->getFallbackDirsPsr4()) {
54-
return $this->getClassLoader()->getFallbackDirsPsr4()[0].'/'.str_replace('\\', '/', $className).'.php';
59+
if ($classLoader->getFallbackDirsPsr4()) {
60+
return $classLoader->getFallbackDirsPsr4()[0].'/'.str_replace('\\', '/', $className).'.php';
5561
}
5662

57-
if ($this->getClassLoader()->getFallbackDirs()) {
58-
return $this->getClassLoader()->getFallbackDirs()[0].'/'.str_replace('\\', '/', $className).'.php';
63+
if ($classLoader->getFallbackDirs()) {
64+
return $classLoader->getFallbackDirs()[0].'/'.str_replace('\\', '/', $className).'.php';
5965
}
6066

6167
return null;
@@ -72,29 +78,35 @@ public function getNamespacePrefixForClass(string $className): string
7278
return '';
7379
}
7480

75-
private function getClassLoader(): ClassLoader
81+
/**
82+
* Returns if the namespace is configured by composer autoloader
83+
*
84+
* @param string $namespace
85+
*
86+
* @return bool
87+
*/
88+
public function isNamespaceConfiguredToAutoload(string $namespace)
7689
{
77-
if (null === $this->classLoader) {
78-
$autoloadFunctions = spl_autoload_functions();
79-
foreach ($autoloadFunctions as $autoloader) {
80-
if (is_array($autoloader) && isset($autoloader[0]) && is_object($autoloader[0])) {
81-
if ($autoloader[0] instanceof ClassLoader) {
82-
$this->classLoader = $autoloader[0];
83-
break;
84-
}
85-
if ($autoloader[0] instanceof DebugClassLoader
86-
&& is_array($autoloader[0]->getClassLoader())
87-
&& $autoloader[0]->getClassLoader()[0] instanceof ClassLoader) {
88-
$this->classLoader = $autoloader[0]->getClassLoader()[0];
89-
break;
90-
}
91-
}
90+
$namespace = trim($namespace, '\\').'\\';
91+
$classLoader = $this->getClassLoader();
92+
93+
foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) {
94+
if (0 === strpos($namespace, $prefix)) {
95+
return true;
9296
}
93-
if (null === $this->classLoader) {
94-
throw new \Exception('Composer ClassLoader not found!');
97+
}
98+
99+
foreach ($classLoader->getPrefixes() as $prefix => $paths) {
100+
if (0 === strpos($namespace, $prefix)) {
101+
return true;
95102
}
96103
}
97104

98-
return $this->classLoader;
105+
return false;
106+
}
107+
108+
private function getClassLoader(): ClassLoader
109+
{
110+
return $this->autoloaderFinder->getClassLoader();
99111
}
100112
}

src/Util/ComposerAutoloaderFinder.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Util;
13+
14+
use Composer\Autoload\ClassLoader;
15+
use Symfony\Component\Debug\DebugClassLoader;
16+
17+
/**
18+
* @internal
19+
*/
20+
class ComposerAutoloaderFinder
21+
{
22+
/**
23+
* @var ClassLoader|null
24+
*/
25+
private $classLoader = null;
26+
27+
public function getClassLoader(): ClassLoader
28+
{
29+
if (null === $this->classLoader) {
30+
$this->classLoader = $this->findComposerClassLoader();
31+
}
32+
33+
if (null === $this->classLoader) {
34+
throw new \Exception('Composer ClassLoader not found!');
35+
}
36+
37+
return $this->classLoader;
38+
}
39+
40+
/**
41+
* @return ClassLoader|null
42+
*/
43+
private function findComposerClassLoader()
44+
{
45+
$autoloadFunctions = spl_autoload_functions();
46+
47+
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+
}
59+
}
60+
61+
return null;
62+
}
63+
}

tests/Util/AutoloaderUtilTest.php

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Composer\Autoload\ClassLoader;
66
use PHPUnit\Framework\TestCase;
77
use Symfony\Bundle\MakerBundle\Util\AutoloaderUtil;
8+
use Symfony\Bundle\MakerBundle\Util\ComposerAutoloaderFinder;
89
use Symfony\Component\Filesystem\Filesystem;
910

1011
class AutoloaderUtilTest extends TestCase
@@ -28,8 +29,36 @@ public static function setupPaths()
2829

2930
public function testGetPathForFutureClass()
3031
{
31-
$classLoader = new ClassLoader();
32-
$composerJson = [
32+
$autoloaderUtil = new AutoloaderUtil($this->createComposerAutoloaderFinder());
33+
34+
foreach ($this->getPathForFutureClassTests() as $className => $expectedPath) {
35+
$this->assertSame(
36+
str_replace('\\', '/', self::$currentRootDir.'/'.$expectedPath),
37+
// normalize slashes for Windows comparison
38+
str_replace('\\', '/', $autoloaderUtil->getPathForFutureClass($className)),
39+
sprintf('class "%s" should have been in path "%s"', $className, $expectedPath)
40+
);
41+
}
42+
}
43+
44+
public function testIsNamespaceConfiguredToAutoload()
45+
{
46+
$autoloaderUtil = new AutoloaderUtil($this->createComposerAutoloaderFinder());
47+
48+
foreach ($this->isNamespaceConfiguredToAutoloadTests() as $namespace => $expected) {
49+
$configured = $autoloaderUtil->isNamespaceConfiguredToAutoload($namespace);
50+
51+
if ($expected) {
52+
$this->assertTrue($configured, sprintf('namespace "%s" is not found but must be', $namespace));
53+
} else {
54+
$this->assertFalse($configured, sprintf('namespace "%s" is found but must not be', $namespace));
55+
}
56+
}
57+
}
58+
59+
private function createComposerAutoloaderFinder(array $composerJsonParams = null): ComposerAutoloaderFinder
60+
{
61+
$composerJsonParams = $composerJsonParams ?: [
3362
'autoload' => [
3463
'psr-4' => [
3564
'Also\\In\\Src\\' => '/src/SubDir',
@@ -43,7 +72,9 @@ public function testGetPathForFutureClass()
4372
],
4473
];
4574

46-
foreach ($composerJson['autoload'] as $psr => $dirs) {
75+
$classLoader = new ClassLoader();
76+
77+
foreach ($composerJsonParams['autoload'] as $psr => $dirs) {
4778
foreach ($dirs as $prefix => $path) {
4879
if ($psr == 'psr-4') {
4980
$classLoader->addPsr4($prefix, self::$currentRootDir.$path);
@@ -53,43 +84,43 @@ public function testGetPathForFutureClass()
5384
}
5485
}
5586

56-
$reflection = new \ReflectionClass(AutoloaderUtil::class);
57-
$property = $reflection->getProperty('classLoader');
58-
$property->setAccessible(true);
59-
60-
$autoloaderUtil = new AutoloaderUtil();
87+
/** @var \PHPUnit_Framework_MockObject_MockObject|ComposerAutoloaderFinder $finder */
88+
$finder = $this
89+
->getMockBuilder(ComposerAutoloaderFinder::class)
90+
->getMock();
6191

62-
$property->setValue($autoloaderUtil, $classLoader);
92+
$finder
93+
->method('getClassLoader')
94+
->willReturn($classLoader);
6395

64-
foreach ($this->getPathForFutureClassTests() as $className => $expectedPath) {
65-
$this->assertSame(
66-
str_replace('\\', '/', self::$currentRootDir.'/'.$expectedPath),
67-
// normalize slashes for Windows comparison
68-
str_replace('\\', '/', $autoloaderUtil->getPathForFutureClass($className)),
69-
sprintf('class "%s" should have been in path "%s"', $className, $expectedPath)
70-
);
71-
}
96+
return $finder;
7297
}
7398

74-
public function testCanFindClassLoader()
75-
{
76-
$reflection = new \ReflectionClass(AutoloaderUtil::class);
77-
$method = $reflection->getMethod('getClassLoader');
78-
$method->setAccessible(true);
79-
$autoloaderUtil = new AutoloaderUtil();
80-
$autoloader = $method->invoke($autoloaderUtil);
81-
$this->assertInstanceOf(ClassLoader::class, $autoloader, 'Wrong ClassLoader found');
82-
}
83-
84-
public function getPathForFutureClassTests()
99+
private function getPathForFutureClassTests()
85100
{
86101
return [
87102
'App\Foo' => 'src/Foo.php',
88103
'App\Entity\Product' => 'src/Entity/Product.php',
89104
'Totally\Weird' => 'fallback_dir/Totally/Weird.php',
90105
'Also\In\Src\Some\OtherClass' => 'src/SubDir/Some/OtherClass.php',
91106
'Other\Namespace\Admin\Foo' => 'lib/Admin/Foo.php',
92-
'Psr0\Package\Admin\Bar' => 'lib/other/Psr0/Package/Admin/Bar.php'
107+
'Psr0\Package\Admin\Bar' => 'lib/other/Psr0/Package/Admin/Bar.php',
108+
];
109+
}
110+
111+
private function isNamespaceConfiguredToAutoloadTests()
112+
{
113+
return [
114+
'App' => true,
115+
'App\\' => true,
116+
'\\App' => true,
117+
'\\App\\' => true,
118+
'App\\Entity' => true,
119+
'Also\\In\\Src\\Some' => true,
120+
'Other\\Namespace\\Admin' => true,
121+
'Psr0\\Package' => true,
122+
'Psr0\\Package\\Some' => true,
123+
'Unknown' => false,
93124
];
94125
}
95126
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\MakerBundle\Tests\Util;
4+
5+
use Composer\Autoload\ClassLoader;
6+
use PHPUnit\Framework\TestCase;
7+
use Symfony\Bundle\MakerBundle\Util\ComposerAutoloaderFinder;
8+
9+
class ComposerAutoloaderFinderTest extends TestCase
10+
{
11+
public static $getSplAutoloadFunctions = 'spl_autoload_functions';
12+
13+
/**
14+
* @after
15+
*/
16+
public function resetAutoloadFunction()
17+
{
18+
self::$getSplAutoloadFunctions = 'spl_autoload_functions';
19+
}
20+
21+
public function testGetClassLoader()
22+
{
23+
$loader = (new ComposerAutoloaderFinder())->getClassLoader();
24+
25+
$this->assertInstanceOf(ClassLoader::class, $loader, 'Wrong ClassLoader found');
26+
}
27+
28+
/**
29+
* @expectedException \Exception
30+
*/
31+
public function testGetClassLoaderWhenItIsEmpty()
32+
{
33+
self::$getSplAutoloadFunctions = function () {
34+
return [];
35+
};
36+
37+
// throws \Exception
38+
(new ComposerAutoloaderFinder())->getClassLoader();
39+
}
40+
}
41+
42+
namespace Symfony\Bundle\MakerBundle\Util;
43+
44+
use Symfony\Bundle\MakerBundle\Tests\Util\ComposerAutoloaderFinderTest;
45+
46+
function spl_autoload_functions()
47+
{
48+
return call_user_func_array(ComposerAutoloaderFinderTest::$getSplAutoloadFunctions, func_get_args());
49+
}

0 commit comments

Comments
 (0)