Skip to content

Commit

Permalink
Changed: Refactoring and housekeeping
Browse files Browse the repository at this point in the history
  • Loading branch information
merloxx committed Jul 14, 2023
1 parent 17e480a commit 1e7f904
Show file tree
Hide file tree
Showing 7 changed files with 423 additions and 207 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Please see [CONTRIBUTING](https://zaphyr.org/contributions) for details.

## Code of Conduct

Please see [CODE OF CONDUCT](https://zaphyr.org/contributions#content-code-of-conduct) for details.
Please see [CODE OF CONDUCT](https://zaphyr.org/contributions#code-of-conduct) for details.

## License

Expand Down
21 changes: 21 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
parameters:
phpVersion: 80100
level: 8
paths:
- src
parallel:
maximumNumberOfProcesses: 1
checkGenericClassInNonGenericObjectType: false
ignoreErrors:
-
message: '%Template type T of method Zaphyr\\Container\\Container::(get\(\)) is not referenced in a parameter.%'
path: src/Container.php
-
message: '%Template type T of method Zaphyr\\Container\\Contracts\\ContainerInterface::(resolve\(\)) is not referenced in a parameter.%'
path: src/Contracts/ContainerInterface.php
-
message: '%Cannot call method getName\(\) on ReflectionClass\|null.%'
path: src/Utils/Reflector.php
-
message: '%Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\<T of object\\>\\|T of object, string given.%'
path: src/Utils/Reflector.php
153 changes: 71 additions & 82 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,40 @@
namespace Zaphyr\Container;

use Closure;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionParameter;
use Zaphyr\Container\Contracts\ContainerInterface;
use Zaphyr\Container\Exceptions\ContainerException;
use Zaphyr\Container\Exceptions\NotFoundException;
use Zaphyr\Container\Utils\Reflector;
use Zaphyr\Container\Utils\TagGenerator;

/**
* @author merloxx <merloxx@zaphyr.org>
*/
class Container implements PsrContainerInterface
class Container implements ContainerInterface
{

/**
* @var array<string, array<string, Closure|string|null|bool>>
*/
protected array $bindings = [];

/**
* @var array<string, mixed>
*/
protected array $instances = [];

/**
* @var array<string, string[]>
*/
protected array $tags = [];

/**
* @var array<string, Closure[]>
*/
protected array $extends = [];

/**
* {@inheritdoc}
*/
public function bind(string $alias, Closure|string|null $concrete = null, bool $shared = false): void
{
if ($concrete === null) {
Expand All @@ -34,6 +48,9 @@ public function bind(string $alias, Closure|string|null $concrete = null, bool $
$this->bindings[$alias] = compact('concrete', 'shared');
}

/**
* {@inheritdoc}
*/
public function resolve(string $alias): mixed
{
if (isset($this->instances[$alias])) {
Expand All @@ -57,7 +74,14 @@ public function resolve(string $alias): mixed
return $object;
}

public function get(string $id)
/**
* @template T
*
* @param class-string<T>|string $id
*
* {@inheritdoc}
*/
public function get(string $id): mixed
{
try {
return $this->resolve($id);
Expand All @@ -74,16 +98,27 @@ public function get(string $id)
}
}

/**
* {@inheritdoc}
*/
public function has(string $id): bool
{
return isset($this->bindings[$id]);
}

/**
* {@inheritdoc}
*/
public function isShared(string $alias): bool
{
return isset($this->bindings[$alias]['shared']) && $this->bindings[$alias]['shared'] === true;
}

/**
* @param string $alias
*
* @return mixed
*/
protected function getConcrete(string $alias): mixed
{
if (isset($this->bindings[$alias])) {
Expand All @@ -93,96 +128,44 @@ protected function getConcrete(string $alias): mixed
return $alias;
}

/**
* @param Closure|string $concrete
* @param string $alias
*
* @return bool
*/
protected function isBuildable(Closure|string $concrete, string $alias): bool
{
return $concrete === $alias || $concrete instanceof Closure;
}

/**
* @param Closure|string $concrete
*
* @throws ContainerException
* @return mixed
*/
protected function build(Closure|string $concrete): mixed
{
if ($concrete instanceof Closure) {
return $concrete($this);
}

try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $exception) {
throw new ContainerException('Could not resolve "' . $concrete . '"', 0, $exception);
}

if (!$reflector->isInstantiable()) {
throw new ContainerException('"' . $concrete . '" is not instantiable');
}

$constructor = $reflector->getConstructor();

if ($constructor === null) {
return new $concrete();
}

if (!$constructor->isPublic()) {
throw new ContainerException('Constructor of class "' . $concrete . '" is not public');
}

$dependencies = $constructor->getParameters();
$instances = $this->resolveDependencies($dependencies);

return $reflector->newInstanceArgs($instances);
}

protected function resolveDependencies(array $dependencies): array
{
$results = [];

foreach ($dependencies as $dependency) {
$result = DependencyResolver::getParameterClassName($dependency) === null
? $this->resolvePrimitive($dependency)
: $this->resolveClass($dependency);

if ($dependency->isVariadic()) {
$results = array_merge($results, $result);
} else {
$results[] = $result;
}
}

return $results;
}

protected function resolvePrimitive(ReflectionParameter $parameter): mixed
{
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}

if ($parameter->isVariadic()) {
return [];
}

throw new ContainerException(
'Could not resolve dependency "' . $parameter . '" in class ' .
$parameter->getDeclaringClass()->getName()
);
}

protected function resolveClass(ReflectionParameter $parameter): mixed
{
if ($parameter->isVariadic()) {
throw new ContainerException(
'Could not resolve variadic dependency "' . $parameter . '" in class ' .
$parameter->getDeclaringClass()->getName()
);
}

return $this->resolve(DependencyResolver::getParameterClassName($parameter));
return Reflector::build($this, $concrete);
}

public function call($callable, array $parameters = []): mixed
/**
* {@inheritdoc}
*/
public function call(array|string|Closure $callable, array $parameters = []): mixed
{
return DependencyResolver::call($this, $callable, $parameters);
return Reflector::call($this, $callable, $parameters);
}

public function tag($aliases, array $tags): void
/**
* {@inheritdoc}
*/
public function tag(array|string $aliases, array $tags): void
{
foreach ($tags as $tag) {
if (!isset($this->tags[$tag])) {
Expand All @@ -195,9 +178,12 @@ public function tag($aliases, array $tags): void
}
}

/**
* {@inheritdoc}
*/
public function tagged(string $tag): iterable
{
if (! isset($this->tags[$tag])) {
if (!isset($this->tags[$tag])) {
throw new ContainerException('Tag "' . $tag . '" is not defined');
}

Expand All @@ -208,6 +194,9 @@ public function tagged(string $tag): iterable
}, count($this->tags[$tag]));
}

/**
* {@inheritdoc}
*/
public function extend(string $alias, Closure $closure): void
{
if (isset($this->instances[$alias])) {
Expand Down
74 changes: 74 additions & 0 deletions src/Contracts/ContainerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Zaphyr\Container\Contracts;

use Closure;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Zaphyr\Container\Exceptions\ContainerException;

/**
* @author merloxx <merloxx@zaphyr.org>
*/
interface ContainerInterface extends PsrContainerInterface
{
/**
* @param string $alias
* @param Closure|string|null $concrete
* @param bool $shared
*
* @return void
*/
public function bind(string $alias, Closure|string|null $concrete = null, bool $shared = false): void;

/**
* @template T
*
* @param class-string<T>|string $alias
*
* @throws ContainerException
* @return mixed
*/
public function resolve(string $alias): mixed;

/**
* @param string $alias
*
* @return bool
*/
public function isShared(string $alias): bool;

/**
* @param array<mixed>|string|Closure $callable
* @param array<mixed> $parameters
*
* @throws ContainerException
* @return mixed
*/
public function call(array|string|Closure $callable, array $parameters = []): mixed;

/**
* @param string[]|string $aliases
* @param string[] $tags
*
* @return void
*/
public function tag(array|string $aliases, array $tags): void;

/**
* @param string $tag
*
* @throws ContainerException
* @return iterable<mixed>
*/
public function tagged(string $tag): iterable;

/**
* @param string $alias
* @param Closure $closure
*
* @return void
*/
public function extend(string $alias, Closure $closure): void;
}
Loading

0 comments on commit 1e7f904

Please sign in to comment.