-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Jakub Vojacek <jakub@motv.eu> Co-authored-by: Markus Staab <maggus.staab@googlemail.com> Co-authored-by: jakubvojacek <jakubvojacek@users.noreply.github.com>
- Loading branch information
1 parent
89474dc
commit dc579c0
Showing
20 changed files
with
5,259 additions
and
880 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace staabm\PHPStanDba\DibiReflection; | ||
|
||
final class DibiReflection | ||
{ | ||
public function rewriteQuery(string $queryString): ?string | ||
{ | ||
$queryString = str_replace('%in', '(1)', $queryString); | ||
$queryString = str_replace('%lmt', 'LIMIT 1', $queryString); | ||
$queryString = str_replace('%ofs', ', 1', $queryString); | ||
$queryString = preg_replace('#%(i|s)#', "'1'", $queryString) ?? ''; | ||
$queryString = preg_replace('#%(t|d)#', '"2000-1-1"', $queryString) ?? ''; | ||
$queryString = preg_replace('#%(and|or)#', '(1 = 1)', $queryString) ?? ''; | ||
$queryString = preg_replace('#%~?like~?#', '"%1%"', $queryString) ?? ''; | ||
|
||
if (strpos($queryString, '%n') > 0) { | ||
$queryString = null; | ||
} elseif (strpos($queryString, '%ex') > 0) { | ||
$queryString = null; | ||
} elseif (0 !== preg_match('#^\s*(START|ROLLBACK|SET|SAVEPOINT|SHOW)#i', $queryString)) { | ||
$queryString = null; | ||
} | ||
|
||
return $queryString; | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
src/Extensions/DibiConnectionFetchDynamicReturnTypeExtension.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace staabm\PHPStanDba\Extensions; | ||
|
||
use PhpParser\Node\Expr; | ||
use PhpParser\Node\Expr\MethodCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Reflection\MethodReflection; | ||
use PHPStan\Type\ArrayType; | ||
use PHPStan\Type\Constant\ConstantArrayType; | ||
use PHPStan\Type\DynamicMethodReturnTypeExtension; | ||
use PHPStan\Type\IntegerType; | ||
use PHPStan\Type\MixedType; | ||
use PHPStan\Type\NullType; | ||
use PHPStan\Type\Type; | ||
use PHPStan\Type\TypeCombinator; | ||
use PHPStan\Type\UnionType; | ||
use staabm\PHPStanDba\DibiReflection\DibiReflection; | ||
use staabm\PHPStanDba\QueryReflection\QueryReflection; | ||
use staabm\PHPStanDba\QueryReflection\QueryReflector; | ||
use staabm\PHPStanDba\UnresolvableQueryException; | ||
|
||
final class DibiConnectionFetchDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension | ||
{ | ||
public function getClass(): string | ||
{ | ||
return \Dibi\Connection::class; | ||
} | ||
|
||
public function isMethodSupported(MethodReflection $methodReflection): bool | ||
{ | ||
return \in_array($methodReflection->getName(), [ | ||
'fetch', | ||
'fetchAll', | ||
'fetchPairs', | ||
'fetchSingle', | ||
], true); | ||
} | ||
|
||
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type | ||
{ | ||
$args = $methodCall->getArgs(); | ||
|
||
if (\count($args) < 1) { | ||
return null; | ||
} | ||
|
||
if ($scope->getType($args[0]->value) instanceof MixedType) { | ||
return null; | ||
} | ||
|
||
try { | ||
return $this->inferType($methodReflection, $args[0]->value, $scope); | ||
} catch (UnresolvableQueryException $exception) { | ||
// simulation not possible.. use default value | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private function inferType(MethodReflection $methodReflection, Expr $queryExpr, Scope $scope): ?Type | ||
{ | ||
$queryReflection = new QueryReflection(); | ||
$queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope); | ||
|
||
return $this->createFetchType($queryStrings, $methodReflection); | ||
} | ||
|
||
/** | ||
* @param iterable<string> $queryStrings | ||
*/ | ||
private function createFetchType(iterable $queryStrings, MethodReflection $methodReflection): ?Type | ||
{ | ||
$queryReflection = new QueryReflection(); | ||
$dibiReflection = new DibiReflection(); | ||
|
||
$fetchTypes = []; | ||
foreach ($queryStrings as $queryString) { | ||
$queryString = $dibiReflection->rewriteQuery($queryString); | ||
if (null === $queryString) { | ||
continue; | ||
} | ||
|
||
$resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_ASSOC); | ||
|
||
if (null === $resultType) { | ||
return null; | ||
} | ||
|
||
$fetchResultType = $this->reduceResultType($methodReflection, $resultType); | ||
if (null === $fetchResultType) { | ||
return null; | ||
} | ||
|
||
$fetchTypes[] = $fetchResultType; | ||
} | ||
|
||
if (\count($fetchTypes) > 1) { | ||
return TypeCombinator::union(...$fetchTypes); | ||
} | ||
if (1 === \count($fetchTypes)) { | ||
return $fetchTypes[0]; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private function reduceResultType(MethodReflection $methodReflection, Type $resultType): ?Type | ||
{ | ||
$methodName = $methodReflection->getName(); | ||
|
||
if ('fetch' === $methodName) { | ||
return new UnionType([new NullType(), $resultType]); | ||
} elseif ('fetchAll' === $methodName) { | ||
return new ArrayType(new IntegerType(), $resultType); | ||
} elseif ('fetchPairs' === $methodName && $resultType instanceof ConstantArrayType && 2 === \count($resultType->getValueTypes())) { | ||
return new ArrayType($resultType->getValueTypes()[0], $resultType->getValueTypes()[1]); | ||
} elseif ('fetchSingle' === $methodName && $resultType instanceof ConstantArrayType && 1 === \count($resultType->getValueTypes())) { | ||
return new UnionType([new NullType(), $resultType->getValueTypes()[0]]); | ||
} | ||
|
||
return null; | ||
} | ||
} |
Oops, something went wrong.