Skip to content

Commit d3b4c28

Browse files
Introduce PDO::connect dynamicMethodReturnType
1 parent 02066c7 commit d3b4c28

File tree

4 files changed

+111
-0
lines changed

4 files changed

+111
-0
lines changed

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,11 @@ services:
15851585
tags:
15861586
- phpstan.functionParameterOutTypeExtension
15871587

1588+
-
1589+
class: PHPStan\Type\Php\PDOConnectReturnTypeExtension
1590+
tags:
1591+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
1592+
15881593
-
15891594
class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension
15901595
tags:

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