Skip to content

Commit

Permalink
Merge pull request #284 from shochdoerfer/feature/prefer_local_extens…
Browse files Browse the repository at this point in the history
…ion_class

Ignore ext autoloader for local classes
  • Loading branch information
shochdoerfer authored Jan 21, 2023
2 parents d00470f + bfe87d0 commit 1bcdba3
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 34 deletions.
3 changes: 2 additions & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,15 @@ services:
class: bitExpert\PHPStan\Magento\Autoload\ExtensionInterfaceAutoloader
arguments:
cache: @autoloaderCache
attributeDataProvider: @extensionAttributeDataProvider
classLoaderProvider: @classLoaderProvider
attributeDataProvider: @extensionAttributeDataProvider
tags:
- phpstan.magento.autoloader
extensionAutoloader:
class: bitExpert\PHPStan\Magento\Autoload\ExtensionAutoloader
arguments:
cache: @autoloaderCache
classLoaderProvider: @classLoaderProvider
attributeDataProvider: @extensionAttributeDataProvider
tags:
- phpstan.magento.autoloader
23 changes: 16 additions & 7 deletions src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace bitExpert\PHPStan\Magento\Autoload;

use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
use bitExpert\PHPStan\Magento\Autoload\DataProvider\ExtensionAttributeDataProvider;
use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\DocBlock\Tag\ParamTag;
Expand All @@ -28,6 +29,10 @@ class ExtensionAutoloader implements Autoloader
* @var Cache
*/
private $cache;
/**
* @var ClassLoaderProvider
*/
private $classLoaderProvider;
/**
* @var ExtensionAttributeDataProvider
*/
Expand All @@ -37,13 +42,16 @@ class ExtensionAutoloader implements Autoloader
* ExtensionAutoloader constructor.
*
* @param Cache $cache
* @param ClassLoaderProvider $classLoaderProvider
* @param ExtensionAttributeDataProvider $attributeDataProvider
*/
public function __construct(
Cache $cache,
ClassLoaderProvider $classLoaderProvider,
ExtensionAttributeDataProvider $attributeDataProvider
) {
$this->cache = $cache;
$this->classLoaderProvider = $classLoaderProvider;
$this->attributeDataProvider = $attributeDataProvider;
}

Expand All @@ -53,17 +61,18 @@ public function autoload(string $class): void
return;
}

$cachedFilename = $this->cache->load($class, '');
if ($cachedFilename === null) {
try {
// fix for PHPStan 1.7.5 and later: Classes generated by autoloaders are supposed to "win" against
// local classes in your project. We need to check first if classes exists locally before generating them!
$pathToLocalClass = $this->classLoaderProvider->findFile($class);
if ($pathToLocalClass === false) {
$pathToLocalClass = $this->cache->load($class, '');
if ($pathToLocalClass === null) {
$this->cache->save($class, '', $this->getFileContents($class));
$cachedFilename = $this->cache->load($class, '');
} catch (\Exception $e) {
return;
$pathToLocalClass = $this->cache->load($class, '');
}
}

require_once($cachedFilename);
require_once($pathToLocalClass);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ class ExtensionInterfaceAutoloader implements Autoloader
* @var Cache
*/
private $cache;
/**
* @var ExtensionAttributeDataProvider
*/
private $attributeDataProvider;
/**
* @var ClassLoaderProvider
*/
private $classLoaderProvider;
/**
* @var ExtensionAttributeDataProvider
*/
private $attributeDataProvider;

/**
* ExtensionInterfaceAutoloader constructor.
Expand All @@ -46,12 +46,12 @@ class ExtensionInterfaceAutoloader implements Autoloader
*/
public function __construct(
Cache $cache,
ExtensionAttributeDataProvider $attributeDataProvider,
ClassLoaderProvider $classLoaderProvider
ClassLoaderProvider $classLoaderProvider,
ExtensionAttributeDataProvider $attributeDataProvider
) {
$this->cache = $cache;
$this->attributeDataProvider = $attributeDataProvider;
$this->classLoaderProvider = $classLoaderProvider;
$this->attributeDataProvider = $attributeDataProvider;
}

public function autoload(string $interfaceName): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace bitExpert\PHPStan\Magento\Autoload;

use bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage;
use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
use bitExpert\PHPStan\Magento\Autoload\DataProvider\ExtensionAttributeDataProvider;
use org\bovigo\vfs\vfsStream;
use PHPStan\Cache\Cache;
Expand All @@ -14,6 +15,10 @@ class ExtensionAutoloaderUnitTest extends TestCase
* @var Cache|\PHPUnit\Framework\MockObject\MockObject
*/
private $cache;
/**
* @var ClassLoaderProvider|\PHPUnit\Framework\MockObject\MockObject
*/
private $classLoader;
/**
* @var ExtensionAttributeDataProvider|\PHPUnit\Framework\MockObject\MockObject
*/
Expand All @@ -26,9 +31,11 @@ class ExtensionAutoloaderUnitTest extends TestCase
protected function setUp(): void
{
$this->cache = $this->createMock(Cache::class);
$this->classLoader = $this->createMock(ClassLoaderProvider::class);
$this->extAttrDataProvider = $this->createMock(ExtensionAttributeDataProvider::class);
$this->autoloader = new ExtensionAutoloader(
$this->cache,
$this->classLoader,
$this->extAttrDataProvider
);
}
Expand All @@ -38,17 +45,38 @@ protected function setUp(): void
*/
public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void
{
$this->classLoader->expects(self::never())
->method('findFile');
$this->cache->expects(self::never())
->method('load');

$this->autoloader->autoload('SomeClass');
}

/**
* @test
*/
public function autoloaderPrefersLocalFile(): void
{
$this->classLoader->expects(self::once())
->method('findFile')
->willReturn(__DIR__ . '/HelperExtension.php');
$this->cache->expects(self::never())
->method('load');

$this->autoloader->autoload(HelperExtension::class);

self::assertTrue(class_exists(HelperExtension::class, false));
}

/**
* @test
*/
public function autoloaderUsesCachedFileWhenFound(): void
{
$this->classLoader->expects(self::once())
->method('findFile')
->willReturn(false);
$this->cache->expects(self::once())
->method('load')
->willReturn(__DIR__ . '/HelperExtension.php');
Expand All @@ -66,18 +94,21 @@ public function autoloaderUsesCachedFileWhenFound(): void
*/
public function autoloadGeneratesInterfaceWhenNotCached(): void
{
$this->classLoader->expects(self::once())
->method('findFile')
->willReturn(false);
$this->extAttrDataProvider->expects(self::once())
->method('getAttributesForInterface')
->willReturn(['attr' => 'string']);

$className = 'MyUncachedExtension';
// since the generated class implements an interface, we need to make it available here, otherwise
// the autoloader will fail with an exception that the interface can't be found!
class_alias(HelperExtensionInterface::class, 'MyUncachedExtensionInterface');

$root = vfsStream::setup('test');
$cache = new Cache(new FileCacheStorage($root->url() . '/tmp/cache/PHPStan'));
$autoloader = new ExtensionAutoloader($cache, $this->extAttrDataProvider);

$this->extAttrDataProvider->expects(self::once())
->method('getAttributesForInterface')
->willReturn(['attr' => 'string']);
$autoloader = new ExtensionAutoloader($cache, $this->classLoader, $this->extAttrDataProvider);

$autoloader->autoload($className);
static::assertTrue(class_exists($className));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ExtensionInterfaceAutoloaderUnitTest extends TestCase
/**
* @var ClassLoaderProvider|\PHPUnit\Framework\MockObject\MockObject
*/
private $classyDataProvider;
private $classLoader;
/**
* @var ExtensionInterfaceAutoloader
*/
Expand All @@ -32,12 +32,12 @@ class ExtensionInterfaceAutoloaderUnitTest extends TestCase
protected function setUp(): void
{
$this->cache = $this->createMock(Cache::class);
$this->classLoader = $this->createMock(ClassLoaderProvider::class);
$this->extAttrDataProvider = $this->createMock(ExtensionAttributeDataProvider::class);
$this->classyDataProvider = $this->createMock(ClassLoaderProvider::class);
$this->autoloader = new ExtensionInterfaceAutoloader(
$this->cache,
$this->extAttrDataProvider,
$this->classyDataProvider
$this->classLoader,
$this->extAttrDataProvider
);
}

Expand All @@ -46,7 +46,7 @@ protected function setUp(): void
*/
public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void
{
$this->classyDataProvider->expects(self::never())
$this->classLoader->expects(self::never())
->method('findFile');
$this->cache->expects(self::never())
->method('load');
Expand All @@ -59,7 +59,7 @@ public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void
*/
public function autoloaderPrefersLocalFile(): void
{
$this->classyDataProvider->expects(self::once())
$this->classLoader->expects(self::once())
->method('findFile')
->willReturn(__DIR__ . '/HelperExtensionInterface.php');
$this->cache->expects(self::never())
Expand All @@ -75,7 +75,7 @@ public function autoloaderPrefersLocalFile(): void
*/
public function autoloaderUsesCachedFileWhenFound(): void
{
$this->classyDataProvider->expects(self::once())
$this->classLoader->expects(self::once())
->method('findFile')
->willReturn(false);
$this->cache->expects(self::once())
Expand All @@ -100,14 +100,14 @@ public function autoloadDoesNotGenerateInterfaceWhenNoAttributesExist(): void

$interfaceName = 'NonExistentExtensionInterface';

$this->classyDataProvider->expects(self::once())
$this->classLoader->expects(self::once())
->method('findFile')
->willReturn(false);
$this->cache->expects(self::once())
->method('load')
->willReturn(null);

$this->classyDataProvider->expects(self::once())
$this->classLoader->expects(self::once())
->method('exists')
->willReturn(false);

Expand All @@ -123,13 +123,13 @@ public function autoloadGeneratesInterfaceWhenNotCached(): void

$root = vfsStream::setup('test');
$cache = new Cache(new FileCacheStorage($root->url() . '/tmp/cache/PHPStan'));
$autoloader = new ExtensionInterfaceAutoloader($cache, $this->extAttrDataProvider, $this->classyDataProvider);
$autoloader = new ExtensionInterfaceAutoloader($cache, $this->classLoader, $this->extAttrDataProvider);

$this->classyDataProvider->expects(self::once())
$this->classLoader->expects(self::once())
->method('findFile')
->willReturn(false);

$this->classyDataProvider->expects(self::once())
$this->classLoader->expects(self::once())
->method('exists')
->willReturn(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,15 @@ public function provideAutoloaders(): array
[new MockAutoloader()],
[new ProxyAutoloader($cache, $classLoader)],
[new TestFrameworkAutoloader(__DIR__)],
[new ExtensionAutoloader(
$cache,
new ClassLoaderProvider(__DIR__),
new ExtensionAttributeDataProvider(__DIR__)
)],
[new ExtensionInterfaceAutoloader(
$cache,
new ExtensionAttributeDataProvider(__DIR__),
new ClassLoaderProvider(__DIR__)
new ClassLoaderProvider(__DIR__),
new ExtensionAttributeDataProvider(__DIR__)
)]
];
}
Expand Down

0 comments on commit 1bcdba3

Please sign in to comment.