Skip to content

Commit f396bb6

Browse files
committed
Add DatabaseDriverMap and DoctrineLikeConnection classes with type alias support and testing for key-of and value-of annotation type checking baseline test
1 parent 0027c24 commit f396bb6

3 files changed

Lines changed: 250 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Types;
6+
7+
class DatabaseDriverMap
8+
{
9+
private const DRIVER_MAP = [
10+
'pdo_mysql' => 'PDO\MySQL\Driver',
11+
'pdo_sqlite' => 'PDO\SQLite\Driver',
12+
];
13+
14+
public const PUBLIC_MAP = [
15+
'read' => 1,
16+
'write' => 2,
17+
];
18+
19+
/**
20+
* @param key-of<self::DRIVER_MAP> $driver
21+
*/
22+
public static function checkStaticDriverKey(string $driver): string
23+
{
24+
return $driver;
25+
}
26+
27+
/**
28+
* @param value-of<self::DRIVER_MAP> $driverClass
29+
*/
30+
public static function checkStaticDriverValue(string $driverClass): string
31+
{
32+
return $driverClass;
33+
}
34+
35+
/**
36+
* @param key-of<self::DRIVER_MAP> $driver
37+
*/
38+
public function checkInstanceDriverKey(string $driver): string
39+
{
40+
return $driver;
41+
}
42+
43+
/**
44+
* @param key-of<self::PUBLIC_MAP> $action
45+
*/
46+
private function checkPrivateMethodKey(string $action): string
47+
{
48+
return $action;
49+
}
50+
51+
public function proxyPrivateMethod(string $action): string
52+
{
53+
return $this->checkPrivateMethodKey($action);
54+
}
55+
56+
/**
57+
* @param key-of<array{id: int, name: string}> $key
58+
*/
59+
public static function checkArrayShapeKey(string $key): string
60+
{
61+
return $key;
62+
}
63+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Types;
6+
7+
/**
8+
* Mock imitating Doctrine DBAL's DriverManager using type aliases
9+
*
10+
* @phpstan-type ConnectionParams array{
11+
* driver: key-of<DatabaseDriverMap::DRIVER_MAP>,
12+
* driverClass?: value-of<DatabaseDriverMap::DRIVER_MAP>
13+
* }
14+
* @phpstan-type LocalParams array{
15+
* action: key-of<self::LOCAL_ACTIONS>
16+
* }
17+
*/
18+
class DoctrineLikeConnection
19+
{
20+
/**
21+
* @param ConnectionParams $params
22+
*/
23+
public function connect(array $params): bool
24+
{
25+
return true;
26+
}
27+
28+
private const LOCAL_ACTIONS = ['start' => 1, 'stop' => 0];
29+
30+
/**
31+
* @param LocalParams $params
32+
*/
33+
public function localAction(array $params): bool
34+
{
35+
return true;
36+
}
37+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use TypePHP\Tests\Fixtures\Types\DatabaseDriverMap;
6+
use TypePHP\Tests\Fixtures\Types\DoctrineLikeConnection;
7+
use TypePHP\Tests\Fixtures\Types\StatusEnum;
8+
9+
/**
10+
* External function evaluating key-of on a public class constant
11+
*
12+
* @param key-of<DatabaseDriverMap::PUBLIC_MAP> $action
13+
*/
14+
function testExternalPublicConstKey(string $action): string
15+
{
16+
return $action;
17+
}
18+
19+
/**
20+
* @param key-of<StatusEnum> $statusName
21+
*/
22+
function testEnumKeyOf(string $statusName): string
23+
{
24+
return $statusName;
25+
}
26+
27+
/**
28+
* @param value-of<StatusEnum> $statusValue
29+
*/
30+
function testEnumValueOf(string $statusValue): string
31+
{
32+
return $statusValue;
33+
}
34+
35+
describe('key-of<T> and value-of<T> Annotations', function () {
36+
37+
describe('Array Constants (e.g. self::DRIVER_MAP)', function () {
38+
39+
test('accepts valid key-of on a private constant from a static method', function () {
40+
expect(DatabaseDriverMap::checkStaticDriverKey('pdo_mysql'))->toBe('pdo_mysql');
41+
expect(DatabaseDriverMap::checkStaticDriverKey('pdo_sqlite'))->toBe('pdo_sqlite');
42+
});
43+
44+
test('throws TypeError on invalid key-of on a private constant from a static method', function () {
45+
expect(fn () => DatabaseDriverMap::checkStaticDriverKey('pdo_pgsql'))
46+
->toThrow(TypeError::class, 'must be a key of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::DRIVER_MAP');
47+
});
48+
49+
test('accepts valid key-of on a private constant from an instance method', function () {
50+
$dbMap = new DatabaseDriverMap();
51+
expect($dbMap->checkInstanceDriverKey('pdo_mysql'))->toBe('pdo_mysql');
52+
});
53+
54+
test('throws TypeError on invalid key-of on a public constant from a private method', function () {
55+
$dbMap = new DatabaseDriverMap();
56+
57+
expect($dbMap->proxyPrivateMethod('read'))->toBe('read');
58+
59+
expect(fn () => $dbMap->proxyPrivateMethod('delete'))
60+
->toThrow(TypeError::class, 'must be a key of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::PUBLIC_MAP');
61+
});
62+
63+
test('accepts valid key-of on a public constant from an external function', function () {
64+
expect(testExternalPublicConstKey('write'))->toBe('write');
65+
66+
expect(fn () => testExternalPublicConstKey('execute'))
67+
->toThrow(TypeError::class, 'must be a key of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::PUBLIC_MAP');
68+
});
69+
70+
test('accepts valid value-of on a private constant array', function () {
71+
expect(DatabaseDriverMap::checkStaticDriverValue('PDO\MySQL\Driver'))->toBe('PDO\MySQL\Driver');
72+
});
73+
74+
test('throws TypeError on invalid value-of on a private constant array', function () {
75+
expect(fn () => DatabaseDriverMap::checkStaticDriverValue('PDO\PgSQL\Driver'))
76+
->toThrow(TypeError::class, 'must be a value of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::DRIVER_MAP');
77+
});
78+
79+
});
80+
81+
describe('Enums (e.g. StatusEnum)', function () {
82+
83+
test('key-of<Enum> strictly checks against the Enum CASE NAMES', function () {
84+
expect(testEnumKeyOf('Active'))->toBe('Active');
85+
expect(testEnumKeyOf('Pending'))->toBe('Pending');
86+
87+
expect(fn () => testEnumKeyOf('Archived'))
88+
->toThrow(TypeError::class, 'must be a key of enum TypePHP\Tests\Fixtures\Types\StatusEnum');
89+
90+
expect(fn () => testEnumKeyOf('active'))
91+
->toThrow(TypeError::class, "must be a key of enum TypePHP\Tests\Fixtures\Types\StatusEnum, string 'active' given");
92+
});
93+
94+
test('value-of<Enum> strictly checks against the Enum BACKING VALUES', function () {
95+
expect(testEnumValueOf('active'))->toBe('active');
96+
expect(testEnumValueOf('pending'))->toBe('pending');
97+
98+
expect(fn () => testEnumValueOf('archived'))
99+
->toThrow(TypeError::class, 'must be a value of enum TypePHP\Tests\Fixtures\Types\StatusEnum');
100+
101+
expect(fn () => testEnumValueOf('Active'))
102+
->toThrow(TypeError::class, "must be a value of enum TypePHP\Tests\Fixtures\Types\StatusEnum, string 'Active' given");
103+
});
104+
105+
});
106+
107+
describe('Array Shapes (e.g. key-of<array{id: int, name: string}>)', function () {
108+
109+
test('accepts valid string keys of an inline array shape', function () {
110+
expect(DatabaseDriverMap::checkArrayShapeKey('id'))->toBe('id');
111+
expect(DatabaseDriverMap::checkArrayShapeKey('name'))->toBe('name');
112+
});
113+
114+
test('throws TypeError on invalid string key of an inline array shape', function () {
115+
expect(fn () => DatabaseDriverMap::checkArrayShapeKey('invalid_key'))
116+
->toThrow(TypeError::class, 'must be a key of the specified array shape');
117+
});
118+
119+
});
120+
121+
describe('Type Aliases (@phpstan-type nested shapes)', function () {
122+
123+
test('validates key-of and value-of correctly when deeply nested inside an array shape type alias', function () {
124+
$conn = new DoctrineLikeConnection();
125+
126+
expect($conn->connect([
127+
'driver' => 'pdo_mysql',
128+
'driverClass' => 'PDO\MySQL\Driver'
129+
]))->toBeTrue();
130+
131+
expect(fn () => $conn->connect(['driver' => 'pdo_pgsql']))
132+
->toThrow(TypeError::class, "['driver'] must be a key of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::DRIVER_MAP");
133+
134+
expect(fn () => $conn->connect([
135+
'driver' => 'pdo_mysql',
136+
'driverClass' => 'PDO\PgSQL\Driver'
137+
]))->toThrow(TypeError::class, "['driverClass'] must be a value of TypePHP\Tests\Fixtures\Types\DatabaseDriverMap::DRIVER_MAP");
138+
});
139+
140+
test('validates key-of with self:: reference natively inside a local type alias', function () {
141+
$conn = new DoctrineLikeConnection();
142+
143+
expect($conn->localAction(['action' => 'start']))->toBeTrue();
144+
145+
expect(fn () => $conn->localAction(['action' => 'pause']))
146+
->toThrow(TypeError::class, "['action'] must be a key of TypePHP\Tests\Fixtures\Types\DoctrineLikeConnection::LOCAL_ACTIONS");
147+
});
148+
149+
});
150+
});

0 commit comments

Comments
 (0)