Skip to content

Commit 243018a

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

File tree

4 files changed

+112
-0
lines changed

4 files changed

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