Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

function doFoo(A $a) {

}
32 changes: 32 additions & 0 deletions src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Reflection\BetterReflection\SourceLocator;

use PhpParser\NodeTraverser;
use PHPStan\Cache\Cache;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\File\FileReader;
Expand All @@ -13,16 +14,44 @@
final class FileNodesFetcher
{

private const CACHE_ROOT_KEY = 'vendor-reflections';
private const CACHE_VARIABLE_KEY = 'v2';

private array $data = [];

public function __construct(
private CachingVisitor $cachingVisitor,
#[AutowiredParameter(ref: '@defaultAnalysisParser')]
private Parser $parser,
private Cache $cache,
)
{
}

private function persist(): void
{
$this->cache->save(self::CACHE_ROOT_KEY, self::CACHE_VARIABLE_KEY, $this->data);
}

private function loadCache(): void
{
$cached = $this->cache->load(self::CACHE_ROOT_KEY, self::CACHE_VARIABLE_KEY);
if ($cached !== null) {
$this->data = $cached;
}
}


public function fetchNodes(string $fileName): FetchedNodesResult
{
if ($this->data === []) {
$this->loadCache();
}

if (isset($this->data[$fileName])) {
return unserialize($this->data[$fileName]);
}

$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor($this->cachingVisitor);

Expand All @@ -44,6 +73,9 @@ public function fetchNodes(string $fileName): FetchedNodesResult

$this->cachingVisitor->reset($fileName, $contents);

$this->data[$fileName] = serialize($result);
$this->persist();

return $result;
}

Expand Down
136 changes: 136 additions & 0 deletions src/Reflection/ReflectionProvider/CachedVendorReflectionProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection\ReflectionProvider;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Cache\Cache;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ConstantReflection;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\NamespaceAnswerer;
use PHPStan\Reflection\ReflectionProvider;
use function strtolower;

final class CachedVendorReflectionProvider implements ReflectionProvider
{

/** @var array<string, bool> */
private array $hasClasses = [];

/** @var array<string, ClassReflection> */
private array $classes = [];

/** @var array<string, string> */
private array $classNames = [];

private const CACHE_ROOT_KEY = 'vendor-reflections';
private const CACHE_VARIABLE_KEY = 'v1';

private bool $needsUpdate = false;

public function __construct(
private ReflectionProvider $provider,
private Cache $cache,
)
{
register_shutdown_function(function() {
if ($this->needsUpdate) {
$this->persist();
}
});

$this->loadCache();
}

private function persist(): void
{
$this->cache->save(self::CACHE_ROOT_KEY, self::CACHE_VARIABLE_KEY, [
$this->hasClasses,
$this->classNames
]);
}

private function loadCache(): void
{
$cached = $this->cache->load(self::CACHE_ROOT_KEY, self::CACHE_VARIABLE_KEY);
if ($cached !== null) {
[
$this->hasClasses,
$this->classNames
] = $cached;
}
}

public function hasClass(string $className): bool
{
if (isset($this->hasClasses[$className])) {
return $this->hasClasses[$className];
}
$this->needsUpdate = true;

return $this->hasClasses[$className] = $this->provider->hasClass($className);
}

public function getClass(string $className): ClassReflection
{
$lowerClassName = strtolower($className);
if (isset($this->classes[$lowerClassName])) {
return $this->classes[$lowerClassName];
}

return $this->classes[$lowerClassName] = $this->provider->getClass($className);
}

public function getClassName(string $className): string
{
$lowerClassName = strtolower($className);
if (isset($this->classNames[$lowerClassName])) {
return $this->classNames[$lowerClassName];
}
$this->needsUpdate = true;

return $this->classNames[$lowerClassName] = $this->provider->getClassName($className);
}

public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection
{
return $this->provider->getAnonymousClassReflection($classNode, $scope);
}

public function getUniversalObjectCratesClasses(): array
{
return $this->provider->getUniversalObjectCratesClasses();
}

public function hasFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool
{
return $this->provider->hasFunction($nameNode, $namespaceAnswerer);
}

public function getFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): FunctionReflection
{
return $this->provider->getFunction($nameNode, $namespaceAnswerer);
}

public function resolveFunctionName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string
{
return $this->provider->resolveFunctionName($nameNode, $namespaceAnswerer);
}

public function hasConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool
{
return $this->provider->hasConstant($nameNode, $namespaceAnswerer);
}

public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ConstantReflection
{
return $this->provider->getConstant($nameNode, $namespaceAnswerer);
}

public function resolveConstantName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string
{
return $this->provider->resolveConstantName($nameNode, $namespaceAnswerer);
}

}
Loading