Page MenuHomeDevCentral

No OneTemporary

diff --git a/src/Reflection/CallableElement.php b/src/Reflection/CallableElement.php
index 2fade21..a7bc3a4 100644
--- a/src/Reflection/CallableElement.php
+++ b/src/Reflection/CallableElement.php
@@ -1,75 +1,106 @@
<?php
declare(strict_types=1);
namespace Keruald\OmniTools\Reflection;
use Closure;
use InvalidArgumentException;
use ReflectionException;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
class CallableElement {
private ReflectionFunctionAbstract $callable;
/**
* @throws ReflectionException if the callable does not exist.
*/
public function __construct (callable $callable) {
$this->callable = self::getReflectionFunction($callable);
}
/**
* @throws ReflectionException
*/
private static function getReflectionFunction (callable $callable)
: ReflectionFunctionAbstract {
///
/// Functions
///
if ($callable instanceof Closure) {
return new ReflectionFunction($callable);
}
///
/// Objets and methods
///
if (is_array($callable)) {
return new ReflectionMethod($callable[0], $callable[1]);
}
if (is_object($callable)) {
// If __invoke() doesn't exist, the objet isn't a callable.
// Calling this method with such object would throw a TypeError
// before reaching this par of the code, so it is safe to assume
// we can correctly call it.
return new ReflectionMethod([$callable, '__invoke']);
}
///
/// Hybrid cases
///
if (is_string($callable)) {
if (!str_contains($callable, "::")) {
return new ReflectionFunction($callable);
}
return new ReflectionMethod($callable);
}
throw new InvalidArgumentException(
"Callable not recognized: " . gettype($callable)
);
}
public function countArguments () : int {
return $this->callable->getNumberOfParameters();
}
+ /**
+ * Determines if the callable has an explicit return type
+ * and that return type matches the specified type.
+ *
+ * Closure, arrow functions MUST declare explicitly the return type
+ * in the signature to be able to detect it. It will always be false if not.
+ */
+ public function hasReturnType (string $type) : bool {
+ if (!$this->callable->hasReturnType()) {
+ return false;
+ }
+
+ return $this->callable->getReturnType()->getName() === $type;
+ }
+
+ /**
+ * Gets the return type of the callable.
+ *
+ * @throws InvalidArgumentException if the callable doesn't have
+ * an explicit return type.
+ */
+ public function getReturnType () : string {
+ if (!$this->callable->hasReturnType()) {
+ throw new InvalidArgumentException(
+ "Callable doesn't have an explicit return type"
+ );
+ }
+
+ return $this->callable->getReturnType()->getName();
+ }
+
}
diff --git a/src/Reflection/CodeVariable.php b/src/Reflection/CodeVariable.php
index 95503f8..89fe971 100644
--- a/src/Reflection/CodeVariable.php
+++ b/src/Reflection/CodeVariable.php
@@ -1,39 +1,53 @@
<?php
declare(strict_types=1);
namespace Keruald\OmniTools\Reflection;
class CodeVariable {
private mixed $variable;
///
/// Constructor
///
public static function from (mixed $variable) : self {
$instance = new self;
$instance->variable = $variable;
return $instance;
}
///
/// Type helper methods
///
public function hasType (string $type) : bool {
$ourType = gettype($this->variable);
// For scalar types, gettype() doesn't return the same types
// as does reflection classes.
return match ($ourType) {
"boolean" => $type === "bool" || $type === "boolean",
"integer" => $type === "int" || $type === "integer",
"double" => $type === "float" || $type === "double",
"object" => $this->variable::class === $type,
default => $ourType === $type,
};
}
+ public function getType () : string {
+ $type = gettype($this->variable);
+
+ // For scalar types, gettype() doesn't return the same types
+ // as does reflection classes.
+ return match ($type) {
+ "boolean" => "bool",
+ "integer" => "int",
+ "double" => "float",
+ "object" => $this->variable::class,
+ default => $type,
+ };
+ }
+
}
diff --git a/tests/Reflection/CallableElementTest.php b/tests/Reflection/CallableElementTest.php
new file mode 100644
index 0000000..e6806c6
--- /dev/null
+++ b/tests/Reflection/CallableElementTest.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\Reflection;
+
+use InvalidArgumentException;
+use Keruald\OmniTools\Reflection\CallableElement;
+
+use PHPUnit\Framework\TestCase;
+
+class CallableElementTest extends TestCase {
+
+ private CallableElement $closure;
+
+ protected function setUp () : void {
+ $fn = fn(int $a, int $b) : int => $a + $b;
+ $this->closure = new CallableElement($fn);
+ }
+
+ public function testCountArguments () : void {
+ $this->assertEquals(2, $this->closure->countArguments());
+ }
+
+ public function testCountArgumentsWhenThereIsNone () : void {
+ $fn = fn() => null;
+ $closure = new CallableElement($fn);
+
+ $this->assertEquals(0, $closure->countArguments());
+ }
+
+ public function testHasReturnType () : void {
+ $this->assertTrue($this->closure->hasReturnType("int"));
+ $this->assertFalse($this->closure->hasReturnType("quux"));
+ }
+
+ public function testHasReturnTypeWhenThereIsNone () : void {
+ // Closure without any explicit return type
+ $fn = fn(int $a, int $b) => $a + $b;
+ $closure = new CallableElement($fn);
+
+ $this->assertFalse($closure->hasReturnType("int"));
+ }
+
+ public function testGetReturnType () : void {
+ $this->assertEquals("int", $this->closure->getReturnType());
+ }
+
+ public function testGetReturnTypeWhenThereIsNone () : void {
+ // Closure without any explicit return type
+ $fn = fn(int $a, int $b) => $a + $b;
+ $closure = new CallableElement($fn);
+
+ $this->expectException(InvalidArgumentException::class);
+ $closure->getReturnType();
+ }
+
+}
diff --git a/tests/Reflection/CodeVariableTest.php b/tests/Reflection/CodeVariableTest.php
index 1a8eade..7355b3e 100644
--- a/tests/Reflection/CodeVariableTest.php
+++ b/tests/Reflection/CodeVariableTest.php
@@ -1,63 +1,87 @@
<?php
namespace Keruald\OmniTools\Tests\Reflection;
use Keruald\OmniTools\Collections\Vector;
use Keruald\OmniTools\Reflection\CodeVariable;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class CodeVariableTest extends TestCase {
public function testHasTypeWithObject () {
$object = new Vector;
$variable = CodeVariable::from($object);
$this->assertTrue($variable->hasType(Vector::class));
}
#[DataProvider('provideScalarsAndTypes')]
public function testHasTypeWithScalar (mixed $scalar, string $type) {
$variable = CodeVariable::from($scalar);
$this->assertTrue($variable->hasType($type));
}
+ public function testGetTypeWithObject () {
+ $object = new Vector;
+ $variable = CodeVariable::from($object);
+
+ $this->assertEquals(Vector::class, $variable->getType());
+ }
+
+ #[DataProvider('provideScalarsAndReturnedTypes')]
+ public function testGetTypeWithScalar (mixed $scalar, string $expected) {
+ $variable = CodeVariable::from($scalar);
+
+ $this->assertEquals($expected, $variable->getType());
+ }
+
#[DataProvider('provideScalars')]
public function testFromWithScalar (mixed $scalar) {
$variable = CodeVariable::from($scalar);
$this->assertInstanceOf(CodeVariable::class, $variable);
}
public function testFromWithObject () {
$object = new Vector;
$variable = CodeVariable::from($object);
$this->assertInstanceOf(CodeVariable::class, $variable);
}
///
/// Data providers
///
public static function provideScalars () : iterable {
yield [0];
yield [""];
yield [19];
yield ["This is Sparta."];
yield [true];
yield [false];
yield [null];
}
public static function provideScalarsAndTypes () : iterable {
yield [0, "integer"];
yield ["", "string"];
yield [19, "integer"];
yield ["This is Sparta.", "string"];
yield [true, "boolean"];
yield [false, "boolean"];
yield [null, "NULL"];
}
+
+ public static function provideScalarsAndReturnedTypes () : iterable {
+ yield [0, "int"];
+ yield ["", "string"];
+ yield [19, "int"];
+ yield ["This is Sparta.", "string"];
+ yield [true, "bool"];
+ yield [false, "bool"];
+ yield [null, "NULL"];
+ }
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Sep 18, 23:26 (7 m, 49 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2991916
Default Alt Text
(9 KB)

Event Timeline