Skip to content

Commit

Permalink
Make ClassLike::from return type assert the subclass type (#154)
Browse files Browse the repository at this point in the history
* Make ClassLike::from return type more strict

* Throw exception instead of relying on PHP type error

* Improve exception message and add test
  • Loading branch information
GromNaN committed Mar 7, 2024
1 parent 64cdb3e commit 42279b7
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 13 deletions.
22 changes: 17 additions & 5 deletions src/PhpGenerator/ClassLike.php
Expand Up @@ -37,18 +37,30 @@ abstract class ClassLike
private ?PhpNamespace $namespace;
private ?string $name;


public static function from(string|object $class, bool $withBodies = false): self
public static function from(string|object $class, bool $withBodies = false): static
{
return (new Factory)
$instance = (new Factory)
->fromClassReflection(new \ReflectionClass($class), $withBodies);

if (!$instance instanceof static) {
$class = is_object($class) ? get_class($class) : $class;
throw new Nette\InvalidArgumentException("'$class' cannot be represented with " . static::class . ". Call " . get_class($instance) . "::" . __FUNCTION__ . "() or " . __METHOD__ . "() instead.");
}

return $instance;
}


public static function fromCode(string $code): self
public static function fromCode(string $code): static
{
return (new Factory)
$instance = (new Factory)
->fromClassCode($code);

if (!$instance instanceof static) {
throw new Nette\InvalidArgumentException("Provided code cannot be represented with " . static::class . ". Call " . get_class($instance) . "::" . __FUNCTION__ . "() or " . __METHOD__ . "() instead.");
}

return $instance;
}


Expand Down
28 changes: 28 additions & 0 deletions tests/PhpGenerator/ClassLike.typecheck.phpt
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\EnumType;
use Nette\PhpGenerator\InterfaceType;
use Nette\PhpGenerator\TraitType;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';
require __DIR__ . '/fixtures/classes.php';

Assert::exception(function () {
ClassType::from(Abc\Interface1::class);
}, Nette\InvalidArgumentException::class, "'Abc\\Interface1' cannot be represented with Nette\\PhpGenerator\\ClassType. Call Nette\\PhpGenerator\\InterfaceType::from() or Nette\\PhpGenerator\\ClassLike::from() instead.");

Assert::exception(function () {
TraitType::from(Abc\Class1::class);
}, Nette\InvalidArgumentException::class, "'Abc\\Class1' cannot be represented with Nette\\PhpGenerator\\TraitType. Call Nette\\PhpGenerator\\ClassType::from() or Nette\\PhpGenerator\\ClassLike::from() instead.");

Assert::exception(function () {
ClassType::fromCode("<?php interface I {}");
}, Nette\InvalidArgumentException::class, "Provided code cannot be represented with Nette\\PhpGenerator\\ClassType. Call Nette\\PhpGenerator\\InterfaceType::fromCode() or Nette\\PhpGenerator\\ClassLike::fromCode() instead.");

Assert::exception(function () {
InterfaceType::fromCode("<?php trait T {}");
}, Nette\InvalidArgumentException::class, "Provided code cannot be represented with Nette\\PhpGenerator\\InterfaceType. Call Nette\\PhpGenerator\\TraitType::fromCode() or Nette\\PhpGenerator\\ClassLike::fromCode() instead.");
3 changes: 2 additions & 1 deletion tests/PhpGenerator/ClassType.from.82.phpt
Expand Up @@ -7,11 +7,12 @@
declare(strict_types=1);

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\TraitType;

require __DIR__ . '/../bootstrap.php';
require __DIR__ . '/fixtures/classes.82.php';

$res[] = ClassType::from(new Abc\Class13);
$res[] = ClassType::from(Abc\Trait13::class);
$res[] = TraitType::from(Abc\Trait13::class);

sameFile(__DIR__ . '/expected/ClassType.from.82.expect', implode("\n", $res));
9 changes: 5 additions & 4 deletions tests/PhpGenerator/ClassType.from.phpt
Expand Up @@ -7,15 +7,16 @@
declare(strict_types=1);

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\InterfaceType;
use Nette\PhpGenerator\Factory;

require __DIR__ . '/../bootstrap.php';
require __DIR__ . '/fixtures/classes.php';

$res[] = ClassType::from(Abc\Interface1::class);
$res[] = ClassType::from(Abc\Interface2::class);
$res[] = ClassType::from(Abc\Interface3::class);
$res[] = ClassType::from(Abc\Interface4::class);
$res[] = InterfaceType::from(Abc\Interface1::class);
$res[] = InterfaceType::from(Abc\Interface2::class);
$res[] = InterfaceType::from(Abc\Interface3::class);
$res[] = InterfaceType::from(Abc\Interface4::class);
$res[] = ClassType::from(Abc\Class1::class);
$res[] = ClassType::from(new Abc\Class2);
$obj = new Abc\Class3;
Expand Down
6 changes: 3 additions & 3 deletions tests/PhpGenerator/ClassType.from.trait.phpt
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\ClassLike;

require __DIR__ . '/../bootstrap.php';
require __DIR__ . '/fixtures/traits.php';
Expand All @@ -19,11 +19,11 @@ $classes = [
Class5::class,
];

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

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


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

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

0 comments on commit 42279b7

Please sign in to comment.