Skip to content

Commit 1b6430b

Browse files
committed
readme
1 parent abc0994 commit 1b6430b

File tree

1 file changed

+280
-11
lines changed

1 file changed

+280
-11
lines changed

README.md

Lines changed: 280 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,28 @@
55
* PHP >= 7.4
66
* Composer (PSR-4 Autoload)
77

8+
## Table of contents
9+
10+
* [How it works](##how-it-works)
11+
* [Quick start](##quick-start)
12+
* [Example](##example)
13+
* [Features](##features)
14+
* [Tests](##tests)
15+
16+
## How it works
17+
18+
+ parse generics classes;
19+
+ generate concrete classes based on them;
20+
+ say to "composer autoload" to load files from directory with generated classes first and then load classes from main directory.
21+
822
## Quick start
923

1024
Install library
1125
```bash
1226
composer require mrsuh/php-generics
1327
```
1428

15-
Add directory for generated files(`"cache/"`) to autoload (directory must be placed before the main directory)
29+
Add directory(`"cache/"`) to composer autoload PSR-4 for generated classes. It should be placed before the main directory.
1630
composer.json
1731
```json
1832
{
@@ -24,9 +38,25 @@ composer.json
2438
}
2539
```
2640

41+
Generate concrete classes from generic classes with `composer dump-generics` command
42+
```bash
43+
composer dump-generics -vv
44+
```
45+
46+
Generate vendor/autoload.php with `composer dump-autoload` command
47+
```bash
48+
composer dump-autoload
49+
```
50+
2751
## Example
2852

29-
Add generic class
53+
!! You can find repository with this example [here](https://github.com/mrsuh/php-generics-example).
54+
55+
For example, you need to add several PHP files:
56+
+ generic class `Box`
57+
+ class `Usage` for use generic class
58+
+ script with composer autoload and `Usage` class
59+
3060
src/Box.php
3161
```php
3262
<?php
@@ -47,7 +77,6 @@ class Box<T> {
4777
}
4878
```
4979

50-
Add usage generic class
5180
src/Usage.php
5281
```php
5382
<?php
@@ -69,7 +98,6 @@ class Usage {
6998
}
7099
```
71100

72-
Add test with Usage class and composer autoload
73101
bin/test.php
74102
```php
75103
<?php
@@ -82,12 +110,21 @@ $usage = new Usage();
82110
$usage->run();
83111
```
84112

85-
Generate concrete classes from generics
113+
Generate concrete classes from generic classes with `composer dump-generics` command
86114
```bash
87115
composer dump-generics -vv
88116
```
89117

90-
Dump autoload classes
118+
What the `composer dump-generics` command does?
119+
+ finds all generic uses in classes (`src/Usage.php` for example).
120+
+ generates concrete classes from generic classes with unique names based on name and arguments of generic class.
121+
+ replaces generic class names to concrete class names in places of use.
122+
123+
In this case should be generated:
124+
+ 2 concrete classes of generics `BoxForInt` and `BoxForString`;
125+
+ 1 concrete class `Usage` with replaced generics class names to concrete class names.
126+
127+
Generate vendor/autoload.php with `composer dump-autoload` command
91128
```bash
92129
composer dump-autoload
93130
```
@@ -97,22 +134,254 @@ Run bin/test.php script
97134
php bin/test.php
98135
```
99136

100-
See the [tests](./tests) folder for more examples.
137+
Composer autoload first checks the "cache" directory and then the "src" directory to load the classes.
138+
139+
## Feature
140+
141+
#### What syntax is used?
142+
143+
The [RFC](https://github.com/PHPGenerics/php-generics-rfc) does not define a specific syntax so i took [this one](https://github.com/PHPGenerics/php-generics-rfc/issues/45) implemented by Nikita Popov
144+
145+
Syntax example:
146+
```php
147+
<?php
148+
149+
namespace App;
150+
151+
class Generic<in T: Iface = int, out V: Iface = string> {
152+
153+
public function test(T $var): V {
154+
155+
}
156+
}
157+
```
158+
159+
#### Syntax problems
160+
161+
I had to upgrade [nikic/php-parser](https://github.com/nikic/PHP-Parser) for parse code with new syntax.<br>
162+
You can see [here](https://github.com/mrsuh/PHP-Parser/pull/1/files#diff-14ec37995c001c0c9808ab73668d64db5d1acc1ab0f60a360dcb9c611ecd57ea) the grammar changes that had to be made for support generics.
163+
164+
Parser use [PHP implementation](https://github.com/ircmaxell/PHP-Yacc) of [YACC](https://wikipedia.org/wiki/Yacc). <br>
165+
The YACC([LALR](https://wikipedia.org/wiki/LALR(1))) algorithm and current PHP syntax make it impossible to describe the full syntax of generics due to collisions.
166+
167+
Collision example:
168+
```php
169+
<?php
170+
171+
const FOO = 'FOO';
172+
const BAR = 'BAR';
173+
174+
var_dump(new \DateTime<FOO,BAR>('now')); // is it generic?
175+
var_dump( (new \DateTime < FOO) , ( BAR > 'now') ); // no, it doesn't
176+
```
177+
178+
[Solution options](https://github.com/PHPGenerics/php-generics-rfc/issues/35#issuecomment-571546650)
179+
180+
Therefore, nested generics are not currently supported.
181+
```php
182+
<?php
183+
184+
namespace App;
185+
186+
class Usage {
187+
public function run() {
188+
$map = new Map<Key<int>, Value<string>>();//not supported
189+
}
190+
}
191+
```
192+
193+
#### Parameter names have not special restrictions
194+
195+
```php
196+
<?php
197+
198+
namespace App;
199+
200+
class GenericClass<T, varType, myCoolLongParaterName> {
201+
private T $var1;
202+
private varType $var2;
203+
private myCoolLongParaterName $var3;
204+
}
205+
```
206+
207+
#### Several generic parameters support
208+
209+
```php
210+
<?php
211+
212+
namespace App;
213+
214+
class Map<keyType, valueType> {
215+
216+
private array $map;
217+
218+
public function set(keyType $key, valueType $value): void {
219+
$this->map[$key] = $value;
220+
}
221+
222+
public function get(keyType $key): ?valueType {
223+
return $this->map[$key] ?? null;
224+
}
225+
}
226+
```
227+
228+
#### Default generic parameter support
229+
230+
```php
231+
<?php
232+
233+
namespace App;
234+
235+
class Map<keyType = string, valueType = int> {
236+
237+
private array $map = [];
238+
239+
public function set(keyType $key, valueType $value): void {
240+
$this->map[$key] = $value;
241+
}
242+
243+
public function get(keyType $key): ?valueType {
244+
return $this->map[$key] ?? null;
245+
}
246+
}
247+
```
248+
249+
```php
250+
<?php
251+
252+
namespace App;
253+
254+
class Usage {
255+
public function run() {
256+
$map = new Map<>();//be sure to add "<>"
257+
$map->set('key', 1);
258+
var_dump($map->get('key'));
259+
}
260+
}
261+
```
262+
263+
#### Where in class can generics be used?
264+
265+
+ extends
266+
+ implements
267+
+ trait use
268+
+ property type
269+
+ method argument type
270+
+ method return type
271+
+ instanceof
272+
+ new
273+
+ class constants
274+
275+
An example of class that uses generics:
276+
```php
277+
<?php
278+
279+
namespace App;
280+
281+
use App\Entity\Cat;
282+
use App\Entity\Bird;
283+
use App\Entity\Dog;
284+
285+
class Test extends GenericClass<Cat> implements GenericInterface<Bird> {
286+
287+
use GenericTrait<Dog>;
288+
289+
private GenericClass<int>|GenericClass<Dog> $var;
290+
291+
public function test(GenericInterface<int>|GenericInterface<Dog> $var): GenericClass<string>|GenericClass<Bird> {
292+
293+
var_dump($var instanceof GenericInterface<int>);
294+
295+
var_dump(new GenericClass<int>::class);
296+
297+
var_dump(new GenericClass<array>::CONSTANT);
298+
299+
return new GenericClass<float>();
300+
}
301+
}
302+
```
303+
304+
#### Where in generic class can parameters be used?
305+
306+
+ extends
307+
+ implements
308+
+ trait use
309+
+ property type
310+
+ method argument type
311+
+ method return type
312+
+ instanceof
313+
+ new
314+
+ class constants
315+
316+
And example of generic class:
317+
```php
318+
<?php
319+
320+
namespace App;
321+
322+
class Test<T,V> extends GenericClass<T> implements GenericInterface<V> {
323+
324+
use GenericTrait<T>;
325+
use T;
326+
327+
private T|GenericClass<V> $var;
328+
329+
public function test(T|GenericInterface<V> $var): T|GenericClass<V> {
330+
331+
var_dump($var instanceof GenericInterface<V>);
332+
333+
var_dump($var instanceof T);
334+
335+
var_dump(new GenericClass<T>::class);
336+
337+
var_dump(T::class);
338+
339+
var_dump(new GenericClass<T>::CONSTANT);
340+
341+
var_dump(T::CONSTANT);
342+
343+
$obj1 = new T();
344+
$obj2 = new GenericClass<V>();
345+
346+
return $obj2;
347+
}
348+
}
349+
```
350+
351+
#### How fast is it?
352+
353+
All concrete classes are pre-generated and can be cached(should not affect performance).
354+
355+
Generating many concrete classes should negatively impact performance when:
356+
+ resolves concrete classes;
357+
+ storing concrete classes in memory;
358+
+ type checking for each concrete class.
359+
360+
I think it's all individual for a specific case.
361+
362+
#### Doesn't work without composer autoload
363+
364+
Autoload magic of concrete classes works with composer autoload only. <br>
365+
Nothing will work because of syntax error if you include file by "require"
366+
367+
#### Reflection
368+
369+
PHP does type checks in [runtime](https://github.com/PHPGenerics/php-generics-rfc/issues/43). <br>
370+
Therefore, all generics arguments [must me available](https://github.com/PHPGenerics/php-generics-rfc/blob/cc7219792a5b35226129d09536789afe20eac029/generics.txt#L426-L430) through reflection in runtime. <br>
371+
It can't be, because information about generics arguments is erased after concrete classes are generated.
101372

102373
## Tests
103374

104375
### How to run tests?
376+
105377
```bash
106378
php bin/test.php
107379
```
108380

109381
### How to add test?
382+
110383
+ Add directory 00-your-dir-name to ./tests
111384
+ Generate output files and check it
112385
```bash
113386
php bin/generate.php tests/000-your-dir-name/input tests/000-your-dir-name/output 'Test\'
114387
```
115-
+ Test output files
116-
```bash
117-
php bin/test.php
118-
```

0 commit comments

Comments
 (0)