Matthias Lantsch(1 year, 1 month ago)
New dependency container; Use reflection to inject dependencies straight into the constructor
Browse Filesdiff --git a/fb87637f5789154a756e5bd96a9ed70053a0c14c b/c1d5638deaf7b1c51c309857c7e807c64efcd6e9
index fb87637..c1d5638 100644
--- a/fb87637f5789154a756e5bd96a9ed70053a0c14c
+++ b/c1d5638deaf7b1c51c309857c7e807c64efcd6e9
@@ -6,4 +6,4 @@
tests/coverage/*
/composer.lock
.idea/
-
+.phpunit.cache/*
diff --git a/37d4bfaa150067f345365be862a851b1ed6eb797 b/8f4f756c403f1c26cc94027b78c732f59bb5605b
index 37d4bfa..8f4f756 100644
--- a/37d4bfaa150067f345365be862a851b1ed6eb797
+++ b/8f4f756c403f1c26cc94027b78c732f59bb5605b
@@ -1,6 +1,9 @@
<?php
$config = require 'vendor/holonet/hdev/.php-cs-fixer.dist.php';
+$config->setRules([
+ 'php_unit_test_class_requires_covers' => false
+]);
$config->setFinder(PhpCsFixer\Finder::create()
->exclude('vendor')
->exclude('tests/data')
diff --git a/31b6bd769cb6f516c6a7a4c79def5b3f8b16e8cc b/097579b7772e2459e7e7e22a743958a8a7cba60e
index 31b6bd7..097579b 100644
--- a/31b6bd769cb6f516c6a7a4c79def5b3f8b16e8cc
+++ b/097579b7772e2459e7e7e22a743958a8a7cba60e
@@ -15,7 +15,7 @@
},
"require-dev": {
"holonet/hdev": "~1.1.0@dev",
- "phpunit/phpunit": "^9.5"
+ "phpunit/phpunit": "^10.0.0"
},
"provide": {
"psr/container-implementation": "2.0.2"
@@ -26,6 +26,7 @@
"url": "https://holonet.easylabs.ch/hgit/composer/"
}
],
+ "minimum-stability": "dev",
"autoload": {
"psr-4": {
"holonet\\common\\": "src/"
diff --git a/dcfabe5d30bfe0cff973c8208a48d78cfa85ae6f b/20dd276382c6594baa54d3f351f845a1e9e7cfa3
index dcfabe5..20dd276 100644
--- a/dcfabe5d30bfe0cff973c8208a48d78cfa85ae6f
+++ b/20dd276382c6594baa54d3f351f845a1e9e7cfa3
@@ -1,16 +1,16 @@
-<phpunit
- forceCoversAnnotation="true"
- bootstrap="vendor/autoload.php"
- colors="true">
+<?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">
<testsuite name="tests">
<directory>./tests</directory>
</testsuite>
- <coverage processUncoveredFiles="true">
- <include>
- <directory suffix=".php">./src</directory>
- </include>
+ <coverage>
<report>
<html outputDirectory="tests/coverage/html"/>
</report>
</coverage>
+ <source>
+ <include>
+ <directory suffix=".php">./src</directory>
+ </include>
+ </source>
</phpunit>
diff --git a/438250bf06145e861d2a47ff32f2ddbc6f498a4b b/9cd09b5663fc7ee8ef75de35be5def0279e7b7a2
index 438250b..9cd09b5 100644
--- a/438250bf06145e861d2a47ff32f2ddbc6f498a4b
+++ b/9cd09b5663fc7ee8ef75de35be5def0279e7b7a2
@@ -21,11 +21,15 @@ use holonet\common\error\BadEnvironmentException;
class ConfigRegistry extends Registry {
/**
* @template T
- * Get a verified instance of a config dto class supplied by the user.
+ * Get an instance of a config dto class supplied by the user.
* @param class-string<T>|T $cfgDto
* @return T
*/
- public function verifiedDto(string $configKey, string|object $cfgDto): object {
+ public function asDto(string $configKey, string|object $cfgDto): object {
+ if (!$this->has($configKey)) {
+ throw BadEnvironmentException::faultyConfig($configKey, 'Config item doesn\'t exist');
+ }
+
$value = $this->get($configKey);
if (!is_array($value)) {
$value = array($value);
@@ -41,6 +45,18 @@ class ConfigRegistry extends Registry {
throw BadEnvironmentException::faultyConfig($configKey, "TypeError: {$e->getMessage()}");
}
+ return $cfgDto;
+ }
+
+ /**
+ * @template T
+ * Get a verified instance of a config dto class supplied by the user.
+ * @param class-string<T>|T $cfgDto
+ * @return T
+ */
+ public function verifiedDto(string $configKey, string|object $cfgDto): object {
+ $cfgDto = $this->asDto($configKey, $cfgDto);
+
$proof = verify($cfgDto);
if ($proof->pass()) {
return $cfgDto;
diff --git a/72fa9456440f9ab849554b8370a8c2f1fba63060 b/5c0e9405c1307a4ff5cfa11a8ba408317d2fd718
index 72fa945..5c0e940 100644
--- a/72fa9456440f9ab849554b8370a8c2f1fba63060
+++ b/5c0e9405c1307a4ff5cfa11a8ba408317d2fd718
@@ -9,110 +9,182 @@
namespace holonet\common\di;
-use TypeError;
use ReflectionClass;
-use ReflectionException;
use Psr\Container\ContainerInterface;
+use holonet\common\di\autowire\AutoWire;
+use holonet\common\config\ConfigRegistry;
+use holonet\common\di\autowire\AutoWireException;
/**
* Dependency Injection container conforming with PSR-11.
*/
class Container implements ContainerInterface {
/**
- * @var string DI_PREFIX Prefix value for the injected class properties
+ * @var array<string, string> $aliases key mapping with all available services on the container
*/
- public const DI_PREFIX = 'di_';
+ protected array $aliases = array();
+
+ protected AutoWire $autoWiring;
+
+ /**
+ * @var array<string, array<string, array{string, array}>> $callers Method calls with injection definitions
+ */
+ protected array $callers = array();
+
+ /**
+ * @var array<string, object> $instances a key value storage with dependency objects
+ */
+ protected array $instances = array();
+
+ /**
+ * @var string[] $recursionPath Array used keep track of injections (to prevent recursive dependencies)
+ */
+ protected array $recursionPath = array();
/**
- * @var array<string, object> $dependencies A key value storage with dependency objects
+ * @var array<string, 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).
*/
- private array $dependencies = array();
+ protected array $wiring = array();
+
+ public function __construct(public ConfigRegistry $registry = new ConfigRegistry()) {
+ $this->autoWiring = new AutoWire($this);
+ }
/**
- * @var array<string, array> $lazyLoadedDeps Lazily loaded dependency objects
+ * @template T
+ * @param class-string<T> $class
+ * @return T
*/
- private array $lazyLoadedDeps = array();
+ public function byType(string $class, ?string $id = null): object {
+ $keys = array_keys($this->aliases, $class);
+ if (count($keys) === 1) {
+ return $this->get(reset($keys));
+ }
+
+ if ($id === null) {
+ throw new DependencyInjectionException(sprintf('Ambiguous dependency of type \'%s\' requested: found %d dependencies of that type', $class, count($keys)));
+ }
+
+ if (!in_array($id, $keys)) {
+ // we don't have it, let's try to make it
+ return $this->make($class);
+ }
+
+ return $this->get($id);
+ }
/**
* {@inheritDoc}
- * @param string[] $getFor Array used keep track of injections (to prevent recursive dependencies)
*/
- public function get($id, array $getFor = array()) {
- if (in_array($id, $getFor)) {
- throw new DependencyInjectionException('Recursive dependency definition detected: '.implode(' => ', $getFor));
+ public function get(string $id): object {
+ if (in_array($id, $this->recursionPath)) {
+ throw new DependencyInjectionException(sprintf('Recursive dependency definition detected: %s', implode(' => ', $this->recursionPath)));
}
- if (isset($this->dependencies[$id])) {
- return $this->dependencies[$id];
+ if (!$this->has($id)) {
+ throw new DependencyNotFoundException("Container has no named dependency called '{$id}'");
}
- if (isset($this->lazyLoadedDeps[$id])) {
- try {
- list('class' => $class, 'args' => $args) = $this->lazyLoadedDeps[$id];
- $rfc = new ReflectionClass($class);
- $value = $rfc->newInstanceWithoutConstructor();
- $getFor[] = $id;
- $this->inject($value, true, $getFor);
- if (method_exists($value, '__construct')) {
- $value->__construct(...$args);
- }
- if (method_exists($value, 'init')) {
- trigger_error('Relying on init() to initialise dependency objects after injecting is no longer required and deprecated', \E_USER_DEPRECATED);
- }
- $this->dependencies[$id] = $value;
-
- return $value;
- } catch (TypeError | ReflectionException $e) {
- throw new DependencyInjectionException("Cannot initialise dependency '{$id}' on Dependency Container: '{$e->getMessage()}'", (int)($e->getCode()), $e);
- }
- } else {
- throw new DependencyNotFoundException("Dependency '{$id}' does not exist on Dependency Container");
+
+ // if we have the dependency, just return it
+ if (isset($this->instances[$id])) {
+ return $this->instances[$id];
}
+
+ list($class, $params) = $this->wiring[$id];
+
+ $this->recursionPath[] = $id;
+ $this->instances[$id] = $this->instance($class, $params);
+ array_pop($this->recursionPath);
+
+ return $this->instances[$id];
}
/**
* {@inheritDoc}
*/
- public function has($id) {
- return isset($this->dependencies[$id]) || isset($this->lazyLoadedDeps[$id]);
+ public function has($id): bool {
+ return isset($this->aliases[$id]);
}
/**
- * Method used to inject dependencies into an object, here called "the user of the dependencies".
- * @param object $dependencyUser The object to be injected
- * @param bool $forceInjection Whether to throw an exception if a dependency cannot be found
- * @param array $injectFor Array used keep track of injections (to prevent recursive dependencies)
+ * @template T
+ * @param class-string<T>|string $abstract
+ * @return T
*/
- public function inject(object $dependencyUser, bool $forceInjection = true, array $injectFor = array()): void {
- foreach (array_keys(get_class_vars(get_class($dependencyUser))) as $propertyName) {
- $propertyName = (string)$propertyName;
- if (mb_strpos($propertyName, self::DI_PREFIX) === 0) {
- $depKey = str_replace(self::DI_PREFIX, '', $propertyName);
- if (!$this->has($depKey) && $forceInjection) {
- throw new DependencyNotFoundException("Dependency '{$depKey}' does not exist on Dependency Container");
- }
- $dependencyUser->{$propertyName} = $this->get($depKey, $injectFor);
+ public function make(string $abstract, array $extraParams = array()): object {
+ if ($this->has($abstract)) {
+ return $this->get($abstract);
+ }
+
+ if (in_array($abstract, $this->recursionPath)) {
+ throw new DependencyInjectionException(sprintf('Recursive dependency definition detected: %s', implode(' => ', $this->recursionPath)));
+ }
+
+ $this->recursionPath[] = $abstract;
+ if (isset($this->wiring[$abstract])) {
+ list($class, $params) = $this->wiring[$abstract];
+ $instance = $this->instance($class, array_merge($params, $extraParams));
+ } else {
+ if (!class_exists($abstract)) {
+ throw new DependencyInjectionException("No idea how to make '{$abstract}'. Class does not exist and no wire directive was set");
}
+
+ $instance = $this->instance($abstract, $extraParams);
}
+ array_pop($this->recursionPath);
+
+ return $instance;
}
/**
* Method used to set a dependency in this class.
- * If the given value is an object, it will get injected and saved under the key
+ * If the given value is an object, be saved under the key
* If the given value is a string a class name is assumed and the class / argument combination will be saved for later instantiation.
- * @param string $id The key to save the dependency under
- * @param mixed $value The dependency to save
- * @param mixed ...$constructorArgs Arguments for the class instantiation
*/
- public function set(string $id, $value, ...$constructorArgs): void {
- if (is_string($value) && class_exists($value)) {
- $this->lazyLoadedDeps[$id] = array('class' => $value, 'args' => $constructorArgs);
- } else {
- if (!is_object($value)) {
- throw new DependencyInjectionException("Cannot create dependency '{$id}' on Dependency Container");
+ public function set(string $id, object|string $value, array $params = array()): void {
+ // as the object has already been created, we must assume it has its dependencies
+ if (is_object($value)) {
+ $this->aliases[$id] = get_class($value);
+ $this->instances[$id] = $value;
+
+ return;
+ }
+
+ if (class_exists($value)) {
+ $this->aliases[$id] = $value;
+ $this->wire($value, $params, $id);
+ }
+ }
+
+ /**
+ * Set up a wiring from an abstract to an actual implementation.
+ * This can be used to choose strategy pattern possibilities based on config.
+ * The wired object will also get additional params autowired.
+ */
+ public function wire(string $class, array $params = array(), ?string $abstract = null): void {
+ if (!class_exists($class)) {
+ throw new DependencyInjectionException("Could not auto-wire abstract '{$class}': class does not exist");
+ }
+
+ $abstract ??= $class;
+
+ $this->wiring[$abstract] = array($class, $params);
+ }
+
+ protected function instance(string $class, array $params): object {
+ $reflection = new ReflectionClass($class);
+ $constructor = $reflection->getConstructor();
+ if ($constructor === null) {
+ if (!empty($params)) {
+ AutoWireException::failNoConstructor($reflection, $params);
}
- $this->inject($value);
- $this->dependencies[$id] = $value;
+ return new $class();
}
+
+ $params = $this->autoWiring->autoWire($constructor, $params);
+
+ return new $class(...$params);
}
}
<?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\autowire;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionUnionType;
use ReflectionFunctionAbstract;
use ReflectionIntersectionType;
use holonet\common\di\Container;
use holonet\common\di\DependencyInjectionException;
use holonet\common\di\autowire\provider\ParamAutoWireProvider;
use holonet\common\di\autowire\provider\ConfigAutoWireProvider;
use holonet\common\di\autowire\provider\ForwardAutoWireProvider;
use holonet\common\di\autowire\provider\ContainerAutoWireProvider;
/**
* Small helper class which uses reflection to try and auto-wire parameters for a function / method.
* Uses a given set of provider classes which can try to find a parameter based on it's name, user supplied value and type.
*/
class AutoWire {
/**
* @var ParamAutoWireProvider[]
*/
protected array $paramProviders;
public function __construct(protected Container $container) {
$this->paramProviders = array(
new ForwardAutoWireProvider(),
new ConfigAutoWireProvider(),
new ContainerAutoWireProvider(),
);
}
public function autoWire(ReflectionFunctionAbstract $method, array $givenParams = array()): array {
$parameters = $method->getParameters();
$mapped = array();
foreach ($parameters as $param) {
$autoWiredValue = $this->autoWireParameter($param, $givenParams[$param->getName()] ?? null);
// if we are here and the auto-wired value is null, it must be because the parameter
// has a default or null works as a value for the parameter.
if ($autoWiredValue !== null || !$param->isDefaultValueAvailable()) {
$mapped[$param->getName()] = $autoWiredValue;
}
}
return $mapped;
}
private function autoWireNamedType(ReflectionParameter $param, ReflectionNamedType $type, mixed $paramValue): mixed {
foreach ($this->paramProviders as $provider) {
$wiredValue = $provider->provide($this->container, $param, $type, $paramValue);
if ($wiredValue !== null) {
return $wiredValue;
}
}
if ($param->allowsNull() || $param->isOptional()) {
return null;
}
AutoWireException::failParam($param, "Cannot auto-wire to type '{$type->getName()}'");
}
private function autoWireParameter(ReflectionParameter $param, mixed $paramValue): mixed {
$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->autoWireUnionType($param, $paramType, $paramValue);
}
return $this->autoWireNamedType($param, $paramType, $paramValue);
}
private function autoWireUnionType(ReflectionParameter $param, ReflectionUnionType $type, mixed $paramValue): mixed {
$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 $wiredValue;
}
} catch (DependencyInjectionException $e) {
$errors[$type->getName()] = $e->getMessage();
}
}
}
$unionType = implode('|', array_keys($errors));
$errors = implode("\n", $errors);
AutoWireException::failParam($param, "Cannot auto-wire to union type '{$unionType}': \n{$errors}");
}
}
<?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\autowire;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionFunctionAbstract;
use holonet\common\di\DependencyInjectionException;
class AutoWireException extends DependencyInjectionException {
private function __construct(ReflectionFunctionAbstract|ReflectionClass $reflection, string $message) {
$identifier = $reflection->getName();
if ($reflection instanceof ReflectionMethod) {
$identifier = sprintf('%s::%s', $reflection->getDeclaringClass()->getName(), $reflection->getName());
}
parent::__construct("Failed to auto-wire '{$identifier}': {$message}");
}
public static function failNoConstructor(ReflectionClass $reflection, array $params): void {
throw new static($reflection, sprintf('Has no constructor, but %d parameters were given', count($params)));
}
public static function failParam(ReflectionParameter $param, string $message): never {
throw new static($param->getDeclaringFunction(), "Parameter #{$param->getPosition()}: {$param->getName()}: {$message}");
}
}
<?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\autowire\attribute;
use Attribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class ConfigItem {
public function __construct(public ?string $key = null, public bool $verified = 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\autowire\provider;
use ReflectionNamedType;
use ReflectionParameter;
use holonet\common\di\Container;
use holonet\common\di\autowire\AutoWireException;
use holonet\common\di\autowire\attribute\ConfigItem;
use function holonet\common\reflection_get_attribute;
/**
* Provider which will automatically inject a config dto object read from the registry.
* This is achieved using a special marker attribute on the parameter.
* The corresponding config key that will be used to collect the config data can be supplied using:
* - a given parameter (a string) which represents the config key
* - the property in the marker attribute.
*/
class ConfigAutoWireProvider implements ParamAutoWireProvider {
/**
* {@inheritDoc}
*/
public function provide(Container $container, ReflectionParameter $param, ReflectionNamedType $type, mixed $givenParam): mixed {
$expectedType = $type->getName();
$attribute = reflection_get_attribute($param, ConfigItem::class);
if ($attribute === null) {
return null;
}
$configKey = ($givenParam ?? $attribute->key);
if (!is_string($configKey)) {
AutowireException::failParam($param, 'Cannot auto-wire to a config dto object without supplying a config key');
}
if (class_exists($expectedType)) {
if ($attribute->verified) {
return $container->registry->verifiedDto($configKey, $expectedType);
}
return $container->registry->asDto($configKey, $expectedType);
}
return $container->registry->get($configKey);
}
}
<?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\autowire\provider;
use ReflectionNamedType;
use ReflectionParameter;
use holonet\common\di\Container;
use holonet\common\di\DependencyInjectionException;
class ContainerAutoWireProvider implements ParamAutoWireProvider {
/**
* {@inheritDoc}
*/
public function provide(Container $container, ReflectionParameter $param, ReflectionNamedType $type, mixed $givenParam): mixed {
if (class_exists($type->getName())) {
try {
return $container->byType($type->getName(), $param->getName());
} catch (DependencyInjectionException $e) {
if (!$type->allowsNull() && !$param->isOptional()) {
throw $e;
}
}
}
return null;
}
}
<?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\autowire\provider;
use ReflectionNamedType;
use ReflectionParameter;
use holonet\common\di\Container;
/**
* Special provider to check the types of a given parameter and just forward it if the types are the same.
* This is so a user can just provide an actual scalar value in a configuration array.
*/
class ForwardAutoWireProvider implements ParamAutoWireProvider {
/**
* {@inheritDoc}
*/
public function provide(Container $container, ReflectionParameter $param, ReflectionNamedType $type, mixed $givenParam): mixed {
$givenType = gettype($givenParam);
$expectedType = $type->getName();
$givenType = match ($givenType) {
'integer' => 'int',
'double' => 'float',
'boolean' => 'bool',
default => $givenType
};
if (in_array($givenType, explode('|', $expectedType)) || $givenParam instanceof $expectedType) {
return $givenParam;
}
return null;
}
}
<?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\autowire\provider;
use ReflectionNamedType;
use ReflectionParameter;
use holonet\common\di\Container;
/**
* Interface for parameter auto-wire behaviours.
* An implementation is supposed to provide a way to auto-wire a given type of parameter.
*/
interface ParamAutoWireProvider {
/**
* @param ReflectionParameter $param reflection object for the parameter that should be auto-wired
* @param ReflectionNamedType $type reflection type for the given parameter
* @param mixed $givenParam Parameter provided by the user
* The given parameter could come from:
* - an array that the user supplied to a get() or wire() call on the container
* - a config array in the container configuration on the registry
*
* This method should return a mapped parameter value. If it is not appropriate for this provider to supply
* such a value, it should return null
* 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;
}
diff --git a/f6be009859ad696ecaeda9ff88bd0600f6ae2045 b/e6264a6fc5a8db85091f27cfb89808cd7da17f2f
index f6be009..e6264a6 100644
--- a/f6be009859ad696ecaeda9ff88bd0600f6ae2045
+++ b/e6264a6fc5a8db85091f27cfb89808cd7da17f2f
@@ -18,7 +18,7 @@ use function holonet\common\stringify;
*/
class BadEnvironmentException extends RuntimeException {
public static function faultyConfig(string $key, string $errors): static {
- return new static("Faulty config with key {$key}: {$errors}");
+ return new static("Faulty config with key '{$key}': {$errors}");
}
public static function faultyConfigFromProof(string $key, Proof $proof): static {
diff --git a/953d80ba05844aec4f5f5d13a1f9cafd12ddfd3d b/4f404341265bc36f5fae637c5c92e9f2c3559f7c
index 953d80b..4f40434 100644
--- a/953d80ba05844aec4f5f5d13a1f9cafd12ddfd3d
+++ b/4f404341265bc36f5fae637c5c92e9f2c3559f7c
@@ -10,7 +10,10 @@
namespace holonet\common;
use ReflectionClass;
+use RuntimeException;
use ReflectionProperty;
+use ReflectionParameter;
+use InvalidArgumentException;
use holonet\common\verifier\Proof;
use holonet\common\verifier\Verifier;
use holonet\common\code\FileUseStatementParser;
@@ -89,7 +92,7 @@ if (!function_exists(__NAMESPACE__.'\\reflection_get_attribute')) {
* @param class-string<T> $class
* @return ?T
*/
- function reflection_get_attribute(ReflectionClass|ReflectionProperty $reflection, string $class): ?object {
+ function reflection_get_attribute(ReflectionClass|ReflectionProperty|ReflectionParameter $reflection, string $class): ?object {
$attrs = $reflection->getAttributes($class);
return reset($attrs) ? reset($attrs)->newInstance() : null;
@@ -125,18 +128,6 @@ if (!function_exists(__NAMESPACE__.'\\stringify')) {
}
}
-if (!function_exists(__NAMESPACE__.'\\trigger_error_context')) {
- /**
- * function using the php debug backtrace to trigger an error on the calling line.
- * @param string $message The message to throw in the error
- * @param int $level Error level integer, defaults to E_USER_ERROR
- */
- function trigger_error_context(string $message, int $level = \E_USER_ERROR): void {
- $caller = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
- trigger_error("{$message} in file {$caller['file']} on line {$caller['line']}", $level);
- }
-}
-
if (!function_exists(__NAMESPACE__.'\\indentText')) {
/**
* function used to indent a text with newlines in it
diff --git a/53e6ecd4f6caacca9a7e94d20b11a93b01606e35 b/0aaa5641701648defe20647d3c9b2dc40d1509d7
index 53e6ecd..0aaa564 100644
--- a/53e6ecd4f6caacca9a7e94d20b11a93b01606e35
+++ b/0aaa5641701648defe20647d3c9b2dc40d1509d7
@@ -12,21 +12,21 @@ namespace holonet\common\tests;
use Exception;
use PHPUnit\Framework\TestCase;
use holonet\common\config\ConfigReader;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
+use holonet\common\config\parsers\IniConfigParser;
+use holonet\common\config\parsers\PhpConfigParser;
+use holonet\common\config\parsers\JsonConfigParser;
-/**
- * Tests the functionality of the ConfigReader class.
- *
- * @covers \holonet\common\config\ConfigReader
- *
- * @internal
- *
- * @small
- */
+#[CoversClass(ConfigReader::class)]
+#[CoversClass(IniConfigParser::class)]
+#[CoversClass(JsonConfigParser::class)]
+#[CoversClass(PhpConfigParser::class)]
class ConfigReaderTest extends TestCase {
/**
* Return an entry for each test config file there is so we can test all the file formats.
*/
- public function configTestProvider() {
+ public static function configTestProvider(): array {
$ret = array();
foreach (glob(__DIR__.'/data/config.*') as $file) {
$ext = pathinfo($file, \PATHINFO_EXTENSION);
@@ -48,13 +48,8 @@ class ConfigReaderTest extends TestCase {
$this->assertSame("File path 'iSurelyDon'tExist.ini' does not exist", $msg);
}
- /**
- * @dataProvider configTestProvider
- * @covers \holonet\common\config\parsers\IniConfigParser
- * @covers \holonet\common\config\parsers\JsonConfigParser
- * @covers \holonet\common\config\parsers\PhpConfigParser
- */
- public function testParseFiles($file): void {
+ #[DataProvider('configTestProvider')]
+ public function testParseFiles(string $file): void {
$expectedData = array('toplevel' => 'value', 'sublevel' => array('config' => 'sub'));
$configreader = new ConfigReader();
diff --git a/7bed3cc4d042d4745f9961d325b6f4c9c169e0fb b/3ea67ae6b991f4e14968e807b579c09577823dec
index 7bed3cc..3ea67ae 100644
--- a/7bed3cc4d042d4745f9961d325b6f4c9c169e0fb
+++ b/3ea67ae6b991f4e14968e807b579c09577823dec
@@ -10,17 +10,18 @@
namespace holonet\common\tests;
use PHPUnit\Framework\TestCase;
+use holonet\common\collection\Registry;
use holonet\common\config\ConfigRegistry;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\CoversFunction;
use holonet\common\error\BadEnvironmentException;
use holonet\common\verifier\rules\string\MinLength;
use holonet\common\verifier\rules\string\ExactLength;
-/**
- * @covers \holonet\common\config\ConfigRegistry
- * @covers \holonet\common\collection\Registry
- * @covers \holonet\common\dot_key_get()
- * @covers \holonet\common\dot_key_set()
- */
+#[CoversClass(ConfigRegistry::class)]
+#[CoversClass(Registry::class)]
+#[CoversFunction('holonet\common\dot_key_get')]
+#[CoversFunction('holonet\common\dot_key_set')]
class ConfigRegistryTest extends TestCase {
protected function setUp(): void {
$_ENV['ENV_VALUE'] = 'cool env config value';
@@ -55,7 +56,7 @@ class ConfigRegistryTest extends TestCase {
public function testVerifiedDto(): void {
$this->expectException(BadEnvironmentException::class);
- $this->expectExceptionMessage('Faulty config with key test.testProp: testProp must be exactly 11 characters long');
+ $this->expectExceptionMessage('Faulty config with key \'test.testProp\': testProp must be exactly 11 characters long');
$dto = new class() {
public function __construct(
@@ -73,7 +74,7 @@ class ConfigRegistryTest extends TestCase {
public function testVerifiedDtoTypeError(): void {
$this->expectException(BadEnvironmentException::class);
- $this->expectExceptionMessage('Faulty config with key test: TypeError: Cannot assign array to property class@anonymous::$testProp of type string');
+ $this->expectExceptionMessage('Faulty config with key \'test\': TypeError: Cannot assign array to property class@anonymous::$testProp of type string');
$dto = new class() {
public function __construct(
diff --git a/e28d3153661a1139572c179a2e39815ade95df72 b/cbf6f01cd8edf9ded6f164c75e0de2f6bf2868f0
index e28d315..cbf6f01 100644
--- a/e28d3153661a1139572c179a2e39815ade95df72
+++ b/cbf6f01cd8edf9ded6f164c75e0de2f6bf2868f0
@@ -14,22 +14,22 @@ use holonet\common as co;
use PHPUnit\Framework\TestCase;
use holonet\common\verifier\Proof;
use function holonet\common\verify;
+use holonet\common\FilesystemUtils;
use holonet\common\verifier\Verifier;
use holonet\common\verifier\rules\Required;
+use PHPUnit\Framework\Attributes\CoversClass;
use function holonet\common\get_absolute_path;
+use holonet\common\code\FileUseStatementParser;
+use PHPUnit\Framework\Attributes\CoversFunction;
-/**
- * @coversNothing
- */
+#[CoversClass(FileUseStatementParser::class)]
+#[CoversClass(FilesystemUtils::class)]
+#[CoversFunction('holonet\common\verify')]
class FunctionsTest extends TestCase {
protected function tearDown(): void {
verify(new stdClass(), new Verifier());
}
- /**
- * @covers \holonet\common\FilesystemUtils::dirpath()
- * @covers \holonet\common\FilesystemUtils::filepath()
- */
public function testAbsolutePaths(): void {
$expected = implode(\DIRECTORY_SEPARATOR, array(__DIR__, 'subfolder', 'subsubfolder')).\DIRECTORY_SEPARATOR;
$this->assertSame($expected, co\FilesystemUtils::dirpath(__DIR__, 'subfolder', 'subsubfolder'));
@@ -38,10 +38,6 @@ class FunctionsTest extends TestCase {
$this->assertSame($expected, co\FilesystemUtils::filepath(__DIR__, 'subfolder', 'subsubfolder', 'test.txt'));
}
- /**
- * @covers \holonet\common\file_get_use_statements()
- * @covers \holonet\common\code\FileUseStatementParser
- */
public function testFileGetUseStatements(): void {
$this->assertSame(array(
'class' => array(
@@ -70,19 +66,10 @@ class FunctionsTest extends TestCase {
), co\file_get_use_statements(__DIR__.'/data/use_statements.txt'));
}
- /**
- * @covers \holonet\common\get_absolute_path()
- */
public function testGetAbsolutePath(): void {
$this->assertSame('this/a/test/is', get_absolute_path('this/is/../a/./test/./is'));
}
- /**
- * @covers \holonet\common\FilesystemUtils::dirpath()
- * @covers \holonet\common\FilesystemUtils::filepath()
- * @covers \holonet\common\FilesystemUtils::reldirpath()
- * @covers \holonet\common\FilesystemUtils::relfilepath()
- */
public function testRelativePaths(): void {
$expected = implode(\DIRECTORY_SEPARATOR, array(__DIR__, 'subfolder', 'subsubfolder')).\DIRECTORY_SEPARATOR;
$this->assertSame($expected, co\FilesystemUtils::reldirpath('subfolder', 'subsubfolder'));
@@ -91,26 +78,6 @@ class FunctionsTest extends TestCase {
$this->assertSame($expected, co\FilesystemUtils::relfilepath('subfolder', 'subsubfolder', 'test.txt'));
}
- /**
- * @covers \holonet\common\trigger_error_context()
- */
- public function testTriggerErrorContext(): void {
- $msg = '';
-
- try {
- $line = (__LINE__) + 1;
- co\trigger_error_context('oh nos');
- } catch (\PHPUnit\Exception $e) {
- $msg = $e->getMessage();
- }
-
- $expected = 'oh nos in file '.__FILE__." on line {$line}";
- $this->assertSame($expected, $msg);
- }
-
- /**
- * @covers \holonet\common\verify()
- */
public function testVerifierCanBeInjectedIntoVerify(): void {
$test = new class() {
#[Required]
diff --git a/a583344fb79c4c8018111f00a36bac4b011270d9 b/9f92571b617b7c98d18637656df09c9e29452e81
index a583344..9f92571 100644
--- a/a583344fb79c4c8018111f00a36bac4b011270d9
+++ b/9f92571b617b7c98d18637656df09c9e29452e81
@@ -11,12 +11,12 @@ namespace holonet\common\tests;
use PHPUnit\Framework\TestCase;
use holonet\common\collection\Registry;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\CoversFunction;
-/**
- * @covers \holonet\common\collection\Registry
- * @covers \holonet\common\dot_key_get()
- * @covers \holonet\common\dot_key_set()
- */
+#[CoversClass(Registry::class)]
+#[CoversFunction('holonet\common\dot_key_get')]
+#[CoversFunction('holonet\common\dot_key_set')]
class RegistryTest extends TestCase {
public function testArrayAccessCalls(): void {
$registry = new Registry();
<?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;
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(Container::class)]
#[CoversClass(ConfigAutoWireProvider::class)]
#[CoversClass(AutoWire::class)]
#[CoversClass(ConfigItem::class)]
class ConfigAutoWireProviderTest extends TestCase {
public function testConfigItemInjectionKeyInArguments(): void {
$container = new Container();
$container->registry->set('service.config', array('stringValue' => 'test'));
$result = $container->make(Dependency::class, array('config' => 'service.config'));
$this->assertSame('test', $result->config->stringValue);
}
public function testConfigItemInjectionKeyInAttribute(): void {
$container = new Container();
$container->registry->set('service.other', array('stringValue' => 'test'));
$result = $container->make(OtherDependency::class);
$this->assertSame('test', $result->config->stringValue);
}
public function testConfigItemIsVerified(): void {
$this->expectException(BadEnvironmentException::class);
$this->expectExceptionMessage('Faulty config with key \'service.config.stringValue\': stringValue must be at most 10 characters long');
$container = new Container();
$container->registry->set('service.config', array('stringValue' => 'test_longer_than_10'));
$container->make(OtherDependency::class, array('config' => 'service.config'));
}
public function testInjectArrayConfigValue(): void {
$container = new Container();
$value = array('test', 'cool');
$container->registry->set('config.just_an_array_value', $value);
$result = $container->make(ServiceWithArrayConfigValue::class);
$this->assertSame($value, $result->value);
}
public function testInjectStringConfigValue(): void {
$container = new Container();
$container->registry->set('config.just_a_string_value', 'configured method');
$result = $container->make(ServiceWithStringConfigValue::class);
$this->assertSame('configured method', $result->value);
}
public function testPropertyWithoutAttributeIsIgnored(): void {
$container = new Container();
$result = $container->make(ClassWithoutAttribute::class);
$this->assertNotNull($result);
}
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');
$container = new Container();
$container->make(Dependency::class);
}
}
class ClassWithoutAttribute {
public function __construct(Simple $config) {
}
}
class Simple {
}
class Dependency {
public function __construct(
#[ConfigItem(verified: false)]
public Config $config
) {
}
}
class OtherDependency {
public function __construct(
#[ConfigItem(key: 'service.other')]
public Config $config
) {
}
}
class ServiceWithStringConfigValue {
public function __construct(
#[ConfigItem(key: 'config.just_a_string_value')]
public string $value
) {
}
}
class ServiceWithArrayConfigValue {
public function __construct(
#[ConfigItem(key: 'config.just_an_array_value')]
public array $value
) {
}
}
class Config {
public function __construct(
#[MaxLength(10)]
public string $stringValue,
public array $array = array()) {
}
}
<?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;
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\DependencyInjectionException;
use holonet\common\di\autowire\provider\ContainerAutoWireProvider;
#[CoversClass(Container::class)]
#[CoversClass(ContainerAutoWireProvider::class)]
#[CoversClass(AutoWire::class)]
#[CoversClass(AutoWireException::class)]
#[CoversClass(DependencyInjectionException::class)]
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\'');
$container = new Container();
$container->make(ServiceMultipleVersionDependencies::class);
}
public function testInjectionIgnoresMissingOptionalParams(): void {
$container = new Container();
$result = $container->make(ServiceOptionalDep::class);
$this->assertNull($result->dependency);
$this->assertNotNull($result->optional);
}
public function testInjectionOfMultipleVersionsOfService(): void {
$container = new Container();
$serviceOne = new DependencyWithParameter('string_one');
$serviceTwo = new DependencyWithParameter('string_two');
$container->set('serviceOne', $serviceOne);
$container->set('serviceTwo', $serviceTwo);
$result = $container->make(ServiceMultipleVersionDependencies::class);
$this->assertSame($serviceOne, $result->serviceOne);
$this->assertSame($serviceTwo, $result->serviceTwo);
}
public function testInjectionUsingMake(): void {
$container = new Container();
$container->wire(DependencyWithParameter::class, array('mustBeSuppliedParameter' => 'test_string'));
$one = $container->make(ServiceWithDependencyInjectUsingMake::class);
$two = $container->make(ServiceWithDependencyInjectUsingMake::class);
// because it's injected using make, they should be two different instances
$this->assertTrue($one->dep !== $two->dep);
$this->assertSame('test_string', $one->dep->mustBeSuppliedParameter);
$this->assertSame('test_string', $two->dep->mustBeSuppliedParameter);
}
public function testServiceInjection(): void {
$container = new Container();
$dependency = new DependencyContainerAutoWire();
$container->set('dependency', $dependency);
$result = $container->make(Service::class);
$this->assertSame($dependency, $result->dependency);
}
}
class ServiceMultipleVersionDependencies {
public function __construct(public DependencyWithParameter $serviceOne, public DependencyWithParameter $serviceTwo) {
}
}
class ServiceOptionalDep {
public function __construct(public ?DependencyWithParameter $dependency = null, public DependencyWithParameter $optional = new DependencyWithParameter('test')) {
}
}
class Service {
public function __construct(public DependencyContainerAutoWire $dependency) {
}
}
class ServiceWithDependencyInjectUsingMake {
public function __construct(public DependencyWithParameter $dep) {
}
}
class DependencyContainerAutoWire {
public function __construct() {
}
}
class DependencyWithParameter {
public function __construct(public string $mustBeSuppliedParameter) {
}
}
<?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;
use Countable;
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(AutoWire::class)]
#[CoversClass(AutoWireException::class)]
#[CoversClass(DependencyInjectionException::class)]
class ContainerTest extends TestCase {
public function testGetNonExistingDependency(): void {
$this->expectException(DependencyNotFoundException::class);
$this->expectExceptionMessage("Container has no named dependency called 'kaudermelsh'");
$container = new Container();
$container->get('kaudermelsh');
}
public function testInjectionWithConstructor(): void {
$container = new Container();
$container->set('anonDep', DiAnonDep::class);
$container->set('anonClassTwo', DiAnonClassTwo::class);
$this->assertSame('test', $container->get('anonClassTwo')->test);
}
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');
$container = new Container();
$container->make(IntersectionTypes::class);
}
public function testLazyLoadReturnsOneInstance(): void {
$container = new Container();
$container->set('anonDep', DiAnonDep::class);
$one = $container->get('anonDep');
$two = $container->get('anonDep');
$this->assertSame($one, $two);
}
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');
$container = new Container();
$container->make(MyInterface::class);
}
public function testMakeReturningGivenInstancesIfAvailable(): void {
$container = new Container();
$override = new DiAnonDep();
$container->set(DiAnonDep::class, $override);
$this->assertSame($override, $container->make(DiAnonDep::class));
}
public function testMakeReturnsNewInstanceEveryCall(): void {
$container = new Container();
$one = $container->make(DiAnonDep::class);
$two = $container->make(DiAnonDep::class);
$this->assertNotSame($one, $two);
}
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\'');
$container = new Container();
$container->make(SomeService::class);
}
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');
$container = new Container();
$container->make(DiAnonDep::class, array('cool'));
}
public function testMultipleInstancesOfServiceBothAvailable(): void {
$one = new DiAnonDep();
$two = new DiAnonDep();
$this->assertNotSame($one, $two);
$container = new Container();
$container->set('config_one', $one);
$container->set('config_two', $two);
// both should be available by their ids
$this->assertSame($one, $container->get('config_one'));
$this->assertSame($two, $container->get('config_two'));
// both should be available by their type when supplying a name hint
$this->assertSame($one, $container->byType(DiAnonDep::class, 'config_one'));
$this->assertNotSame($two, $container->byType(DiAnonDep::class, 'config_one'));
$this->assertSame($one, $container->byType(DiAnonDep::class, 'config_one'));
$this->assertNotSame($two, $container->byType(DiAnonDep::class, 'config_one'));
// 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');
$container->byType(DiAnonDep::class);
}
public function testOptionalTypelessParametersAreIgnored(): void {
$container = new Container();
$result = $container->make(TypelessClassOptional::class);
$this->assertSame('test', $result->test);
}
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');
$container = new Container();
$container->make(RecursionA::class);
}
public function testRecursionDetectionServiceGet(): void {
$this->expectException(DependencyInjectionException::class);
$this->expectExceptionMessage('Recursive dependency definition detected: A => B => C');
$container = new Container();
$container->set('A', RecursionA::class);
$container->set('B', RecursionB::class);
$container->set('C', RecursionC::class);
$container->get('A');
}
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');
$container = new Container();
$container->make(TypelessClass::class);
}
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'
Message
);
$container = new Container();
$container->make(UnionTypesMultipleFailures::class);
}
public function testWireNonExistingClassThrowsError(): void {
$this->expectException(DependencyInjectionException::class);
$this->expectExceptionMessage('Could not auto-wire abstract \'\nonsense\class\TestClass\': class does not exist');
$container = new Container();
$container->wire('\\nonsense\\class\\TestClass');
}
public function testWireParametersGetAppliedWiring(): void {
$container = new Container();
$container->wire(SomeService::class, array('parameter' => 'test'));
$one = $container->make(SomeService::class);
$two = $container->make(SomeService::class);
$this->assertNotSame($one, $two);
$this->assertTrue($one->parameter === $two->parameter);
}
public function testWiringAnInterfaceToAnImplementation(): void {
$container = new Container();
$container->wire(TestClass::class, abstract: MyInterface::class);
$container->wire(TestClass::class, abstract: AbstractBaseClass::class);
$this->assertInstanceOf(TestClass::class, $container->make(MyInterface::class));
$this->assertInstanceOf(TestClass::class, $container->make(AbstractBaseClass::class));
}
}
class RecursionA {
public function __construct(RecursionB $recursionB) {
}
}
class RecursionB {
public function __construct(RecursionC $recursionC) {
}
}
class RecursionC {
public function __construct(RecursionA $recursionA) {
}
}
class UnionTypesMultipleFailures {
public function __construct(SomeService|SomeServiceTwo $service) {
}
}
class IntersectionTypes {
public function __construct(Countable&Stringable $intersection) {
}
}
class TypelessClass {
public function __construct(public $test) {
}
}
class TypelessClassOptional {
public function __construct(public $test = 'test') {
}
}
class DiAnonClassTwo {
public string $test;
public function __construct(public DiAnonDep $anonDep) {
$this->test = $this->anonDep->test();
}
}
class DiAnonDep {
public function test(): string {
return 'test';
}
}
class SomeService {
public function __construct(public string $parameter) {
}
}
class SomeServiceTwo {
public function __construct(public string $parameter) {
}
}
abstract class AbstractBaseClass {
}
class TestClass extends AbstractBaseClass implements MyInterface {
}
interface MyInterface {
}
<?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;
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\DependencyInjectionException;
use holonet\common\di\autowire\provider\ForwardAutoWireProvider;
#[CoversClass(Container::class)]
#[CoversClass(ForwardAutoWireProvider::class)]
#[CoversClass(AutoWire::class)]
#[CoversClass(AutoWireException::class)]
#[CoversClass(DependencyInjectionException::class)]
class ForwardAutoWireProviderTest extends TestCase {
public function testBasicScalarParameterForwarding(): void {
$container = new Container();
$params = array(
'string' => 'gojsdgoisjdgio',
'int' => 5,
'float' => 10.5,
'boolean' => true,
'array' => array('value1', 'value2')
);
$result = $container->make(DependencyForwardAutoWire::class, $params);
$this->assertSame($params, get_object_vars($result));
}
public function testObjectTypesForwarding(): void {
$container = new Container();
$apples = new Apples();
$result = $container->make(DependencyWithObjectParam::class, array('apples' => $apples));
$this->assertSame($apples, $result->apples);
}
public function testObjectTypesForwardingUnionTypes(): void {
$container = new Container();
$apples = new Apples();
$other = new DependencyWithUnionTypeHints();
$one = $container->make(DependencyWithUnionTypeHintsObjects::class, array('other' => $apples));
$two = $container->make(DependencyWithUnionTypeHintsObjects::class, array('other' => $other));
$this->assertSame($apples, $one->other);
$this->assertSame($other, $two->other);
}
public function testScalarUnionTypesForwarding(): void {
$container = new Container();
$one = $container->make(DependencyWithUnionTypeHints::class, array('testUnion' => 'string_value'));
$two = $container->make(DependencyWithUnionTypeHints::class, array('testUnion' => 5.4));
$this->assertSame('string_value', $one->testUnion);
$this->assertSame(5.4, $two->testUnion);
}
}
class DependencyForwardAutoWire {
public function __construct(public string $string, public int $int, public float $float, public bool $boolean, public array $array) {
}
}
class DependencyWithObjectParam {
public function __construct(public Apples $apples) {
}
}
class Apples {
}
class DependencyWithUnionTypeHints {
public function __construct(public string|float $testUnion = 5.0) {
}
}
class DependencyWithUnionTypeHintsObjects {
public function __construct(public Apples|DependencyWithUnionTypeHints $other) {
}
}
diff --git a/daafa15b0100ba09f9842716b0304e40123c4494 b/74bf67637083c186bc49cabdc05304c19bdd4a9d
index daafa15..74bf676 100644
--- a/daafa15b0100ba09f9842716b0304e40123c4494
+++ b/74bf67637083c186bc49cabdc05304c19bdd4a9d
@@ -12,25 +12,24 @@ namespace holonet\common\tests\error;
use PHPUnit\Framework\TestCase;
use holonet\common\verifier\Proof;
use function holonet\common\stringify;
+use PHPUnit\Framework\Attributes\CoversClass;
use holonet\common\error\BadEnvironmentException;
-/**
- * @covers \holonet\common\error\BadEnvironmentException
- */
+#[CoversClass(BadEnvironmentException::class)]
class BadEnvironmentExceptionTest extends TestCase {
public function testFaultyConfigFactoryMethod(): void {
$proof = new Proof();
$proof->add('guard_enabled', 'guard_enabled is required');
$ex = BadEnvironmentException::faultyConfigFromProof('app.auth', $proof);
- $this->assertSame('Faulty config with key app.auth.guard_enabled: guard_enabled is required', $ex->getMessage());
+ $this->assertSame('Faulty config with key \'app.auth.guard_enabled\': guard_enabled is required', $ex->getMessage());
$proof->add('guard_enabled', 'guard_enabled is invalid');
$ex = BadEnvironmentException::faultyConfigFromProof('app.auth', $proof);
- $this->assertSame(sprintf('Faulty config with key app.auth.guard_enabled: %s', stringify($proof->attr('guard_enabled'), true)), $ex->getMessage());
+ $this->assertSame(sprintf('Faulty config with key \'app.auth.guard_enabled\': %s', stringify($proof->attr('guard_enabled'), true)), $ex->getMessage());
$proof->add('handler', 'handler must be a subclass of Handler');
$ex = BadEnvironmentException::faultyConfigFromProof('app.auth', $proof);
- $this->assertSame(sprintf('Faulty config with key app.auth: %s', stringify($proof->all(), true)), $ex->getMessage());
+ $this->assertSame(sprintf('Faulty config with key \'app.auth\': %s', stringify($proof->all(), true)), $ex->getMessage());
}
}
diff --git a/494b15fbc2a78b7ad13f8b8265a89dda5768f2ec b/f00281b8c26289957e76b9af1a86a002958a67d3
index 494b15f..f00281b 100644
--- a/494b15fbc2a78b7ad13f8b8265a89dda5768f2ec
+++ b/f00281b8c26289957e76b9af1a86a002958a67d3
@@ -15,15 +15,14 @@ use holonet\common\verifier\Proof;
use function holonet\common\verify;
use function holonet\common\stringify;
use holonet\common\verifier\rules\Rule;
+use PHPUnit\Framework\Attributes\CoversClass;
use holonet\common\verifier\rules\CheckValueRuleInterface;
use holonet\common\verifier\rules\TransformValueRuleInterface;
-/**
- * @covers \holonet\common\verifier\rules\Rule
- * @covers \holonet\common\verifier\rules\CheckValueRuleInterface
- * @covers \holonet\common\verifier\rules\TransformValueRuleInterface
- */
-abstract class BaseVerifyTest extends TestCase {
+#[CoversClass(Rule::class)]
+#[CoversClass(CheckValueRuleInterface::class)]
+#[CoversClass(TransformValueRuleInterface::class)]
+class BaseVerifyTest extends TestCase {
public function assertProofContainsError(Proof $actual, string $attr, string $error): void {
$this->assertContains($error, $actual->flat());
$this->assertArrayHasKey($attr, $actual->all());
diff --git a/77af731f282bf9754bc120e887ba614897aedcfa b/4236f2cd6fda4a1f812b902935aaa7b4d5b48792
index 77af731..4236f2c 100644
--- a/77af731f282bf9754bc120e887ba614897aedcfa
+++ b/4236f2cd6fda4a1f812b902935aaa7b4d5b48792
@@ -11,10 +11,9 @@ namespace holonet\common\tests\verifier;
use PHPUnit\Framework\TestCase;
use holonet\common\verifier\Proof;
+use PHPUnit\Framework\Attributes\CoversClass;
-/**
- * @covers \holonet\common\verifier\Proof
- */
+#[CoversClass(Proof::class)]
class ProofTest extends TestCase {
public function testProofErrorBag(): void {
$proof = new Proof();
diff --git a/33e9b2e50f013fff52b5943d80512ae840b6a87e b/ea9b6e1d69b34346bc4fbed11a3a2e4ace7d9245
index 33e9b2e..ea9b6e1 100644
--- a/33e9b2e50f013fff52b5943d80512ae840b6a87e
+++ b/ea9b6e1d69b34346bc4fbed11a3a2e4ace7d9245
@@ -10,20 +10,22 @@
namespace holonet\common\tests\verifier;
use function holonet\common\verify;
+use holonet\common\verifier\Verifier;
+use holonet\common\verifier\rules\Rule;
+use PHPUnit\Framework\Attributes\CoversClass;
+use holonet\common\verifier\rules\filesystem\PathRule;
use holonet\common\verifier\rules\filesystem\Readable;
use holonet\common\verifier\rules\filesystem\Writable;
use holonet\common\verifier\rules\filesystem\Directory;
use holonet\common\verifier\rules\filesystem\ValidPath;
-/**
- * @covers \holonet\common\verifier\Verifier
- * @covers \holonet\common\verifier\rules\Rule
- * @covers \holonet\common\verifier\rules\filesystem\ValidPath
- * @covers \holonet\common\verifier\rules\filesystem\PathRule
- * @covers \holonet\common\verifier\rules\filesystem\Readable
- * @covers \holonet\common\verifier\rules\filesystem\Writable
- * @covers \holonet\common\verifier\rules\filesystem\Directory
- */
+#[CoversClass(Verifier::class)]
+#[CoversClass(Rule::class)]
+#[CoversClass(ValidPath::class)]
+#[CoversClass(PathRule::class)]
+#[CoversClass(Readable::class)]
+#[CoversClass(Writable::class)]
+#[CoversClass(Directory::class)]
class VerifyFilesystemRulesTest extends BaseVerifyTest {
public function testCheckPathIsDirectory(): void {
$test = new class('/path/surely/doesnt/exist') {
diff --git a/71231eaba38753c4f9e242c9c0917e9ae72510c4 b/ada4f330c646b72f2899c4e9d286b8f032b87d6f
index 71231ea..ada4f33 100644
--- a/71231eaba38753c4f9e242c9c0917e9ae72510c4
+++ b/ada4f330c646b72f2899c4e9d286b8f032b87d6f
@@ -10,13 +10,14 @@
namespace holonet\common\tests\verifier;
use function holonet\common\verify;
+use holonet\common\verifier\Verifier;
+use holonet\common\verifier\rules\Rule;
use holonet\common\verifier\rules\InArray;
+use PHPUnit\Framework\Attributes\CoversClass;
-/**
- * @covers \holonet\common\verifier\Verifier
- * @covers \holonet\common\verifier\rules\Rule
- * @covers \holonet\common\verifier\rules\InArray
- */
+#[CoversClass(Verifier::class)]
+#[CoversClass(Rule::class)]
+#[CoversClass(InArray::class)]
class VerifyInArrayTest extends BaseVerifyTest {
public function testCheckInArray(): void {
$test = new class('itsy bitsy') {
diff --git a/1a9c5ea9b7a9ab83db793232cf68ae8a02e7c5a3 b/c752a6114e923f168061e07cd99c7e83d9974e86
index 1a9c5ea..c752a61 100644
--- a/1a9c5ea9b7a9ab83db793232cf68ae8a02e7c5a3
+++ b/c752a6114e923f168061e07cd99c7e83d9974e86
@@ -10,19 +10,20 @@
namespace holonet\common\tests\verifier;
use function holonet\common\verify;
+use holonet\common\verifier\Verifier;
+use holonet\common\verifier\rules\Rule;
+use PHPUnit\Framework\Attributes\CoversClass;
use holonet\common\verifier\rules\numeric\Between;
use holonet\common\verifier\rules\numeric\Maximum;
use holonet\common\verifier\rules\numeric\Minimum;
use holonet\common\verifier\rules\numeric\Numeric;
-/**
- * @covers \holonet\common\verifier\Verifier
- * @covers \holonet\common\verifier\rules\Rule
- * @covers \holonet\common\verifier\rules\numeric\Between
- * @covers \holonet\common\verifier\rules\numeric\Maximum
- * @covers \holonet\common\verifier\rules\numeric\Minimum
- * @covers \holonet\common\verifier\rules\numeric\Numeric
- */
+#[CoversClass(Verifier::class)]
+#[CoversClass(Rule::class)]
+#[CoversClass(Between::class)]
+#[CoversClass(Maximum::class)]
+#[CoversClass(Minimum::class)]
+#[CoversClass(Numeric::class)]
class VerifyNumericRulesTest extends BaseVerifyTest {
public function testCheckBetween(): void {
$test = new class(22) {
diff --git a/80b5f71e13fd549713985f1a8d8566a5e9f6e0f2 b/584d677f73277b98041cc2d3210d6473b334148f
index 80b5f71..584d677 100644
--- a/80b5f71e13fd549713985f1a8d8566a5e9f6e0f2
+++ b/584d677f73277b98041cc2d3210d6473b334148f
@@ -10,12 +10,12 @@
namespace holonet\common\tests\verifier;
use function holonet\common\verify;
+use holonet\common\verifier\Verifier;
use holonet\common\verifier\rules\Required;
+use PHPUnit\Framework\Attributes\CoversClass;
-/**
- * @covers \holonet\common\verifier\Verifier
- * @covers \holonet\common\verifier\rules\Required
- */
+#[CoversClass(Verifier::class)]
+#[CoversClass(Required::class)]
class VerifyRequiredTest extends BaseVerifyTest {
public function testCheckForRequiredAfterUnset(): void {
$test = new class('test') {
diff --git a/fc743d37e7f225b85240df507706039762c9dcd6 b/3eab70ae7a1d94c088dd310f8a848135c6655100
index fc743d3..3eab70a 100644
--- a/fc743d37e7f225b85240df507706039762c9dcd6
+++ b/3eab70ae7a1d94c088dd310f8a848135c6655100
@@ -10,21 +10,22 @@
namespace holonet\common\tests\verifier;
use function holonet\common\verify;
+use holonet\common\verifier\Verifier;
+use holonet\common\verifier\rules\Rule;
+use PHPUnit\Framework\Attributes\CoversClass;
use holonet\common\verifier\rules\string\Pattern;
use holonet\common\verifier\rules\string\MaxLength;
use holonet\common\verifier\rules\string\MinLength;
use holonet\common\verifier\rules\string\ExactLength;
use holonet\common\verifier\rules\string\LengthBetween;
-/**
- * @covers \holonet\common\verifier\Verifier
- * @covers \holonet\common\verifier\rules\Rule
- * @covers \holonet\common\verifier\rules\string\MaxLength
- * @covers \holonet\common\verifier\rules\string\MinLength
- * @covers \holonet\common\verifier\rules\string\ExactLength
- * @covers \holonet\common\verifier\rules\string\LengthBetween
- * @covers \holonet\common\verifier\rules\string\Pattern
- */
+#[CoversClass(Verifier::class)]
+#[CoversClass(Rule::class)]
+#[CoversClass(MaxLength::class)]
+#[CoversClass(MinLength::class)]
+#[CoversClass(ExactLength::class)]
+#[CoversClass(LengthBetween::class)]
+#[CoversClass(Pattern::class)]
class VerifyStringRulesTest extends BaseVerifyTest {
public function testCheckExactLength(): void {
$test = new class('itsy bitsy') {