Matthias Lantsch(1 year, 1 month ago)
Update di container to allow for Provider classes
Browse Filesdiff --git a/20dd276382c6594baa54d3f351f845a1e9e7cfa3 b/b745ee6ce22cea66c952365cfe9f617b9fdb429a
index 20dd276..b745ee6 100644
--- a/20dd276382c6594baa54d3f351f845a1e9e7cfa3
+++ b/b745ee6ce22cea66c952365cfe9f617b9fdb429a
@@ -1,5 +1,14 @@
<?xml version="1.0"?>
-<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd" cacheDirectory=".phpunit.cache" requireCoverageMetadata="true">
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ bootstrap="vendor/autoload.php"
+ colors="true"
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd"
+ cacheDirectory=".phpunit.cache"
+ requireCoverageMetadata="true"
+ displayDetailsOnTestsThatTriggerWarnings="true"
+ displayDetailsOnTestsThatTriggerDeprecations="true"
+ displayDetailsOnTestsThatTriggerErrors="true"
+ displayDetailsOnTestsThatTriggerNotices="true">
<testsuite name="tests">
<directory>./tests</directory>
</testsuite>
diff --git a/30ad2403ea728b4eea538c6142922c8b26f14411 b/d97637caf43966a4293e98526aa8804762fe43a2
index 30ad240..d97637c 100644
--- a/30ad2403ea728b4eea538c6142922c8b26f14411
+++ b/d97637caf43966a4293e98526aa8804762fe43a2
@@ -3,7 +3,8 @@
errorLevel="4"
totallyTyped="false"
reportMixedIssues="false"
- resolveFromConfigFile="true"
+ findUnusedPsalmSuppress="true"
+ findUnusedBaselineEntry="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
diff --git a/12c8b270f22e436b35c5abeddcd64407d7f84e32 b/d3f70e5daf08940bf4c7d26de8747964655fb1fb
index 12c8b27..d3f70e5 100644
--- a/12c8b270f22e436b35c5abeddcd64407d7f84e32
+++ b/d3f70e5daf08940bf4c7d26de8747964655fb1fb
@@ -24,6 +24,7 @@ class PhpConfigParser extends AbstractParser {
*/
$ret = require $filename;
//either the user sets a variable called "config" or returns an array
+ /** @psalm-suppress UndefinedVariable */
if (!isset($config) && ($config = $ret) !== 1) {
throw new FileAccessException("Could not parse php config file '{$filename}'; File must either return an array or define the variable \$config");
}
<?php
/**
* This file is part of the holonet common library
* (c) Matthias Lantsch.
*
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @author Matthias Lantsch <[email protected]>
*/
namespace holonet\common\di;
use holonet\common\di\autowire\provider\ConfigAutoWireProvider;
use holonet\common\di\autowire\provider\ContainerAutoWireProvider;
use holonet\common\di\autowire\provider\ForwardAutoWireProvider;
use holonet\common\di\autowire\provider\ParamAutoWireProvider;
use holonet\common\di\Container;
use ReflectionClass;
use Psr\Container\ContainerInterface;
use holonet\common\di\autowire\AutoWire;
use holonet\common\config\ConfigRegistry;
use holonet\common\di\autowire\AutoWireException;
use ReflectionFunctionAbstract;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionObject;
use ReflectionParameter;
use ReflectionUnionType;
use function holonet\common\get_class_short;
/**
* Compile a static php anonymous class which can create all the current definitions in the container without reflection.
*/
class Compiler {
/**
* @var array<string, string> $aliases key mapping with all available services on the container
*/
protected array $aliases = array();
/**
* @var ParamAutoWireProvider[]
*/
protected array $paramProviders;
/**
* @var array<string, array{string, array}> $wiring Wiring information on how to make certain types of objects.
* Mapped by name / type => class abstract (array with class name and parameters).
*/
protected array $wiring = array();
public function __construct(protected Container $container) {
$reflection = new ReflectionObject($this->container);
$this->aliases = $reflection->getProperty('aliases')->getValue($container);
$this->wiring = $reflection->getProperty('wiring')->getValue($container);
$this->paramProviders = array(
new ForwardAutoWireProvider(),
new ConfigAutoWireProvider(),
new ContainerAutoWireProvider(),
);
}
public function compile(): string {
$methods[] = $this->compileMakeMethod();
foreach ($this->wiring as $alias => $constructor) {
$methods[] = $this->compileWiringMakeMethod($alias);
}
$methods = implode("\n\t", explode("\n", implode("\n\n", $methods)));
return <<<PHP
if (!isset(\$config) || !\$config instanceof \holonet\common\config\ConfigRegistry) {
throw new \InvalidArgumentException('The config parameter must be an instance of \holonet\common\config\ConfigRegistry');
}
return new class(\$config) extends \holonet\common\di\Container {
{$methods}
};
PHP;
}
/**
* Compile a static version of the make() method of a container
*/
private function compileMakeMethod(): string {
$matchStatements = array();
foreach ($this->wiring as $abstract => $constructor) {
$matchKey = "{$abstract}::class";
if (array_key_exists($abstract, $this->aliases)) {
$matchKey = "'{$abstract}'";
}
$matchStatements[] = "\t\t{$matchKey} => \$this->{$this->serviceMakeMethodName($abstract)}()";
}
$matchStatements[] = "\t\tdefault => parent::make(\$abstract, \$extraParams)";
$matchStatements = implode(",\n", $matchStatements);
return <<<PHP
public function make(string \$abstract, array \$extraParams = array()): object {
return match (\$abstract) {
$matchStatements
};
}
PHP;
}
private function serviceMakeMethodName(string $alias): string {
return sprintf('make_%s', str_replace('\\', '_', $alias));
}
private function compileWiringMakeMethod(string $alias): string {
list($class, $params) = $this->wiring[$alias];
return <<<PHP
public function {$this->serviceMakeMethodName($alias)}(): {$class} {
return {$this->compileNewStatement($class, $params)};
}
PHP;
}
private function compileNewStatement(string $class, array $params): string {
$reflection = new ReflectionClass($class);
$constructor = $reflection->getConstructor();
if ($constructor === null) {
if (!empty($params)) {
AutoWireException::failNoConstructor($reflection, $params);
}
return "new $class()";
}
$params = $this->compileAutoWiring($constructor, $params);
return sprintf('new %s(%s)', $class, implode(', ', $params));
}
protected function compileAutoWiring(ReflectionFunctionAbstract $method, array $givenParams): array {
$parameters = $method->getParameters();
$compiled = array();
foreach ($parameters as $param) {
$compiledValue = $this->compileParameter($param, $givenParams[$param->getName()] ?? null);
if ($compiledValue !== null) {
$compiled[] = "{$param->getName()}: {$compiledValue}";
}
}
return $compiled;
}
private function compileParameter(ReflectionParameter $param, mixed $paramValue): ?string {
$paramType = $param->getType();
if ($paramType instanceof ReflectionIntersectionType) {
AutoWireException::failParam($param, 'Cannot auto-wire intersection types');
}
if ($paramType === null) {
if ($param->isOptional()) {
return null;
}
AutoWireException::failParam($param, 'Can only auto-wire typed parameters');
}
if ($paramType instanceof ReflectionUnionType) {
return $this->compileUnionType($param, $paramType, $paramValue);
}
return $this->compileNamedType($param, $paramType, $paramValue);
}
private function compileNamedType(ReflectionParameter $param, ReflectionNamedType $type, mixed $paramValue): ?string {
foreach ($this->paramProviders as $provider) {
$wiredValue = $provider->provide($this->container, $param, $type, $paramValue);
if ($wiredValue !== null) {
return $provider->compile($param, $type, $paramValue);
}
}
if ($param->isOptional()) {
return null;
}
if ($param->allowsNull()) {
return 'null';
}
AutoWireException::failParam($param, "Cannot auto-wire to type '{$type->getName()}'");
}
private function compileUnionType(ReflectionParameter $param, ReflectionUnionType $type, mixed $paramValue): ?string {
$types = $type->getTypes();
$errors = array();
foreach ($this->paramProviders as $provider) {
foreach ($types as $type) {
try {
$wiredValue = $provider->provide($this->container, $param, $type, $paramValue);
if ($wiredValue !== null) {
return $provider->compile($param, $type, $paramValue);
}
} catch (DependencyInjectionException $e) {
$errors[$type->getName()] = $e->getMessage();
}
}
}
$unionType = implode('|', array_map(fn ($type) => $type->getName(), $types));
AutoWireException::failParam($param, "Cannot auto-wire to union type '{$unionType}'");
}
}
diff --git a/9d2d0de62cba14d97a5b48dac1a4873aa0f5e715 b/be4c710d5864027f3fe7b195fbe969c9315b6ba7
index 9d2d0de..be4c710 100644
--- a/9d2d0de62cba14d97a5b48dac1a4873aa0f5e715
+++ b/be4c710d5864027f3fe7b195fbe969c9315b6ba7
@@ -42,7 +42,7 @@ class Container implements ContainerInterface {
protected array $recursionPath = array();
/**
- * @var array<string, array{string, array<string, array>}> $wiring Wiring information on how to make certain types of objects.
+ * @var array<string, array{string, array}> $wiring Wiring information on how to make certain types of objects.
* Mapped by name / type => class abstract (array with class name and parameters).
*/
protected array $wiring = array();
@@ -55,6 +55,8 @@ class Container implements ContainerInterface {
* @template T
* @param class-string<T> $class
* @return T
+ * @psalm-suppress InvalidReturnType
+ * @psalm-suppress InvalidReturnStatement
*/
public function byType(string $class, ?string $id = null): object {
$keys = array_keys($this->aliases, $class);
@@ -111,6 +113,8 @@ class Container implements ContainerInterface {
* @template T
* @param class-string<T>|string $abstract
* @return T
+ * @psalm-suppress InvalidReturnType
+ * @psalm-suppress InvalidReturnStatement
*/
public function make(string $abstract, array $extraParams = array()): object {
if ($this->has($abstract)) {
@@ -147,7 +151,11 @@ class Container implements ContainerInterface {
$reflection = new ReflectionClass($value);
$factoryMethod = $reflection->getMethod('make');
- $this->aliases[$id] = $factoryMethod->getReturnType()->getName();
+ $returnType = $factoryMethod->getReturnType();
+ if (!$returnType instanceof \ReflectionNamedType || $returnType->getName() === 'object') {
+ throw new DependencyInjectionException("Provider factory method {$reflection->getName()}::make() has no return type");
+ }
+ $this->aliases[$id] = $returnType->getName();
$this->wire($reflection->getName(), $params, $id);
return;
@@ -164,7 +172,11 @@ class Container implements ContainerInterface {
if (class_exists($value)) {
$this->aliases[$id] = $value;
$this->wire($value, $params, $id);
+
+ return;
}
+
+ throw new DependencyInjectionException("Could not set dependency '{$id}': value is not an object or class name");
}
/**
@@ -181,7 +193,11 @@ class Container implements ContainerInterface {
$reflection = new ReflectionClass($class);
$factoryMethod = $reflection->getMethod('make');
- $abstract = $factoryMethod->getReturnType()->getName();
+ $returnType = $factoryMethod->getReturnType();
+ if (!$returnType instanceof \ReflectionNamedType || $returnType->getName() === 'object') {
+ throw new DependencyInjectionException("Provider factory method {$class}::make() has no return type");
+ }
+ $abstract = $returnType->getName();
}
$abstract ??= $class;
@@ -190,12 +206,6 @@ class Container implements ContainerInterface {
}
protected function instance(string $class, array $params): object {
- if (is_a($class, Provider::class, true)) {
- $provider = new $class($this);
-
- return $provider->make();
- }
-
$reflection = new ReflectionClass($class);
$constructor = $reflection->getConstructor();
if ($constructor === null) {
@@ -203,11 +213,17 @@ class Container implements ContainerInterface {
AutoWireException::failNoConstructor($reflection, $params);
}
- return new $class();
+ $result = new $class();
+ } else {
+ $params = $this->autoWiring->autoWire($constructor, $params);
+
+ $result = new $class(...$params);
}
- $params = $this->autoWiring->autoWire($constructor, $params);
+ if ($result instanceof Provider) {
+ return $result->make();
+ }
- return new $class(...$params);
+ return $result;
}
}
<?php
/**
* This file is part of the holonet common library
* (c) Matthias Lantsch.
*
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @author Matthias Lantsch <[email protected]>
*/
namespace holonet\common\di;
use holonet\common\di\discovery\ConfigDependencyDiscovery;
use holonet\common\di\discovery\DependencyDiscovery;
use holonet\common\error\BadEnvironmentException;
use holonet\common\config\ConfigRegistry;
/**
* Factory class that is supposed to initialise a container based on a configuration.
*/
class Factory {
/**
* @var DependencyDiscovery[] $discoverers
*/
protected array $discoverers = array();
public function __construct(protected ConfigRegistry $registry = new ConfigRegistry()) {
$this->discoverers[] = new ConfigDependencyDiscovery();
}
public function make(): Container {
if ($this->registry->has('di.cache_path')) {
return $this->makeCompiledContainer();
} else {
return $this->makeContainer();
}
}
private function makeCompiledContainer(): Container {
$cacheFile = $this->cacheFilePath();
$config = $this->registry;
if (file_exists($cacheFile)) {
return require $cacheFile;
}
$container = $this->makeContainer();
$compiler = new Compiler($container);
file_put_contents($cacheFile, "<?php\n\n{$compiler->compile()}");
return require $cacheFile;
}
private function makeContainer(): Container {
$container = new Container($this->registry);
foreach ($this->discoverers as $discoverer) {
$discoverer->discover($container);
}
return $container;
}
private function cacheFilePath(): string {
$dir = $this->registry->get('di.cache_path');
if (!is_dir($dir) || !is_writable($dir)) {
throw new BadEnvironmentException("Container compile path '{$dir}' is not a writable directory");
}
return "{$dir}/container.php";
}
}
diff --git a/1a126f451e855c2371179dd5255b9d1090e46acb b/a804f82c5c539efd54b028000cf3e16af39cc9f2
index 1a126f4..a804f82 100644
--- a/1a126f451e855c2371179dd5255b9d1090e46acb
+++ b/a804f82c5c539efd54b028000cf3e16af39cc9f2
@@ -9,6 +9,7 @@
namespace holonet\common\di\autowire\provider;
+use LogicException;
use ReflectionNamedType;
use ReflectionParameter;
use holonet\common\di\Container;
@@ -51,4 +52,26 @@ class ConfigAutoWireProvider implements ParamAutoWireProvider {
return $container->registry->get($configKey);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public function compile(ReflectionParameter $param, ReflectionNamedType $type, mixed $givenParam): string {
+ $expectedType = $type->getName();
+
+ // if we got called here, it means provide() has returned something, so we can assume the attribute exists
+ $attribute = reflection_get_attribute($param, ConfigItem::class) ?? throw new LogicException();
+
+ $configKey = ($givenParam ?? $attribute->key);
+
+ if (class_exists($expectedType)) {
+ if ($attribute->verified) {
+ return "\$this->registry->verifiedDto('{$configKey}', '{$expectedType}')";
+ }
+
+ return "\$this->registry->asDto('{$configKey}', '{$expectedType}')";
+ }
+
+ return "\$this->registry->get('{$configKey}')";
+ }
}
diff --git a/6a282d4f2dffcb87679baa7707d4897cacdb0101 b/7eb6b88995623024b1d501e53ef2d1330804cf11
index 6a282d4..7eb6b88 100644
--- a/6a282d4f2dffcb87679baa7707d4897cacdb0101
+++ b/7eb6b88995623024b1d501e53ef2d1330804cf11
@@ -31,4 +31,11 @@ class ContainerAutoWireProvider implements ParamAutoWireProvider {
return null;
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public function compile(ReflectionParameter $param, ReflectionNamedType $type, mixed $givenParam): string {
+ return "\$this->byType('{$type->getName()}', '{$param->getName()}')";
+ }
}
diff --git a/555c5d6d9eb50e9f35ffde61feacc50edec78d73 b/f8b50ee3291c2b3768e60a07e677d6cbe20322a4
index 555c5d6..f8b50ee 100644
--- a/555c5d6d9eb50e9f35ffde61feacc50edec78d73
+++ b/f8b50ee3291c2b3768e60a07e677d6cbe20322a4
@@ -31,4 +31,11 @@ class ForwardAutoWireProvider implements ParamAutoWireProvider {
return null;
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public function compile(ReflectionParameter $param, ReflectionNamedType $type, mixed $givenParam): string {
+ return var_export($givenParam, true);
+ }
}
diff --git a/33fe60f2a1e8e2d233c5d65676569b4b2c649a0f b/e449491089da5dcec21766b56311e0e68ef8a203
index 33fe60f..e449491 100644
--- a/33fe60f2a1e8e2d233c5d65676569b4b2c649a0f
+++ b/e449491089da5dcec21766b56311e0e68ef8a203
@@ -31,4 +31,11 @@ interface ParamAutoWireProvider {
* If it is appropriate to return a value but it can't it should throw an exception instead.
*/
public function provide(Container $container, ReflectionParameter $param, ReflectionNamedType $type, mixed $givenParam): mixed;
+
+ /**
+ * Return a php code string representation of getting the autowired parameter value.
+ * This is used to compile the static container class.
+ * Assume that $this in the code refers to the container object.
+ */
+ public function compile(ReflectionParameter $param, ReflectionNamedType $type, mixed $givenParam): string;
}
<?php
/**
* This file is part of the holonet common library
* (c) Matthias Lantsch.
*
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @author Matthias Lantsch <[email protected]>
*/
namespace holonet\common\di\discovery;
use holonet\common\di\Container;
use holonet\common\error\BadEnvironmentException;
/**
* Read auto-wiring definitions from the config registry.
*/
class ConfigDependencyDiscovery implements DependencyDiscovery {
public function discover(Container $container): void {
if (is_array($services = $container->registry->get('di.services'))) {
foreach ($services as $service => $abstract) {
if (is_string($abstract)) {
$abstract = array($abstract);
}
$abstract[1] ??= array();
if (!$this->validateAbstract($abstract)) {
throw BadEnvironmentException::faultyConfig("di.services.{$service}", 'Abstract must be class name or array with class name and parameters');
}
list($class, $params) = $abstract;
$container->set($service, $class, $params);
}
}
if (is_array($classWirings = $container->registry->get('di.auto_wire'))) {
foreach ($classWirings as $name => $abstract) {
if (is_string($abstract)) {
$abstract = array($abstract);
}
$abstract[1] ??= array();
if (!$this->validateAbstract($abstract)) {
throw BadEnvironmentException::faultyConfig("di.auto_wire.{$name}", 'Abstract must be class name or array with class name and parameters');
}
list($class, $params) = $abstract;
if (!is_string($name)) {
$name = null;
}
$container->wire($class, $params, $name);
}
}
}
private function validateAbstract(mixed $abstract): bool {
// validate form and size of abstract array
if(!is_array($abstract) || count($abstract) > 2 || !array_is_list($abstract)) {
return false;
}
// validate class name in the abstract array
if (!is_string($abstract[0]) && !is_object($abstract[0])) {
return false;
}
// validate parameters in the abstract array
if (!is_array($abstract[1])) {
return false;
}
return true;
}
}
<?php
/**
* This file is part of the holonet common library
* (c) Matthias Lantsch.
*
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @author Matthias Lantsch <[email protected]>
*/
namespace holonet\common\di\discovery;
use holonet\common\di\Container;
interface DependencyDiscovery {
/**
* Discover auto-wiring definitions for the Container.
*/
public function discover(Container $container): void;
}
diff --git a/63f09dabbe2c79b34b800a12ce7f9c89b55282d6 b/434a329dd0e23a7131f7862de73bb4563cc1bd72
index 63f09da..434a329 100644
--- a/63f09dabbe2c79b34b800a12ce7f9c89b55282d6
+++ b/434a329dd0e23a7131f7862de73bb4563cc1bd72
@@ -60,11 +60,11 @@ class ErrorHandler {
return null;
}
- list('type' => $type, 'name' => $name) = (self::ERROR_LEVEL_LOOKUP[$errno] ?? self::ERROR_LEVEL_LOOKUP[\E_ERROR]);
+ list('level' => $level, 'name' => $name) = (self::ERROR_LEVEL_LOOKUP[$errno] ?? self::ERROR_LEVEL_LOOKUP[\E_ERROR]);
if ($this->logger !== null) {
$this->logger->log(
- $type,
+ $level,
"{$name}: {$msg}",
array(
'code' => $errno,
diff --git a/4f404341265bc36f5fae637c5c92e9f2c3559f7c b/a3a91deda75f58c115b6a114421878067ab6ab24
index 4f40434..a3a91de 100644
--- a/4f404341265bc36f5fae637c5c92e9f2c3559f7c
+++ b/a3a91deda75f58c115b6a114421878067ab6ab24
@@ -141,23 +141,6 @@ if (!function_exists(__NAMESPACE__.'\\indentText')) {
}
}
-if (!function_exists(__NAMESPACE__.'\\isAssoc')) {
- /**
- * function used to check if an array is associative.
- * @param array $arr The array to check
- * @return bool true or false on is associative or not
- */
- function isAssoc(array $arr): bool {
- if ($arr === array()) {
- return false;
- }
- ksort($arr);
-
- /** @psalm-suppress DocblockTypeContradiction */
- return array_keys($arr) !== range(0, count($arr) - 1);
- }
-}
-
if (!function_exists(__NAMESPACE__.'\\readableDurationString')) {
/**
* function used to transform a duration into a human readable string.
@@ -184,7 +167,7 @@ if (!function_exists(__NAMESPACE__.'\\readableDurationString')) {
return $time / 60 .'min';
}
- return (int)($time / 60).'min '.(int)($time % 60).'s';
+ return (int)($time / 60).'min '. $time % 60 .'s';
}
return $time.'s';
<?php
if (!isset($config) || !$config instanceof \holonet\common\config\ConfigRegistry) {
throw new \InvalidArgumentException('The config parameter must be an instance of \holonet\common\config\ConfigRegistry');
}
return new class($config) extends \holonet\common\di\Container {
public function make(string $abstract, array $extraParams = array()): object {
return match ($abstract) {
'service1' => $this->make_service1(),
default => parent::make($abstract, $extraParams)
};
}
public function make_service1(): holonet\common\tests\di\DiAnonDep {
return new holonet\common\tests\di\DiAnonDep();
}
};
<?php
/**
* This file is part of the hdev common library package
* (c) Matthias Lantsch.
*
* @license http://www.wtfpl.net/ Do what the fuck you want Public License
* @author Matthias Lantsch <[email protected]>
*/
namespace holonet\common\tests\di;
use Countable;
use FilesystemIterator;
use holonet\common\config\ConfigRegistry;
use holonet\common\di\autowire\provider\ConfigAutoWireProvider;
use holonet\common\di\autowire\provider\ContainerAutoWireProvider;
use holonet\common\di\autowire\provider\ForwardAutoWireProvider;
use holonet\common\di\Compiler;
use holonet\common\di\Factory;
use holonet\common\Noun;
use Stringable;
use PHPUnit\Framework\TestCase;
use holonet\common\di\Container;
use holonet\common\di\autowire\AutoWire;
use PHPUnit\Framework\Attributes\CoversClass;
use holonet\common\di\autowire\AutoWireException;
use holonet\common\di\DependencyNotFoundException;
use holonet\common\di\DependencyInjectionException;
#[CoversClass(Container::class)]
#[CoversClass(Compiler::class)]
#[CoversClass(AutoWire::class)]
#[CoversClass(AutoWireException::class)]
#[CoversClass(ConfigAutoWireProvider::class)]
#[CoversClass(ContainerAutoWireProvider::class)]
#[CoversClass(ForwardAutoWireProvider::class)]
class CompilerTest extends TestCase
{
public function testParameterForwardCompile(): void
{
$container = new Container();
$params = array(
'string' => 'gojsdgoisjdgio',
'int' => 5,
'float' => 10.5,
'boolean' => true,
'array' => array('value1', 'value2')
);
$container->wire(DependencyForwardAutoWire::class, $params);
$compiler = new Compiler($container);
$compiled = <<<'COMPILED'
if (!isset($config) || !$config instanceof \holonet\common\config\ConfigRegistry) {
throw new \InvalidArgumentException('The config parameter must be an instance of \holonet\common\config\ConfigRegistry');
}
return new class($config) extends \holonet\common\di\Container {
public function make(string $abstract, array $extraParams = array()): object {
return match ($abstract) {
holonet\common\tests\di\DependencyForwardAutoWire::class => $this->make_holonet_common_tests_di_DependencyForwardAutoWire(),
default => parent::make($abstract, $extraParams)
};
}
public function make_holonet_common_tests_di_DependencyForwardAutoWire(): holonet\common\tests\di\DependencyForwardAutoWire {
return new holonet\common\tests\di\DependencyForwardAutoWire(string: 'gojsdgoisjdgio', int: 5, float: 10.5, boolean: true, array: array (
0 => 'value1',
1 => 'value2',
));
}
};
COMPILED;
$actual = $compiler->compile();
$this->assertEqualsIgnoringIndentation($compiled, $actual);
$this->assertValidCompiledContainer($actual, $container->registry);
}
public function testClassWithoutConstructorProvidedParams(): void
{
$this->expectException(AutoWireException::class);
$this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\NoConstructorDependency\': Has no constructor, but 1 parameters were given');
$container = new Container();
$container->wire(NoConstructorDependency::class, array('test' => 'value'));
$compiler = new Compiler($container);
$compiler->compile();
}
public function testClassWithoutConstructorCompiles(): void
{
$container = new Container();
$container->wire(NoConstructorDependency::class);
$compiler = new Compiler($container);
$expected = <<<'COMPILED'
if (!isset($config) || !$config instanceof \holonet\common\config\ConfigRegistry) {
throw new \InvalidArgumentException('The config parameter must be an instance of \holonet\common\config\ConfigRegistry');
}
return new class($config) extends \holonet\common\di\Container {
public function make(string $abstract, array $extraParams = array()): object {
return match ($abstract) {
holonet\common\tests\di\NoConstructorDependency::class => $this->make_holonet_common_tests_di_NoConstructorDependency(),
default => parent::make($abstract, $extraParams)
};
}
public function make_holonet_common_tests_di_NoConstructorDependency(): holonet\common\tests\di\NoConstructorDependency {
return new holonet\common\tests\di\NoConstructorDependency();
}
};
COMPILED;
$actual = $compiler->compile();
$this->assertEqualsIgnoringIndentation($expected, $actual);
$this->assertValidCompiledContainer($actual, $container->registry);
}
public function testIntersectionTypesCannotBeCompiled(): void
{
$this->expectException(AutoWireException::class);
$this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\DependencyWithIntersectionType::__construct\': Parameter #0: param: Cannot auto-wire intersection types');
$container = new Container();
$container->wire(DependencyWithIntersectionType::class);
$compiler = new Compiler($container);
$compiler->compile();
}
public function testCannotAutowireUntypedParameter(): void
{
$this->expectException(AutoWireException::class);
$this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\UntypedParamsDependency::__construct\': Parameter #0: param1: Can only auto-wire typed parameters');
$container = new Container();
$container->wire(UntypedParamsDependency::class);
$compiler = new Compiler($container);
$compiler->compile();
}
public function testCanAutowireUntypedOptionalParameter(): void
{
$container = new Container();
$container->wire(UntypedParamOptionalDependency::class);
$compiler = new Compiler($container);
$this->assertNotEmpty($compiler->compile());
}
public function testCompilesOptionalOrNullableParameter(): void
{
$container = new Container();
$container->wire(OptionalAndNullableParamsDependency::class);
$compiler = new Compiler($container);
$expected = <<<'COMPILED'
if (!isset($config) || !$config instanceof \holonet\common\config\ConfigRegistry) {
throw new \InvalidArgumentException('The config parameter must be an instance of \holonet\common\config\ConfigRegistry');
}
return new class($config) extends \holonet\common\di\Container {
public function make(string $abstract, array $extraParams = array()): object {
return match ($abstract) {
holonet\common\tests\di\OptionalAndNullableParamsDependency::class => $this->make_holonet_common_tests_di_OptionalAndNullableParamsDependency(),
default => parent::make($abstract, $extraParams)
};
}
public function make_holonet_common_tests_di_OptionalAndNullableParamsDependency(): holonet\common\tests\di\OptionalAndNullableParamsDependency {
return new holonet\common\tests\di\OptionalAndNullableParamsDependency(param1: null);
}
};
COMPILED;
$actual = $compiler->compile();
$this->assertEqualsIgnoringIndentation($expected, $actual);
$this->assertValidCompiledContainer($actual, $container->registry);
}
public function testUnionTypeCanBeCompiled(): void
{
$container = new Container();
$container->wire(UnionTypeDependency::class);
$compiler = new Compiler($container);
$expected = <<<'COMPILED'
if (!isset($config) || !$config instanceof \holonet\common\config\ConfigRegistry) {
throw new \InvalidArgumentException('The config parameter must be an instance of \holonet\common\config\ConfigRegistry');
}
return new class($config) extends \holonet\common\di\Container {
public function make(string $abstract, array $extraParams = array()): object {
return match ($abstract) {
holonet\common\tests\di\UnionTypeDependency::class => $this->make_holonet_common_tests_di_UnionTypeDependency(),
default => parent::make($abstract, $extraParams)
};
}
public function make_holonet_common_tests_di_UnionTypeDependency(): holonet\common\tests\di\UnionTypeDependency {
return new holonet\common\tests\di\UnionTypeDependency(param: $this->byType('holonet\common\Noun', 'param'));
}
};
COMPILED;
$actual = $compiler->compile();
$this->assertEqualsIgnoringIndentation($expected, $actual);
$this->assertValidCompiledContainer($actual, $container->registry);
}
public function testCannotCompileNonWireableDependency(): void {
$this->expectException(AutoWireException::class);
$this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\NonWireableDependency::__construct\': Parameter #0: param: Cannot auto-wire to type \'string\'');
$container = new Container();
$container->wire(NonWireableDependency::class);
$compiler = new Compiler($container);
$compiler->compile();
}
public function testUnionTypeNonWireable(): void {
$this->expectException(AutoWireException::class);
$this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\DependencyTest::__construct\': Parameter #0: param: Cannot auto-wire to union type \'holonet\common\tests\di\NonWireableDependency|string\'');
$container = new Container();
$container->wire(DependencyTest::class);
$compiler = new Compiler($container);
$compiler->compile();
}
public function testCompileConfigParam(): void {
$container = new Container();
$value = array('test', 'cool');
$container->registry->set('config.just_an_array_value', $value);
$container->registry->set('service.other', array('stringValue' => 'test'));
$container->registry->set('service.config', array('stringValue' => 'test'));
$container->wire(ServiceWithArrayConfigValue::class);
$container->wire(OtherDependency::class);
$container->wire(Dependency::class, array('config' => 'service.config'));
$compiler = new Compiler($container);
$expected = <<<'COMPILED'
if (!isset($config) || !$config instanceof \holonet\common\config\ConfigRegistry) {
throw new \InvalidArgumentException('The config parameter must be an instance of \holonet\common\config\ConfigRegistry');
}
return new class($config) extends \holonet\common\di\Container {
public function make(string $abstract, array $extraParams = array()): object {
return match ($abstract) {
holonet\common\tests\di\ServiceWithArrayConfigValue::class => $this->make_holonet_common_tests_di_ServiceWithArrayConfigValue(),
holonet\common\tests\di\OtherDependency::class => $this->make_holonet_common_tests_di_OtherDependency(),
holonet\common\tests\di\Dependency::class => $this->make_holonet_common_tests_di_Dependency(),
default => parent::make($abstract, $extraParams)
};
}
public function make_holonet_common_tests_di_ServiceWithArrayConfigValue(): holonet\common\tests\di\ServiceWithArrayConfigValue {
return new holonet\common\tests\di\ServiceWithArrayConfigValue(value: $this->registry->get('config.just_an_array_value'));
}
public function make_holonet_common_tests_di_OtherDependency(): holonet\common\tests\di\OtherDependency {
return new holonet\common\tests\di\OtherDependency(config: $this->registry->verifiedDto('service.other', 'holonet\common\tests\di\Config'));
}
public function make_holonet_common_tests_di_Dependency(): holonet\common\tests\di\Dependency {
return new holonet\common\tests\di\Dependency(config: $this->registry->asDto('service.config', 'holonet\common\tests\di\Config'));
}
};
COMPILED;
$actual = $compiler->compile();
$this->assertEqualsIgnoringIndentation($expected, $actual);
$this->assertValidCompiledContainer($actual, $container->registry);
}
public function testCompileNamedService(): void {
$registry = new ConfigRegistry();
$container = new Container($registry);
$container->set('service1', DiAnonDep::class);
$compiler = new Compiler($container);
$expected = <<<'COMPILED'
if (!isset($config) || !$config instanceof \holonet\common\config\ConfigRegistry) {
throw new \InvalidArgumentException('The config parameter must be an instance of \holonet\common\config\ConfigRegistry');
}
return new class($config) extends \holonet\common\di\Container {
public function make(string $abstract, array $extraParams = array()): object {
return match ($abstract) {
'service1' => $this->make_service1(),
default => parent::make($abstract, $extraParams)
};
}
public function make_service1(): holonet\common\tests\di\DiAnonDep {
return new holonet\common\tests\di\DiAnonDep();
}
};
COMPILED;
$actual = $compiler->compile();
$this->assertEqualsIgnoringIndentation($expected, $actual);
$this->assertValidCompiledContainer($actual, $registry);
}
protected function assertValidCompiledContainer(string $code, ConfigRegistry $config): void {
$container = eval("{$code}");
$this->assertTrue(str_contains(get_class($container), '@anonymous'));
$this->assertInstanceOf(Container::class, $container);
}
protected function assertEqualsIgnoringIndentation(string $expect, string $actual, string $message = ''): void
{
$expect = preg_replace('/\s+/', ' ', $expect);
$actual = preg_replace('/\s+/', ' ', $actual);
$this->assertEquals($expect, $actual, $message);
}
}
class DependencyTest
{
public function __construct(NonWireableDependency|string $param)
{
}
}
class NonWireableDependency
{
public function __construct(string $param)
{
}
}
class UnionTypeDependency
{
public function __construct(string|Noun $param)
{
}
}
class OptionalAndNullableParamsDependency
{
public function __construct(?string $param1, string $param2 = 'default')
{
}
}
class UntypedParamOptionalDependency
{
public function __construct($param1 = null)
{
}
}
class UntypedParamsDependency
{
public function __construct($param1, $param2 = null)
{
}
}
class NoConstructorDependency
{
}
class DependencyWithIntersectionType
{
public function __construct(Stringable&Countable $param)
{
}
}
diff --git a/0df0d02e596d31fad3620fb5c7f61ad88afc3c21 b/e6c8bd796e8437689fe533201abee7263bf65335
index 0df0d02..e6c8bd7 100644
--- a/0df0d02e596d31fad3620fb5c7f61ad88afc3c21
+++ b/e6c8bd796e8437689fe533201abee7263bf65335
@@ -7,7 +7,7 @@
* @author Matthias Lantsch <[email protected]>
*/
-namespace holonet\common\tests;
+namespace holonet\common\tests\di;
use PHPUnit\Framework\TestCase;
use holonet\common\di\Container;
@@ -86,7 +86,7 @@ class ConfigAutoWireProviderTest extends TestCase {
public function testUserMustSupplyConfigKeyForInjection(): void {
$this->expectException(DependencyInjectionException::class);
- $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\Dependency::__construct\': Parameter #0: config: Cannot auto-wire to a config dto object without supplying a config key');
+ $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\Dependency::__construct\': Parameter #0: config: Cannot auto-wire to a config dto object without supplying a config key');
$container = new Container();
<?php
/**
* This file is part of the hdev common library package
* (c) Matthias Lantsch.
*
* @license http://www.wtfpl.net/ Do what the fuck you want Public License
* @author Matthias Lantsch <[email protected]>
*/
namespace holonet\common\tests\di;
use holonet\common\collection\Registry;
use holonet\common\config\ConfigRegistry;
use holonet\common\di\discovery\ConfigDependencyDiscovery;
use PHPUnit\Framework\TestCase;
use holonet\common\di\Container;
use holonet\common\di\autowire\AutoWire;
use PHPUnit\Framework\Attributes\CoversClass;
use holonet\common\error\BadEnvironmentException;
use holonet\common\di\DependencyInjectionException;
use holonet\common\verifier\rules\string\MaxLength;
use holonet\common\di\autowire\attribute\ConfigItem;
use holonet\common\di\autowire\provider\ConfigAutoWireProvider;
#[CoversClass(ConfigDependencyDiscovery::class)]
class ConfigDependencyDiscoveryTest extends TestCase {
public function testDiscover() {
$registry = new ConfigRegistry();
$registry->set('di', [
'services' => [
'service1' => SimpleDependency::class,
'service2' => [DependencyWithParameter::class, ['mustBeSuppliedParameter' => 'value1']],
'service3' => AutoWire::class,
],
'auto_wire' => [
'name1' => SimpleDependency::class,
'name2' => [DependencyWithParameter::class, ['mustBeSuppliedParameter' => 'value1']],
'name3' => AutoWire::class,
],
]);
$container = new Container($registry);
$dependencyDiscovery = new ConfigDependencyDiscovery();
$dependencyDiscovery->discover($container);
$this->assertInstanceOf(SimpleDependency::class, $container->get('service1'));
$this->assertInstanceOf(DependencyWithParameter::class, $container->get('service2'));
$this->assertInstanceOf(AutoWire::class, $container->get('service3'));
$this->assertInstanceOf(SimpleDependency::class, $container->make('name1'));
$this->assertInstanceOf(DependencyWithParameter::class, $container->make('name2'));
$this->assertInstanceOf(AutoWire::class, $container->make('name3'));
}
public function testInvalidOrEmptyAbstractDefinitionServices(): void {
$this->expectException(BadEnvironmentException::class);
$this->expectExceptionMessage('Faulty config with key \'di.services.service1\': Abstract must be class name or array with class name and parameters');
$registry = new ConfigRegistry();
$registry->set('di.services', ['service1' => []]);
$container = new Container($registry);
$dependencyDiscovery = new ConfigDependencyDiscovery();
$dependencyDiscovery->discover($container);
$this->expectExceptionMessage('Faulty config with key \'di.services.service1\': Abstract must be class name or array with class name and parameters');
$registry = new ConfigRegistry();
$registry->set('di.services', ['service1' => ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]]);
$container = new Container($registry);
$dependencyDiscovery = new ConfigDependencyDiscovery();
$dependencyDiscovery->discover($container);
}
public function testInvalidOrEmptyAbstractDefinition(): void {
$this->expectException(BadEnvironmentException::class);
$this->expectExceptionMessage('Faulty config with key \'di.auto_wire.service1\': Abstract must be class name or array with class name and parameters');
$registry = new ConfigRegistry();
$registry->set('di.auto_wire', ['service1' => []]);
$container = new Container($registry);
$dependencyDiscovery = new ConfigDependencyDiscovery();
$dependencyDiscovery->discover($container);
$this->expectExceptionMessage('Faulty config with key \'di.auto_wire.service1\': Abstract must be class name or array with class name and parameters');
$registry = new ConfigRegistry();
$registry->set('di.auto_wire', ['service1' => ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]]);
$container = new Container($registry);
$dependencyDiscovery = new ConfigDependencyDiscovery();
$dependencyDiscovery->discover($container);
}
public function testInvalidClassNameAbstract(): void {
$this->expectException(BadEnvironmentException::class);
$this->expectExceptionMessage('Faulty config with key \'di.auto_wire.service1\': Abstract must be class name or array with class name and parameters');
$registry = new ConfigRegistry();
$registry->set('di.auto_wire', ['service1' => [100500, ['param1' => 'cool']]]);
$container = new Container($registry);
$dependencyDiscovery = new ConfigDependencyDiscovery();
$dependencyDiscovery->discover($container);
}
public function testNonStringWiringKeyIsIgnored(): void {
$this->expectException(DependencyInjectionException::class);
$this->expectExceptionMessage('No idea how to make \'5251\'. Class does not exist and no wire directive was set');
$registry = new ConfigRegistry();
$registry->set('di.auto_wire', [5251 => AutoWire::class]);
$container = new Container($registry);
$dependencyDiscovery = new ConfigDependencyDiscovery();
$dependencyDiscovery->discover($container);
$container->make(5251);
}
public function testInvalidParameters(): void {
$this->expectException(BadEnvironmentException::class);
$this->expectExceptionMessage('Faulty config with key \'di.services.service1\': Abstract must be class name or array with class name and parameters');
$registry = new ConfigRegistry();
$registry->set('di.services', ['service1' => [SimpleDependency::class, 'not an array']]);
$container = new Container($registry);
$dependencyDiscovery = new ConfigDependencyDiscovery();
$dependencyDiscovery->discover($container);
}
}
class SimpleDependency {
}
diff --git a/3f71c359ff1bdf75071ed6011e7581300f32293b b/bd8a7a0e03147aa299dbd54274c2ceec02366369
index 3f71c35..bd8a7a0 100644
--- a/3f71c359ff1bdf75071ed6011e7581300f32293b
+++ b/bd8a7a0e03147aa299dbd54274c2ceec02366369
@@ -7,7 +7,7 @@
* @author Matthias Lantsch <[email protected]>
*/
-namespace holonet\common\tests;
+namespace holonet\common\tests\di;
use PHPUnit\Framework\TestCase;
use holonet\common\di\Container;
@@ -25,7 +25,7 @@ use holonet\common\di\autowire\provider\ContainerAutoWireProvider;
class ContainerAutoWireProviderTest extends TestCase {
public function testInjectionFailedThrowsException(): void {
$this->expectException(DependencyInjectionException::class);
- $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\DependencyWithParameter::__construct\': Parameter #0: mustBeSuppliedParameter: Cannot auto-wire to type \'string\'');
+ $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\DependencyWithParameter::__construct\': Parameter #0: mustBeSuppliedParameter: Cannot auto-wire to type \'string\'');
$container = new Container();
diff --git a/3ff8253d93e1ba3ce84dbb936608920ad6ae4548 b/6bab33777b0b63b90b277bfd0dc760658f2656b2
index 3ff8253..6bab337 100644
--- a/3ff8253d93e1ba3ce84dbb936608920ad6ae4548
+++ b/6bab33777b0b63b90b277bfd0dc760658f2656b2
@@ -7,7 +7,7 @@
* @author Matthias Lantsch <[email protected]>
*/
-namespace holonet\common\tests;
+namespace holonet\common\tests\di;
use Countable;
use Stringable;
@@ -42,7 +42,7 @@ class ContainerTest extends TestCase {
public function testIntersectionTypesCannotBeAutoWired(): void {
$this->expectException(AutoWireException::class);
- $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\IntersectionTypes::__construct\': Parameter #0: intersection: Cannot auto-wire intersection types');
+ $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\IntersectionTypes::__construct\': Parameter #0: intersection: Cannot auto-wire intersection types');
$container = new Container();
@@ -61,7 +61,7 @@ class ContainerTest extends TestCase {
public function testMakeCalledWithJustAnInterface(): void {
$this->expectException(DependencyInjectionException::class);
- $this->expectExceptionMessage('No idea how to make \'holonet\common\tests\MyInterface\'. Class does not exist and no wire directive was set');
+ $this->expectExceptionMessage('No idea how to make \'holonet\common\tests\di\MyInterface\'. Class does not exist and no wire directive was set');
$container = new Container();
@@ -88,7 +88,7 @@ class ContainerTest extends TestCase {
public function testMakeThrowsErrorIfConstructorArgumentsAreNotGiven(): void {
$this->expectException(DependencyInjectionException::class);
- $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\SomeService::__construct\': Parameter #0: parameter: Cannot auto-wire to type \'string\'');
+ $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\SomeService::__construct\': Parameter #0: parameter: Cannot auto-wire to type \'string\'');
$container = new Container();
@@ -97,7 +97,7 @@ class ContainerTest extends TestCase {
public function testMakeThrowsErrorIfParametersAreGivenForConstructorLessAbstract(): void {
$this->expectException(DependencyInjectionException::class);
- $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\DiAnonDep\': Has no constructor, but 1 parameters were given');
+ $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\DiAnonDep\': Has no constructor, but 1 parameters were given');
$container = new Container();
@@ -126,7 +126,7 @@ class ContainerTest extends TestCase {
// if accessing by type without supplying a name hint, an exception should be thrown
$this->expectException(DependencyInjectionException::class);
- $this->expectExceptionMessage('Ambiguous dependency of type \'holonet\common\tests\DiAnonDep\' requested: found 2 dependencies of that type');
+ $this->expectExceptionMessage('Ambiguous dependency of type \'holonet\common\tests\di\DiAnonDep\' requested: found 2 dependencies of that type');
$container->byType(DiAnonDep::class);
}
@@ -140,7 +140,7 @@ class ContainerTest extends TestCase {
public function testRecursionDetectionMake(): void {
$this->expectException(DependencyInjectionException::class);
- $this->expectExceptionMessage('Recursive dependency definition detected: holonet\common\tests\RecursionA => holonet\common\tests\RecursionB => holonet\common\tests\RecursionC');
+ $this->expectExceptionMessage('Recursive dependency definition detected: holonet\common\tests\di\RecursionA => holonet\common\tests\di\RecursionB => holonet\common\tests\di\RecursionC');
$container = new Container();
@@ -162,7 +162,7 @@ class ContainerTest extends TestCase {
public function testTypelessParametersThrowAnException(): void {
$this->expectException(AutoWireException::class);
- $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\TypelessClass::__construct\': Parameter #0: test: Can only auto-wire typed parameters');
+ $this->expectExceptionMessage('Failed to auto-wire \'holonet\common\tests\di\TypelessClass::__construct\': Parameter #0: test: Can only auto-wire typed parameters');
$container = new Container();
@@ -172,9 +172,9 @@ class ContainerTest extends TestCase {
public function testUnionTypesFailedInjection(): void {
$this->expectException(AutoWireException::class);
$this->expectExceptionMessage(<<<'Message'
- Failed to auto-wire 'holonet\common\tests\UnionTypesMultipleFailures::__construct': Parameter #0: service: Cannot auto-wire to union type 'holonet\common\tests\SomeService|holonet\common\tests\SomeServiceTwo':
- Failed to auto-wire 'holonet\common\tests\SomeService::__construct': Parameter #0: parameter: Cannot auto-wire to type 'string'
- Failed to auto-wire 'holonet\common\tests\SomeServiceTwo::__construct': Parameter #0: parameter: Cannot auto-wire to type 'string'
+ Failed to auto-wire 'holonet\common\tests\di\UnionTypesMultipleFailures::__construct': Parameter #0: service: Cannot auto-wire to union type 'holonet\common\tests\di\SomeService|holonet\common\tests\di\SomeServiceTwo':
+ Failed to auto-wire 'holonet\common\tests\di\SomeService::__construct': Parameter #0: parameter: Cannot auto-wire to type 'string'
+ Failed to auto-wire 'holonet\common\tests\di\SomeServiceTwo::__construct': Parameter #0: parameter: Cannot auto-wire to type 'string'
Message
);
@@ -213,6 +213,15 @@ class ContainerTest extends TestCase {
$this->assertInstanceOf(TestClass::class, $container->make(MyInterface::class));
$this->assertInstanceOf(TestClass::class, $container->make(AbstractBaseClass::class));
}
+
+ public function testSetNonsenseAsService(): void {
+ $this->expectException(DependencyInjectionException::class);
+ $this->expectExceptionMessage('Could not set dependency \'nonsense\': value is not an object or class name');
+
+ $container = new Container();
+
+ $container->set('nonsense', '\\nonsense\\class\\TestClass');
+ }
}
class RecursionA {
<?php
/**
* This file is part of the hdev common library package
* (c) Matthias Lantsch.
*
* @license http://www.wtfpl.net/ Do what the fuck you want Public License
* @author Matthias Lantsch <[email protected]>
*/
namespace holonet\common\tests\di;
use holonet\common\config\ConfigRegistry;
use holonet\common\di\Container;
use holonet\common\di\Factory;
use holonet\common\error\BadEnvironmentException;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use holonet\common\di\autowire\AutoWireException;
use holonet\common\di\DependencyNotFoundException;
use holonet\common\di\DependencyInjectionException;
use ReflectionObject;
use function holonet\common\get_class_short;
#[CoversClass(Factory::class)]
class FactoryTest extends TestCase {
public function testCompilerDisabledFactory(): void {
$registry = new ConfigRegistry();
$registry->set('di.services', ['service1' => [Dependency::class]]);
$factory = new Factory($registry);
$container = $factory->make();
$this->assertSame(Container::class, get_class($container));
}
public function testFactoryReturnsOldCompiledContainer(): void {
$this->assertFileExists(dirname(__DIR__).'/data/container.php');
$registry = new ConfigRegistry();
$registry->set('di.services', ['service1' => [DiAnonDep::class]]);
$registry->set('di.cache_path', dirname(__DIR__).'/data');
$factory = new Factory($registry);
$container = $factory->make();
$this->assertTrue(str_contains(get_class($container), '@anonymous'));
$reflection = new ReflectionObject($container);
$this->assertTrue($reflection->hasMethod('make_service1'), 'Factory did not create the make_service1 method');
$this->assertInstanceOf(Container::class, $container);
}
public function testCompileContainerToFile(): void {
@unlink(dirname(__DIR__).'/data/container.php');
$this->assertFileDoesNotExist(dirname(__DIR__).'/data/container.php');
$registry = new ConfigRegistry();
$registry->set('di.services', ['service1' => DiAnonDep::class]);
$registry->set('di.cache_path', dirname(__DIR__).'/data');
$factory = new Factory($registry);
$container = $factory->make();
$this->assertTrue(str_contains(get_class($container), '@anonymous'));
$reflection = new ReflectionObject($container);
$this->assertTrue($reflection->hasMethod('make_service1'), 'Factory did not create the make_service1 method');
$this->assertInstanceOf(Container::class, $container);
$this->assertFileExists(dirname(__DIR__).'/data/container.php');
}
public function testBadCachePathCausesAnException(): void {
$this->expectException(BadEnvironmentException::class);
$this->expectExceptionMessage('Container compile path \'/rubbish/path/does/not/exist\' is not a writable directory');
$registry = new ConfigRegistry();
$registry->set('di.services', ['service1' => DiAnonDep::class]);
$registry->set('di.cache_path', '/rubbish/path/does/not/exist');
$factory = new Factory($registry);
$factory->make();
}
}
diff --git a/fc27e47f43acf4fe52452935f063a8e5a6745082 b/33e9ac84ce4b1cf2333b1db321c545cd301be8bc
index fc27e47..33e9ac8 100644
--- a/fc27e47f43acf4fe52452935f063a8e5a6745082
+++ b/33e9ac84ce4b1cf2333b1db321c545cd301be8bc
@@ -7,7 +7,7 @@
* @author Matthias Lantsch <[email protected]>
*/
-namespace holonet\common\tests;
+namespace holonet\common\tests\di;
use PHPUnit\Framework\TestCase;
use holonet\common\di\Container;
diff --git a/2bcbbb1f347c8d3e6e8e6ef1d28f2a0b21896bff b/cfa19927af3ad5d28a925a0efc29ca6a47151666
index 2bcbbb1..cfa1992 100644
--- a/2bcbbb1f347c8d3e6e8e6ef1d28f2a0b21896bff
+++ b/cfa19927af3ad5d28a925a0efc29ca6a47151666
@@ -7,7 +7,7 @@
* @author Matthias Lantsch <[email protected]>
*/
-namespace holonet\common\tests;
+namespace holonet\common\tests\di;
use Countable;
use holonet\common\di\Provider;
@@ -23,10 +23,6 @@ use holonet\common\di\DependencyInjectionException;
#[CoversClass(Container::class)]
#[CoversClass(Provider::class)]
class ProviderTest extends TestCase {
- // test provider for service
- // test provider for instance
- // test provider for type byType
- // test provider calling make() with the actual class
public function testProviderSetForService(): void {
$container = new Container();
@@ -71,6 +67,24 @@ class ProviderTest extends TestCase {
$this->assertInstanceOf(ProvidedDependency::class, $result);
$this->assertFalse($result === $container->make(ProvidedDependency::class));
}
+
+ public function testMakeWithBadProviderWithoutTypehintCausesException(): void {
+ $this->expectException(DependencyInjectionException::class);
+ $this->expectExceptionMessage('Provider factory method holonet\common\tests\di\BadProviderWithoutTypehint::make() has no return type');
+
+ $container = new Container();
+
+ $container->wire(BadProviderWithoutTypehint::class);
+ }
+
+ public function testSetWithBadProviderWithoutTypehintCausesException(): void {
+ $this->expectException(DependencyInjectionException::class);
+ $this->expectExceptionMessage('Provider factory method holonet\common\tests\di\BadProviderWithoutTypehint::make() has no return type');
+
+ $container = new Container();
+
+ $container->set('test', BadProviderWithoutTypehint::class);
+ }
}
class TestProvider extends Provider {
@@ -81,6 +95,14 @@ class TestProvider extends Provider {
}
+class BadProviderWithoutTypehint extends Provider {
+
+ public function make(): object {
+ return new ProvidedDependency();
+ }
+
+}
+
class ProvidedDependency {
}