Page MenuHomeDevCentral

D3204.id8173.diff
No OneTemporary

D3204.id8173.diff

diff --git a/omnitools/src/Reflection/CodeClass.php b/omnitools/src/Reflection/CodeClass.php
new file mode 100644
--- /dev/null
+++ b/omnitools/src/Reflection/CodeClass.php
@@ -0,0 +1,90 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Reflection;
+
+use Keruald\OmniTools\Collections\Vector;
+
+use InvalidArgumentException;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionMethod;
+
+class CodeClass {
+
+ ///
+ /// CodeClass constructor
+ ///
+
+ public function __construct (
+ private readonly string $className,
+ ) {
+ }
+
+ ///
+ /// Represented class constructor helper methods
+ ///
+
+ /**
+ * @throws ReflectionException
+ * @throws InvalidArgumentException
+ */
+ public function getConstructor () : ReflectionMethod {
+ $class = new ReflectionClass($this->className);
+ $constructor = $class->getConstructor();
+
+ return match ($constructor) {
+ null => throw new InvalidArgumentException(
+ "This class doesn't have a constructor."
+ ),
+ default => $constructor,
+ };
+ }
+
+ /**
+ * @throws ReflectionException
+ */
+ public function getConstructorArgumentsTypes () : Vector {
+ $class = new ReflectionClass($this->className);
+ $constructor = $class->getConstructor();
+
+ if ($constructor === null) {
+ return new Vector;
+ }
+
+ return CodeMethod::fromReflectionMethod($constructor)
+ ->getArgumentsType();
+ }
+
+ /**
+ *
+ * This method can be used for dependency injection to build a class,
+ * like a controller in a MVC model, from a services' container.
+ *
+ * Each argument of the constructor is substituted by an item from
+ * $services. To match properly services and constructor arguments,
+ * each arguments need to have a type, and those types should properly
+ * exist in $services, without duplicate.
+ *
+ * @param iterable $services a collection with keys as type names and values
+ * @return object A new instance of the reflected class
+ * @throws ReflectionException
+ */
+ public function newInstanceFromServices (iterable $services) : object {
+ $args = $this->getConstructorArgumentsTypes()
+ ->map(function (string $type) use ($services) : mixed {
+ foreach ($services as $value) {
+ if (CodeVariable::from($value)->hasType($type)) {
+ return $value;
+ }
+ }
+
+ throw new InvalidArgumentException("No instance of type $type can be found.");
+ });
+
+ $class = new ReflectionClass($this->className);
+ return $class->newInstance(...$args);
+
+ }
+
+}
diff --git a/omnitools/src/Reflection/CodeFunction.php b/omnitools/src/Reflection/CodeFunction.php
new file mode 100644
--- /dev/null
+++ b/omnitools/src/Reflection/CodeFunction.php
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Reflection;
+
+use InvalidArgumentException;
+use ReflectionParameter;
+
+class CodeFunction {
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ public static function getParameterType (ReflectionParameter $parameter) : string {
+ if (!$parameter->hasType()) {
+ $name = $parameter->getName();
+ throw new InvalidArgumentException(
+ "Parameter $name doesn't have a type"
+ );
+ }
+
+ return $parameter->getType()->getName();
+ }
+
+}
diff --git a/omnitools/src/Reflection/CodeMethod.php b/omnitools/src/Reflection/CodeMethod.php
new file mode 100644
--- /dev/null
+++ b/omnitools/src/Reflection/CodeMethod.php
@@ -0,0 +1,39 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Reflection;
+
+use Keruald\OmniTools\Collections\Vector;
+
+use ReflectionMethod;
+use ReflectionParameter;
+
+class CodeMethod extends CodeFunction {
+
+ ///
+ /// Properties
+ ///
+
+ private ReflectionMethod $method;
+
+ ///
+ /// Constructor
+ ///
+
+ public static function fromReflectionMethod (ReflectionMethod $method) : self {
+ $instance = new self;
+ $instance->method = $method;
+
+ return $instance;
+ }
+
+ ///
+ /// Arguments helper methods
+ ///
+
+ public function getArgumentsType () : Vector {
+ return Vector::from($this->method->getParameters())
+ ->map(fn(ReflectionParameter $param) => CodeFunction::getParameterType($param));
+ }
+
+}
diff --git a/omnitools/src/Reflection/CodeVariable.php b/omnitools/src/Reflection/CodeVariable.php
new file mode 100644
--- /dev/null
+++ b/omnitools/src/Reflection/CodeVariable.php
@@ -0,0 +1,39 @@
+<?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,
+ };
+ }
+
+}
diff --git a/omnitools/tests/Reflection/AcmeApplication.php b/omnitools/tests/Reflection/AcmeApplication.php
new file mode 100644
--- /dev/null
+++ b/omnitools/tests/Reflection/AcmeApplication.php
@@ -0,0 +1,27 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Tests\Reflection;
+
+use Keruald\OmniTools\Collections\HashMap;
+use Keruald\OmniTools\DateTime\DateStamp;
+use Keruald\OmniTools\HTTP\Requests\Request;
+
+class AcmeApplication {
+
+ public function __construct (
+ private Request $request,
+ private HashMap $session,
+ private DateStamp $dateStamp,
+ private int $counter,
+ private array $inventory,
+ private float $temperature,
+ private bool $isSecure,
+ ) {
+ }
+
+ public function getDateStamp () : DateStamp {
+ return $this->dateStamp;
+ }
+
+}
diff --git a/omnitools/tests/Reflection/CodeClassTest.php b/omnitools/tests/Reflection/CodeClassTest.php
new file mode 100644
--- /dev/null
+++ b/omnitools/tests/Reflection/CodeClassTest.php
@@ -0,0 +1,107 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Tests\Reflection;
+
+use Keruald\OmniTools\Collections\ArrayUtilities;
+use Keruald\OmniTools\Collections\HashMap;
+use Keruald\OmniTools\Collections\Vector;
+use Keruald\OmniTools\DateTime\DateStamp;
+use Keruald\OmniTools\HTTP\Requests\Request;
+use Keruald\OmniTools\Network\IPv4Range;
+use Keruald\OmniTools\Reflection\CodeClass;
+
+use PHPUnit\Framework\TestCase;
+
+use InvalidArgumentException;
+
+class CodeClassTest extends TestCase {
+
+ private CodeClass $class;
+
+ protected function setUp () : void {
+ $this->class = new CodeClass(AcmeApplication::class);
+ }
+
+ public function testNewInstanceFromServices () {
+ $services = [
+ // Some objects (in different order than the constructor)
+ "request" => new Request(),
+ "date" => DateStamp::fromUnixTime(), // another name than in class
+ "session" => new HashMap(),
+
+ // Scalar values
+ "counter" => 666,
+ "isSecure" => false,
+ "temperature" => 26.6,
+ "inventory" => [],
+
+ // An object not needed by the controller
+ "ip_range" => IPv4Range::from("127.0.0.1/32"),
+ ];
+
+ $app = $this->class->newInstanceFromServices($services);
+
+ $this->assertInstanceOf(AcmeApplication::class, $app);
+ $this->assertEquals($services["date"], $app->getDateStamp());
+ }
+
+ public function testNewInstanceFromServicesWithMissingService () {
+ $incompleteServices = [
+ "foo",
+ ];
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->class->newInstanceFromServices($incompleteServices);
+ }
+
+ public function testNewInstanceFromServicesWithoutConstructor () {
+ $services = [
+ "foo",
+ ];
+
+ $class = new CodeClass(ArrayUtilities::class); // No constructor
+ $utilities = $class->newInstanceFromServices($services);
+
+ $this->assertInstanceOf(ArrayUtilities::class, $utilities);
+ }
+
+ public function testGetConstructorArgumentsTypes () {
+ $expected = Vector::from([
+ Request::class,
+ HashMap::class,
+ DateStamp::class,
+ "int",
+ "array",
+ "float",
+ "bool",
+ ]);
+
+ $actual = $this->class->getConstructorArgumentsTypes();
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testGetConstructorArgumentsTypesWhenNotExisting () {
+ $class = new CodeClass(ArrayUtilities::class); // No constructor
+
+ $this->assertEquals(new Vector, $class->getConstructorArgumentsTypes());
+ }
+
+ public function testGetConstructor () {
+ $constructor = $this->class->getConstructor();
+
+ $this->assertEquals("__construct", $constructor->getName());
+ $this->assertEquals(
+ AcmeApplication::class,
+ $constructor->getDeclaringClass()->getName()
+ );
+ }
+
+ public function testGetConstructorWhenNotExisting () {
+ $this->expectException(InvalidArgumentException::class);
+
+ $class = new CodeClass(ArrayUtilities::class); // No constructor
+ $class->getConstructor();
+ }
+
+}
diff --git a/omnitools/tests/Reflection/CodeFunctionTest.php b/omnitools/tests/Reflection/CodeFunctionTest.php
new file mode 100644
--- /dev/null
+++ b/omnitools/tests/Reflection/CodeFunctionTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\Reflection;
+
+use Keruald\OmniTools\Reflection\CodeFunction;
+use PHPUnit\Framework\TestCase;
+
+use InvalidArgumentException;
+use ReflectionFunction;
+use ReflectionParameter;
+
+class CodeFunctionTest extends TestCase {
+
+ /**
+ * @dataProvider provideFunctionParameters
+ */
+ public function testGetParameterType (ReflectionParameter $parameter, string $type) {
+ $this->assertEquals($type, CodeFunction::getParameterType($parameter));
+ }
+
+ public function testGetParameterTypeWhenNoTypeIsDefined () {
+ $this->expectException(InvalidArgumentException::class);
+
+ $function = new ReflectionFunction("Keruald\OmniTools\Tests\Reflection\doSomething");
+ $parameters = $function->getParameters();
+
+ CodeFunction::getParameterType($parameters[0]);
+ }
+
+ ///
+ /// Data provider
+ ///
+
+ public function provideFunctionParameters () : iterable {
+ // array_change_key_case(array $array, int $case = CASE_LOWER): array
+ $function = new ReflectionFunction("array_change_key_case");
+ $parameters = $function->getParameters();
+
+ yield [$parameters[0], "array"];
+ yield [$parameters[1], "int"];
+ }
+}
+
+function doSomething ($mixed) : void {
+
+}
diff --git a/omnitools/tests/Reflection/CodeMethodTest.php b/omnitools/tests/Reflection/CodeMethodTest.php
new file mode 100644
--- /dev/null
+++ b/omnitools/tests/Reflection/CodeMethodTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Keruald\OmniTools\Reflection;
+
+use Keruald\OmniTools\Collections\Vector;
+use PHPUnit\Framework\TestCase;
+
+use ReflectionMethod;
+
+class CodeMethodTest extends TestCase {
+
+ public function testGetArgumentsType () {
+ // public function replace(iterable $iterable, int $offset = 0, int $len = 0) : self
+ $method = new ReflectionMethod(new Vector, "replace");
+ $method = CodeMethod::fromReflectionMethod($method);
+
+ $expected = Vector::from([
+ "iterable",
+ "int",
+ "int",
+ ]);
+ $actual = $method->getArgumentsType();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testFromReflectionMethod () {
+ $method = new ReflectionMethod(new Vector, "replace");
+ $method = CodeMethod::fromReflectionMethod($method);
+
+ $this->assertInstanceOf(CodeMethod::class, $method);
+ }
+
+}
diff --git a/omnitools/tests/Reflection/CodeVariableTest.php b/omnitools/tests/Reflection/CodeVariableTest.php
new file mode 100644
--- /dev/null
+++ b/omnitools/tests/Reflection/CodeVariableTest.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\Reflection;
+
+use Keruald\OmniTools\Collections\Vector;
+use Keruald\OmniTools\Reflection\CodeVariable;
+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));
+ }
+
+ /**
+ * @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
+ ///
+
+ private function provideScalars () : iterable {
+ yield [0];
+ yield [""];
+ yield [19];
+ yield ["This is Sparta."];
+ yield [true];
+ yield [false];
+ yield [null];
+ }
+
+ private 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"];
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Sun, Nov 24, 06:37 (11 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2259155
Default Alt Text
D3204.id8173.diff (14 KB)

Event Timeline