diff --git a/README.md b/README.md index 7ae95b0..ea6ad4f 100755 --- a/README.md +++ b/README.md @@ -14,10 +14,14 @@ Container, Factories and dependency injectors will help to make your PHP code mo Containers allowing you to easily create and retrieve objects that are needed throughout your application. ```php use MaplePHP\Container\Container; + $container = new Container(); -$container->set("YourClass", \YourNamespace\To\YourClass::class); // Bind "YourClass" to container and dependency injector -$yourClass = $container->get("YourClass")->get(); // Will return "YourClass" -//$yourClass->yourClassMehthod(); + +// You can set mixed values in the container +$container->set("hasEmail", true); + +$hasEmail = $container->get("hasEmail")->get(); +var_dump($hasEmail); // Result in: (bool) true ``` If the constructor of "YourClass" contains unresolved class arguments, the dependency injector will attempt to automatically locate them for you. Read more under the headline **dependency injector**. @@ -40,32 +44,16 @@ Take a look at this example ```php -$container->set("YourClass", \YourNamespace\To\YourClass::class); -$testService = $container->get("YourClass"); -echo $testService->start(); - -``` -The above code will load **YourClass** and auto initialize the class **Test**. - -```php -namespace YourNamespace\To; - -use YourNamespace\ToTestClasses\Test; - -class YourClass { - - private $test; +use MaplePHP\Container\Container; +use MaplePHP\Container\Autowire; +use App\Services\MailService; - // Dependency injector will auto load "Test" class and the "Test" classes and so on. - function __construct(Test $test) { - $this->test = $test; - } +$container = new Container(); +$container->set("MailService", new Autowire(MailService::class)); - function start() { - return $this->test->get("This is the start page"); - } - -} +// This will return "MailService" with all dependencies resolved on constructor +$mailService = $container->get("MailService")->get(); +echo $mailService->send(); ``` ## Event handler diff --git a/composer.json b/composer.json index b905b5e..3346e0f 100644 --- a/composer.json +++ b/composer.json @@ -27,8 +27,12 @@ "maplephp/dto": "^3.0" }, "autoload": { + "files": [ + "src/Setup/aliases.php" + ], "psr-4": { - "MaplePHP\\Container\\": "" + "Psr\\Container\\": "src/Interfaces/", + "MaplePHP\\Container\\": "src" } }, "minimum-stability": "dev" diff --git a/src/Autowire.php b/src/Autowire.php new file mode 100644 index 0000000..ae1363c --- /dev/null +++ b/src/Autowire.php @@ -0,0 +1,76 @@ +reflect = new Reflection($class); + } else { + throw new \InvalidArgumentException("The class {$class} does not exist."); + } + } + + /** + * Disable the dependency injector + * + * Note: This method MUST be implemented in such a way as to retain the immutability + * + * @return $this + */ + public function disableDI(): self + { + $inst = clone $this; + $inst->disableDI = true; + return $inst; + } + + /** + * Pass custom arguments to the class constructor + * + * Note: This method MUST be implemented in such a way as to retain the immutability + * NOTE: This will disable the dependency injector + * + * @param array $args + * @return $this + */ + public function addArgs(array $args): self + { + if(count($args) <= 0) { + throw new \InvalidArgumentException("You must provide at least one argument."); + } + $inst = $this->disableDI(); + $inst->reflect->setArgs($args); + return $inst; + } + + /** + * Run the class with all dependencies. + * + * @return mixed + * @throws \ReflectionException + */ + public function run(): mixed + { + if (is_null($this->class)) { + $this->class = ($this->disableDI) ? $this->reflect->get() : $this->reflect->dependencyInjector(); + } + return $this->class; + } +} \ No newline at end of file diff --git a/Container.php b/src/Container.php similarity index 90% rename from Container.php rename to src/Container.php index f18943d..91baa17 100755 --- a/Container.php +++ b/src/Container.php @@ -5,7 +5,8 @@ namespace MaplePHP\Container; use Closure; -use MaplePHP\Container\Interfaces\ContainerInterface; +use MaplePHP\Container\Interfaces\AutowireInterface; +use Psr\Container\ContainerInterface; use MaplePHP\Container\Interfaces\FactoryInterface; use MaplePHP\DTO\Format\Arr; //use MaplePHP\Container\Reflection; @@ -112,27 +113,26 @@ public function isContainer(string $identifier): bool /** * Get a container or factory - * @param string $identifier [description] - * @param array $args Is possible to overwrite/add __construct or method argumnets - * @return mixed + * @template T of object + * @param class-string|string $identifier The class or service identifier + * @param string $identifier + * @param array $args Is possible to overwrite/add __construct or method arguments + * @return T * @throws ReflectionException */ public function get(string $identifier, array $args = []): mixed { - if ($service = $this->getService($identifier)) { + $service = $this->getService($identifier); + if (isset($service)) { if (count($args) === 0) { $args = $this->getArgs($identifier); } if ($this->isFactory($identifier)) { $this->getter[$identifier] = $service(...$args); } else { - if (empty($this->getter[$identifier])) { - if (is_string($service) && class_exists($service)) { - $reflect = new Reflection($service); - if (count($args) > 0) { - $reflect->setArgs($args); - } - $this->getter[$identifier] = $reflect->get(); + if (!isset($this->getter[$identifier])) { + if ($service instanceof AutowireInterface) { + $this->getter[$identifier] = $service->run(); } else { $this->getter[$identifier] = $service; } diff --git a/EventHandler.php b/src/EventHandler.php similarity index 92% rename from EventHandler.php rename to src/EventHandler.php index 9b786e1..0111d82 100755 --- a/EventHandler.php +++ b/src/EventHandler.php @@ -51,18 +51,18 @@ public function addHandler(object|string $handler, string|array $method = null): public function addEvent(callable|object|string $event, ?string $bind = null): void { - if(!is_callable($event)) { - if(is_string($event)) { + if (!is_callable($event)) { + if (is_string($event)) { $reflect = new Reflection($event); $event = $reflect->get(); } - if(is_object($event) && !($event instanceof EventInterface)) { + if (is_object($event) && !($event instanceof EventInterface)) { throw new Exception("Event object/class needs to be instance of \"EventInterface\"!", 1); } } - if (is_null($bind)) { + if ($bind === null) { $this->event[] = $event; } else { $this->event[] = [$bind => $event]; @@ -95,7 +95,7 @@ public function __call(string $method, array $args): mixed throw new BadMethodCallException("The method \"".$method."\" does not exist in the class (" . $handler[0]::class . ")", 1); } */ - if (is_null($handler[1][0]) || in_array($method, $handler[1])) { + if ($handler[1][0] === null || in_array($method, $handler[1])) { $this->bindable[$method] = $method; } $data = call_user_func_array([$handler[0], $method], $args); @@ -136,7 +136,7 @@ final protected function triggerEvents(): void */ final protected function getEvent(callable|object $data): void { - if(is_callable($data)) { + if (is_callable($data)) { $data(); } else { $data->resolve(); diff --git a/Exceptions/ContainerException.php b/src/Exceptions/ContainerException.php similarity index 100% rename from Exceptions/ContainerException.php rename to src/Exceptions/ContainerException.php diff --git a/Exceptions/NotFoundException.php b/src/Exceptions/NotFoundException.php similarity index 100% rename from Exceptions/NotFoundException.php rename to src/Exceptions/NotFoundException.php diff --git a/src/Interfaces/AutowireInterface.php b/src/Interfaces/AutowireInterface.php new file mode 100644 index 0000000..d37efd8 --- /dev/null +++ b/src/Interfaces/AutowireInterface.php @@ -0,0 +1,34 @@ +setDependMethod($method, $this->reflect); - if(!is_null($constructor)) { + if ($constructor !== null) { $params = $constructor->getParameters(); $this->injectRecursion($params, $this->reflect->getName()); foreach ($params as $param) { - if ($param->getType() && !$param->getType()->isBuiltin()) { - $classKey = $param->getType()->getName(); + if (!$this->isBuiltin($param)) { + $classKey = $this->getClassNameFromParam($param); + if (!$classKey) { + continue; + } + if (isset(self::$class[$classKey])) { $args[] = self::$class[$classKey]; } } } } - if(!is_null($this->dependMethod)) { + if ($this->dependMethod !== null) { $this->dependMethod = null; return $constructor->invokeArgs($class, $args); } @@ -104,12 +111,24 @@ public function setDependMethod(?string $method, ReflectionClass $inst): ?Reflec { $method = ($method === "constructor") ? null : $method; $this->dependMethod = $method; - if(is_null($this->dependMethod)) { + if ($this->dependMethod === null) { return $inst->getConstructor(); } return $inst->getMethod($this->dependMethod); } + /** + * Check if a parameter type is built-in + * + * @param ReflectionParameter $param Parameter to check + * @return bool Returns true if a parameter type is not built-in, false otherwise + */ + private function isBuiltin(ReflectionParameter $param): bool + { + $type = $param->getType(); + return ($type instanceof ReflectionNamedType && $type->isBuiltin()); + } + /** * This will return reflection if class exist or error pointing to file where error existed, * @param class-string $className @@ -142,8 +161,11 @@ private function injectRecursion(array $params, string $fromClass, array $_args { $_args = []; foreach ($params as $param) { - if ($param->getType() && !$param->getType()->isBuiltin()) { - $classNameA = $param->getType()->getName(); + if (!$this->isBuiltin($param)) { + $classNameA = $this->getClassNameFromParam($param); + if (!$classNameA) { + continue; // skip if class name couldn't be resolved + } $inst = $this->initReclusiveReflect($classNameA, $fromClass); $reflectParam = []; $constructor = $inst->getConstructor(); @@ -172,6 +194,42 @@ private function injectRecursion(array $params, string $fromClass, array $_args return $_args; } + /** + * Extracts the class name from a ReflectionParameter object + * Handles named types, union types, and intersection types (PHP 8.1+) + * Returns the first non-built-in type name found or null if none exists + * + * @param ReflectionParameter $param The reflection parameter to analyze + * @return string|null The extracted class name or null if no class name is found + */ + private function getClassNameFromParam(ReflectionParameter $param): ?string + { + $type = $param->getType(); + + if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) { + return $type->getName(); + } + + if ($type instanceof ReflectionUnionType) { + foreach ($type->getTypes() as $innerType) { + if ($innerType instanceof ReflectionNamedType && !$innerType->isBuiltin()) { + return $innerType->getName(); + } + } + } + + // Optionally handle ReflectionIntersectionType for PHP 8.1+ + if (PHP_VERSION_ID >= 80100 && $type instanceof ReflectionIntersectionType) { + foreach ($type->getTypes() as $innerType) { + if ($innerType instanceof ReflectionNamedType && !$innerType->isBuiltin()) { + return $innerType->getName(); // or return all names if needed + } + } + } + return null; + } + + /** * Will insert interface classes (the default classes) * @param ReflectionClass $inst @@ -181,7 +239,7 @@ private function injectRecursion(array $params, string $fromClass, array $_args private function insertInterfaceClasses(ReflectionClass $inst, string $classNameA): void { if ($this->allowInterfaces) { - if (!is_null(self::$interfaceFactory)) { + if (self::$interfaceFactory !== null) { foreach (self::$interfaceFactory as $call) { self::$class[$classNameA] = $call($inst->getShortName(), $classNameA, $inst); } @@ -208,7 +266,7 @@ private function insertMultipleNestedClasses( ): array { $args = []; foreach ($reflectParam as $reflectInstance) { - if ($reflectInstance->getType() && !$reflectInstance->getType()->isBuiltin()) { + if (!$this->isBuiltin($reflectInstance)) { $classNameB = $reflectInstance->getType()->getName(); if (isset(self::$class[$classNameB])) { $args[] = self::$class[$classNameB]; @@ -248,6 +306,15 @@ public function setArgs(array $array): self return $this; } + /** + * Check if args has been initiated + * @return bool + */ + public function hasArgs(): bool + { + return (is_array($this->args) && count($this->args) > 0); + } + /** * Access reflection class * @return ReflectionClass @@ -265,7 +332,7 @@ public function getReflect(): ReflectionClass */ public function get(): mixed { - if (!is_null($this->method)) { + if ($this->method !== null) { $method = $this->reflect->getMethod($this->method); if ($method->isConstructor()) { return $this->getClass(); @@ -275,7 +342,7 @@ public function get(): mixed } $inst = $this->reflect->newInstanceWithoutConstructor(); - if (!is_null($this->args)) { + if ($this->args !== null) { return $method->invokeArgs($inst, $this->args); } else { return $method->invoke($inst); @@ -296,7 +363,7 @@ public static function getClassList(): array */ private function getClass(): object { - if (!is_null($this->args)) { + if ($this->args !== null) { $inst = $this->reflect->newInstanceArgs($this->args); } else { $inst = $this->dependencyInjector(); diff --git a/src/Setup/aliases.php b/src/Setup/aliases.php new file mode 100644 index 0000000..8bf7624 --- /dev/null +++ b/src/Setup/aliases.php @@ -0,0 +1,7 @@ +