Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3767285
D3204.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D3204.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 23, 23:22 (20 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2259155
Default Alt Text
D3204.diff (14 KB)
Attached To
Mode
D3204: Provide reflection bricks to build a service container
Attached
Detach File
Event Timeline
Log In to Comment