Skip to content

Commit

Permalink
Container chain
Browse files Browse the repository at this point in the history
  • Loading branch information
brendt committed May 6, 2024
1 parent 5ab5d44 commit 8b0f89f
Show file tree
Hide file tree
Showing 17 changed files with 341 additions and 310 deletions.
2 changes: 1 addition & 1 deletion src/Container/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Container
{
public function register(string $className, callable $definition): self;

public function singleton(string $className, callable $definition): self;
public function singleton(string $className, object|callable $definition): self;

public function config(object $config): self;

Expand Down
25 changes: 0 additions & 25 deletions src/Container/ContainerLog.php

This file was deleted.

84 changes: 0 additions & 84 deletions src/Container/Context.php

This file was deleted.

96 changes: 65 additions & 31 deletions src/Container/Dependency.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,72 @@

namespace Tempest\Container;

use Closure;
use ReflectionClass;
use ReflectionFunction;
use ReflectionIntersectionType;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionType;
use ReflectionUnionType;
use Reflector;

final readonly class Dependency
{
public function __construct(
public ReflectionParameter|ReflectionClass $reflector,
public Reflector|ReflectionType|Closure|string $dependency,
) {
}

public function getId(): string
public function getName(): string
{
return $this->typeToString($this->getType());
return $this->resolveName($this->dependency);
}

public function __toString(): string
public function getShortName(): string
{
$typeToString = $this->typeToString($this->getType());
$parts = explode('\\', $typeToString);
$typeToString = $parts[array_key_last($parts)];

return implode(
' ',
array_filter([
$typeToString,
'$' . $this->reflector->getName(),
]),
);
return $this->resolveShortName($this->dependency);
}

private function getType(): string|ReflectionType
public function equals(self $other): bool
{
return match($this->reflector::class) {
ReflectionParameter::class => $this->reflector->getType(),
ReflectionClass::class => $this->reflector->getName(),
};
return $this->getName() === $other->getName();
}

private function typeToString(string|ReflectionType|null $type): ?string
private function resolveName(ReflectionType|Reflector|string|Closure $dependency): string
{
if ($type === null) {
return null;
if (is_string($dependency)) {
return $dependency;
}

if (is_string($type)) {
return $type;
return match($dependency::class) {
ReflectionFunction::class => $dependency->getName() . ' in ' . $dependency->getFileName() . ':' . $dependency->getStartLine(),
ReflectionClass::class => $dependency->getName(),
ReflectionMethod::class => $dependency->getDeclaringClass()->getName() . '::' . $dependency->getName(),
ReflectionParameter::class => $this->resolveName($dependency->getType()),
ReflectionNamedType::class => $dependency->getName(),
ReflectionIntersectionType::class => $this->intersectionTypeToString($dependency),
ReflectionUnionType::class => $this->unionTypeToString($dependency),
default => 'unknown',
};
}

private function resolveShortName(ReflectionType|Reflector|string|Closure $dependency): string
{
if (is_string($dependency)) {
return $dependency;
}

return match($type::class) {
ReflectionIntersectionType::class => $this->intersectionTypeToString($type),
ReflectionNamedType::class => $type->getName(),
ReflectionUnionType::class => $this->unionTypeToString($type),
return match($dependency::class) {
ReflectionFunction::class => $dependency->getShortName() . ' in ' . $dependency->getFileName() . ':' . $dependency->getStartLine(),
ReflectionClass::class => $dependency->getShortName(),
ReflectionMethod::class => $this->reflectionMethodToShortString($dependency),
ReflectionParameter::class => $this->resolveShortName($dependency->getType()),
ReflectionNamedType::class => $this->reflectionNameTypeToShortString($dependency),
ReflectionIntersectionType::class => $this->intersectionTypeToString($dependency),
ReflectionUnionType::class => $this->unionTypeToString($dependency),
default => 'unknown',
};
}

Expand All @@ -68,7 +78,7 @@ private function intersectionTypeToString(ReflectionIntersectionType $type): str
return implode(
'&',
array_map(
fn (ReflectionType $subType) => $this->typeToString($subType),
fn (ReflectionType $subType) => $this->resolveName($subType),
$type->getTypes(),
),
);
Expand All @@ -79,9 +89,33 @@ private function unionTypeToString(ReflectionUnionType $type): string
return implode(
'|',
array_map(
fn (ReflectionType $subType) => $this->typeToString($subType),
fn (ReflectionType $subType) => $this->resolveName($subType),
$type->getTypes(),
),
);
}

private function reflectionMethodToShortString(ReflectionMethod $method): string
{
$string = $method->getDeclaringClass()->getShortName() . '::' . $method->getName() . '(';

$parameters = [];

foreach ($method->getParameters() as $parameter) {
$parameters[] = $this->resolveShortName($parameter) . ' $' . $parameter->getName();
}

$string .= implode(', ', $parameters);

$string .= ')';

return $string;
}

private function reflectionNameTypeToShortString(ReflectionNamedType $type): string
{
$parts = explode('\\', $type->getName());

return $parts[array_key_last($parts)] ?? $type->getName();
}
}
61 changes: 61 additions & 0 deletions src/Container/DependencyChain.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Tempest\Container;

use Closure;
use ReflectionType;
use Reflector;
use Tempest\Container\Exceptions\CircularDependencyException;

final class DependencyChain
{
/**
* @var \Tempest\Container\Dependency[]
*/
private array $dependencies = [];

public function __construct(private string $origin)
{
}

public function add(Reflector|ReflectionType|Closure|string $dependency): self
{
$dependency = new Dependency($dependency);

if (isset($this->dependencies[$dependency->getName()])) {
throw new CircularDependencyException($this, $dependency);
}

$this->dependencies[$dependency->getName()] = $dependency;

return $this;
}

public function first(): Dependency
{
return $this->dependencies[array_key_first($this->dependencies)];
}

public function last(): Dependency
{
return $this->dependencies[array_key_last($this->dependencies)];
}

/** @return \Tempest\Container\Dependency[] */
public function all(): array
{
return $this->dependencies;
}

public function getOrigin(): string
{
return $this->origin;
}

public function clone(): self
{
return clone $this;
}
}
24 changes: 12 additions & 12 deletions src/Container/Exceptions/CannotAutowireException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,43 @@
namespace Tempest\Container\Exceptions;

use Exception;
use Tempest\Container\ContainerLog;
use Tempest\Container\DependencyChain;

final class CannotAutowireException extends Exception
{
public function __construct(ContainerLog $containerLog)
public function __construct(DependencyChain $chain)
{
$stack = $containerLog->getStack();
$stack = $chain->all();

$firstContext = $stack[array_key_first($stack)];
$lastContext = $stack[array_key_last($stack)];
$firstDependency = $chain->first();
$lastDependency = $chain->last();

$message = PHP_EOL . PHP_EOL . "Cannot autowire {$firstContext->getName()} because {$lastContext->getName()} cannot be resolved" . PHP_EOL;
$message = PHP_EOL . PHP_EOL . "Cannot autowire {$firstDependency->getName()} because {$lastDependency->getName()} cannot be resolved" . PHP_EOL;

$i = 0;

foreach ($stack as $currentContext) {
foreach ($stack as $currentDependency) {
$pipe = match ($i) {
0 => '┌──',
count($stack) - 1 => '└──',
default => '├──',
};

$message .= PHP_EOL . "\t{$pipe} " . $currentContext;
$message .= PHP_EOL . "\t{$pipe} " . $currentDependency->getShortName();

$i++;
}

$currentDependency = $lastContext->currentDependency();
$currentDependencyName = (string) $currentDependency;
$firstPart = explode($currentDependencyName, (string) $lastContext)[0] ?? null;
$currentDependency = $lastDependency;
$currentDependencyName = $currentDependency->getShortName();
$firstPart = explode($currentDependencyName, $lastDependency->getShortName())[0] ?? null;
$fillerSpaces = str_repeat(' ', strlen($firstPart) + 3);
$fillerArrows = str_repeat('', strlen($currentDependencyName));
$message .= PHP_EOL . "\t {$fillerSpaces}{$fillerArrows}";

$message .= PHP_EOL . PHP_EOL;

$message .= "Originally called in {$containerLog->getOrigin()}";
$message .= "Originally called in {$chain->getOrigin()}";
$message .= PHP_EOL;

parent::__construct($message);
Expand Down
Loading

0 comments on commit 8b0f89f

Please sign in to comment.