Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hack new PHPStan 2.x compatible ReflectionCache #716

Merged
merged 5 commits into from
Nov 17, 2024
Merged
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
640 changes: 14 additions & 626 deletions .phpstan-dba-mysqli.cache

Large diffs are not rendered by default.

638 changes: 13 additions & 625 deletions .phpstan-dba-pdo-mysql.cache

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions config/extensions.neon
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ services:
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: staabm\PHPStanDba\QueryReflection\DIContainerBridge
10 changes: 10 additions & 0 deletions src/Ast/PreviousConnectingVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use staabm\PHPStanDba\QueryReflection\DIContainerBridge;
use function array_pop;

final class PreviousConnectingVisitor extends NodeVisitorAbstract
Expand All @@ -21,6 +22,15 @@ final class PreviousConnectingVisitor extends NodeVisitorAbstract

private ?Node $previous;

// a dummy property to force instantiation of DIContainerBridge
// for use all over the phpstan-dba codebase
private DIContainerBridge $containerBridge; // @phpstan-ignore property.onlyWritten

public function __construct(DIContainerBridge $dummyParameter)
{
$this->containerBridge = $dummyParameter;
}

public function beforeTraverse(array $nodes)
{
$this->stack = [];
Expand Down
33 changes: 33 additions & 0 deletions src/QueryReflection/DIContainerBridge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace staabm\PHPStanDba\QueryReflection;

use PHPStan\DependencyInjection\Container;

/**
* Utility class to access the PHPStan container from phpstan-dba internal classes which cannot access the DI because of BC.
*
* @internal
*/
final class DIContainerBridge
{
private static Container $container;

public function __construct(Container $container)
{
self::$container = $container;
}

/**
* @phpstan-template T of object
* @phpstan-param class-string<T> $className
* @phpstan-return T
* @return mixed
*/
public static function getByType(string $className): object
{
return self::$container->getByType($className);
}
}
10 changes: 6 additions & 4 deletions src/QueryReflection/ReflectionCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

final class ReflectionCache
{
private const SCHEMA_VERSION = 'v12-new-major';
private const SCHEMA_VERSION = 'v12-new-cache5';

private string $cacheFile;

Expand All @@ -38,9 +38,12 @@ final class ReflectionCache
*/
private static $lockHandle;

private TypeSerializer $typeSerializer;

private function __construct(string $cacheFile)
{
$this->cacheFile = $cacheFile;
$this->typeSerializer = new TypeSerializer();

if (null === self::$lockHandle) {
// prevent parallel phpstan-worker-process from writing into the cache file at the same time
Expand Down Expand Up @@ -169,7 +172,7 @@ private function readCachedRecords(bool $useReadLock): ?array
throw new ShouldNotHappenException();
}

return $cache['records']; // @phpstan-ignore-line
return $this->typeSerializer->unserialize($cache['records']); // @phpstan-ignore-line
}

public function persist(): void
Expand Down Expand Up @@ -203,10 +206,9 @@ public function persist(): void
$cacheContent = '<?php return ' . var_export([
'schemaVersion' => self::SCHEMA_VERSION,
'schemaHash' => $this->schemaHash,
'records' => $newRecords,
'records' => $this->typeSerializer->serialize($newRecords),
'runtimeConfig' => QueryReflection::getRuntimeConfiguration()->toArray(),
], true) . ';';

if (false === file_put_contents($this->cacheFile, $cacheContent, LOCK_EX)) {
throw new DbaException(sprintf('Unable to write cache file "%s"', $this->cacheFile));
}
Expand Down
85 changes: 85 additions & 0 deletions src/QueryReflection/TypeSerializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace staabm\PHPStanDba\QueryReflection;

use PHPStan\PhpDoc\TypeStringResolver;
use PHPStan\PhpDocParser\Printer\Printer;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Type;
use staabm\PHPStanDba\Error;

final class TypeSerializer
{
private ?Printer $printer = null;

private ?TypeStringResolver $typeStringResolver = null;

/**
* @param array<string, array{error?: ?Error, result?: array<QueryReflector::FETCH_TYPE*, ?Type>}> $records
* @return array<string, array{error?: ?Error, result?: array<QueryReflector::FETCH_TYPE*, ?array<string>>}>
*/
public function serialize(array $records): array
{
// serialize types, see https://github.com/phpstan/phpstan/discussions/12046
foreach ($records as &$record) {
if (! array_key_exists('result', $record)) {
continue;
}
$record['result'] = array_map(function (?Type $type) {
if ($type === null) {
return null;
}

return [
'type-description' => $this->getPhpdocPrinter()->print($type->toPhpDocNode()),
];
}, $record['result']);
}

return $records; // @phpstan-ignore return.type
}

/**
* @param array<string, array{error?: ?Error, result?: array<QueryReflector::FETCH_TYPE*, ?array<string>>}> $records
* @return array<string, array{error?: ?Error, result?: array<QueryReflector::FETCH_TYPE*, ?Type>}>
*/
public function unserialize(array $records): array
{
// serialize types, see https://github.com/phpstan/phpstan/discussions/12046
foreach ($records as &$record) {
if (! array_key_exists('result', $record)) {
continue;
}
$record['result'] = array_map(function ($serialized): Type {
if (is_array($serialized) && array_key_exists('type-description', $serialized)) {
try {
return $this->getTypeStringResolver()->resolve($serialized['type-description']);
} catch (\Throwable $e) {
throw new ShouldNotHappenException("unexpected type " . print_r($serialized, true) . ': ' . $e->getMessage());
}
}
throw new ShouldNotHappenException("unexpected type " . print_r($serialized, true));
}, $record['result']);
}

return $records; // @phpstan-ignore return.type
}

private function getPhpdocPrinter(): Printer
{
if ($this->printer === null) {
$this->printer = DIContainerBridge::getByType(Printer::class);
}
return $this->printer;
}

private function getTypeStringResolver(): TypeStringResolver
{
if ($this->typeStringResolver === null) {
$this->typeStringResolver = DIContainerBridge::getByType(TypeStringResolver::class);
}
return $this->typeStringResolver;
}
}
7 changes: 5 additions & 2 deletions src/TypeMapping/MysqliTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ final class MysqliTypeMapper
public function __construct(?DbaApi $dbaApi)
{
$constants = get_defined_constants(true);
if (! array_key_exists('mysqli', $constants) || ! is_array($constants['mysqli'])) {
if (
! array_key_exists('mysqli', $constants)
|| ! is_array($constants['mysqli']) // @phpstan-ignore-line
) {
$constants['mysqli'] = [];
}

Expand All @@ -34,7 +37,7 @@ public function __construct(?DbaApi $dbaApi)
continue;
}

if (! is_string($c)) {
if (! is_string($c)) { // @phpstan-ignore-line
continue;
}

Expand Down
Loading