Skip to content

Commit c98fc6c

Browse files
Introduce PDO::connect dynamicMethodReturnType
1 parent 2776f5d commit c98fc6c

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

src/Php/PhpVersion.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,4 +405,9 @@ public function supportsBcMathNumberOperatorOverloading(): bool
405405
return $this->versionId >= 80400;
406406
}
407407

408+
public function hasPDOSubclasses(): bool
409+
{
410+
return $this->versionId >= 80400;
411+
}
412+
408413
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\StaticCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Php\PhpVersion;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
11+
use PHPStan\Type\ObjectType;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\TypeCombinator;
14+
use function count;
15+
use function str_starts_with;
16+
17+
/**
18+
* @see https://wiki.php.net/rfc/pdo_driver_specific_subclasses
19+
* @see https://github.com/php/php-src/pull/12804
20+
*/
21+
#[AutowiredService]
22+
final class PDOConnectReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
23+
{
24+
25+
public function __construct(private PhpVersion $phpVersion)
26+
{
27+
}
28+
29+
public function getClass(): string
30+
{
31+
return 'PDO';
32+
}
33+
34+
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
35+
{
36+
return $this->phpVersion->hasPDOSubclasses() && $methodReflection->getName() === 'connect';
37+
}
38+
39+
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
40+
{
41+
if (count($methodCall->getArgs()) < 1) {
42+
return null;
43+
}
44+
45+
$valueType = $scope->getType($methodCall->getArgs()[0]->value);
46+
$constantStrings = $valueType->getConstantStrings();
47+
if (count($constantStrings) === 0) {
48+
return null;
49+
}
50+
51+
$subclasses = [];
52+
foreach ($constantStrings as $constantString) {
53+
if (str_starts_with($constantString->getValue(), 'mysql:')) {
54+
$subclasses['PDO\Mysql'] = 'PDO\Mysql';
55+
} elseif (str_starts_with($constantString->getValue(), 'firebird:')) {
56+
$subclasses['PDO\Firebird'] = 'PDO\Firebird';
57+
} elseif (str_starts_with($constantString->getValue(), 'dblib:')) {
58+
$subclasses['PDO\Dblib'] = 'PDO\Dblib';
59+
} elseif (str_starts_with($constantString->getValue(), 'odbc:')) {
60+
$subclasses['PDO\Odbc'] = 'PDO\Odbc';
61+
} elseif (str_starts_with($constantString->getValue(), 'pgsql:')) {
62+
$subclasses['PDO\Pgsql'] = 'PDO\Pgsql';
63+
} elseif (str_starts_with($constantString->getValue(), 'sqlite:')) {
64+
$subclasses['PDO\Sqlite'] = 'PDO\Sqlite';
65+
} else {
66+
return null;
67+
}
68+
}
69+
70+
$returnTypes = [];
71+
foreach ($subclasses as $class) {
72+
$returnTypes[] = new ObjectType($class);
73+
}
74+
75+
return TypeCombinator::union(...$returnTypes);
76+
}
77+
78+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php // lint >= 8.4
2+
3+
namespace PdoConnectPHP84;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param 'mysql:foo'|'pgsql:foo' $mysqlOrPgsql
9+
* @param 'mysql:foo'|'foo:foo' $mysqlOrFoo
10+
*/
11+
function test(
12+
string $string,
13+
string $mysqlOrPgsql,
14+
string $mysqlOrFoo,
15+
) {
16+
assertType('PDO\Mysql', \PDO::connect('mysql:foo'));
17+
assertType('PDO\Firebird', \PDO::connect('firebird:foo'));
18+
assertType('PDO\Dblib', \PDO::connect('dblib:foo'));
19+
assertType('PDO\Odbc', \PDO::connect('odbc:foo'));
20+
assertType('PDO\Pgsql', \PDO::connect('pgsql:foo'));
21+
assertType('PDO\Sqlite', \PDO::connect('sqlite:foo'));
22+
23+
assertType('PDO', \PDO::connect($string));
24+
assertType('PDO\Mysql|PDO\Pgsql', \PDO::connect($mysqlOrPgsql));
25+
assertType('PDO', \PDO::connect($mysqlOrFoo));
26+
}

0 commit comments

Comments
 (0)