Skip to content

Commit 42279b7

Browse files
authored
Make ClassLike::from return type assert the subclass type (#154)
* Make ClassLike::from return type more strict * Throw exception instead of relying on PHP type error * Improve exception message and add test
1 parent 64cdb3e commit 42279b7

File tree

5 files changed

+55
-13
lines changed

5 files changed

+55
-13
lines changed

src/PhpGenerator/ClassLike.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,30 @@ abstract class ClassLike
3737
private ?PhpNamespace $namespace;
3838
private ?string $name;
3939

40-
41-
public static function from(string|object $class, bool $withBodies = false): self
40+
public static function from(string|object $class, bool $withBodies = false): static
4241
{
43-
return (new Factory)
42+
$instance = (new Factory)
4443
->fromClassReflection(new \ReflectionClass($class), $withBodies);
44+
45+
if (!$instance instanceof static) {
46+
$class = is_object($class) ? get_class($class) : $class;
47+
throw new Nette\InvalidArgumentException("'$class' cannot be represented with " . static::class . ". Call " . get_class($instance) . "::" . __FUNCTION__ . "() or " . __METHOD__ . "() instead.");
48+
}
49+
50+
return $instance;
4551
}
4652

4753

48-
public static function fromCode(string $code): self
54+
public static function fromCode(string $code): static
4955
{
50-
return (new Factory)
56+
$instance = (new Factory)
5157
->fromClassCode($code);
58+
59+
if (!$instance instanceof static) {
60+
throw new Nette\InvalidArgumentException("Provided code cannot be represented with " . static::class . ". Call " . get_class($instance) . "::" . __FUNCTION__ . "() or " . __METHOD__ . "() instead.");
61+
}
62+
63+
return $instance;
5264
}
5365

5466

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\PhpGenerator\ClassType;
6+
use Nette\PhpGenerator\EnumType;
7+
use Nette\PhpGenerator\InterfaceType;
8+
use Nette\PhpGenerator\TraitType;
9+
use Tester\Assert;
10+
11+
require __DIR__ . '/../bootstrap.php';
12+
require __DIR__ . '/fixtures/classes.php';
13+
14+
Assert::exception(function () {
15+
ClassType::from(Abc\Interface1::class);
16+
}, Nette\InvalidArgumentException::class, "'Abc\\Interface1' cannot be represented with Nette\\PhpGenerator\\ClassType. Call Nette\\PhpGenerator\\InterfaceType::from() or Nette\\PhpGenerator\\ClassLike::from() instead.");
17+
18+
Assert::exception(function () {
19+
TraitType::from(Abc\Class1::class);
20+
}, Nette\InvalidArgumentException::class, "'Abc\\Class1' cannot be represented with Nette\\PhpGenerator\\TraitType. Call Nette\\PhpGenerator\\ClassType::from() or Nette\\PhpGenerator\\ClassLike::from() instead.");
21+
22+
Assert::exception(function () {
23+
ClassType::fromCode("<?php interface I {}");
24+
}, Nette\InvalidArgumentException::class, "Provided code cannot be represented with Nette\\PhpGenerator\\ClassType. Call Nette\\PhpGenerator\\InterfaceType::fromCode() or Nette\\PhpGenerator\\ClassLike::fromCode() instead.");
25+
26+
Assert::exception(function () {
27+
InterfaceType::fromCode("<?php trait T {}");
28+
}, Nette\InvalidArgumentException::class, "Provided code cannot be represented with Nette\\PhpGenerator\\InterfaceType. Call Nette\\PhpGenerator\\TraitType::fromCode() or Nette\\PhpGenerator\\ClassLike::fromCode() instead.");

tests/PhpGenerator/ClassType.from.82.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
declare(strict_types=1);
88

99
use Nette\PhpGenerator\ClassType;
10+
use Nette\PhpGenerator\TraitType;
1011

1112
require __DIR__ . '/../bootstrap.php';
1213
require __DIR__ . '/fixtures/classes.82.php';
1314

1415
$res[] = ClassType::from(new Abc\Class13);
15-
$res[] = ClassType::from(Abc\Trait13::class);
16+
$res[] = TraitType::from(Abc\Trait13::class);
1617

1718
sameFile(__DIR__ . '/expected/ClassType.from.82.expect', implode("\n", $res));

tests/PhpGenerator/ClassType.from.phpt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77
declare(strict_types=1);
88

99
use Nette\PhpGenerator\ClassType;
10+
use Nette\PhpGenerator\InterfaceType;
1011
use Nette\PhpGenerator\Factory;
1112

1213
require __DIR__ . '/../bootstrap.php';
1314
require __DIR__ . '/fixtures/classes.php';
1415

15-
$res[] = ClassType::from(Abc\Interface1::class);
16-
$res[] = ClassType::from(Abc\Interface2::class);
17-
$res[] = ClassType::from(Abc\Interface3::class);
18-
$res[] = ClassType::from(Abc\Interface4::class);
16+
$res[] = InterfaceType::from(Abc\Interface1::class);
17+
$res[] = InterfaceType::from(Abc\Interface2::class);
18+
$res[] = InterfaceType::from(Abc\Interface3::class);
19+
$res[] = InterfaceType::from(Abc\Interface4::class);
1920
$res[] = ClassType::from(Abc\Class1::class);
2021
$res[] = ClassType::from(new Abc\Class2);
2122
$obj = new Abc\Class3;

tests/PhpGenerator/ClassType.from.trait.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
use Nette\PhpGenerator\ClassType;
5+
use Nette\PhpGenerator\ClassLike;
66

77
require __DIR__ . '/../bootstrap.php';
88
require __DIR__ . '/fixtures/traits.php';
@@ -19,11 +19,11 @@ $classes = [
1919
Class5::class,
2020
];
2121

22-
$res = array_map(fn($class) => ClassType::from($class), $classes);
22+
$res = array_map(fn($class) => ClassLike::from($class), $classes);
2323

2424
sameFile(__DIR__ . '/expected/ClassType.from.trait-use.expect', implode("\n", $res));
2525

2626

27-
$res = array_map(fn($class) => ClassType::from($class, withBodies: true), $classes);
27+
$res = array_map(fn($class) => ClassLike::from($class, withBodies: true), $classes);
2828

2929
sameFile(__DIR__ . '/expected/ClassType.from.trait-use.bodies.expect', implode("\n", $res));

0 commit comments

Comments
 (0)