Skip to content

Commit

Permalink
Optimize compiler and configurator.
Browse files Browse the repository at this point in the history
  • Loading branch information
serge-kvashnin committed May 25, 2024
1 parent 2a33e42 commit eb90bb2
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 61 deletions.
103 changes: 68 additions & 35 deletions src/Compiler/ContainerCompiler.php → src/Compiler/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Coalesce;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\Closure as Closure_;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\ClassMethod;
Expand All @@ -37,13 +42,14 @@
use PhpParser\Parser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;

final class ContainerCompiler
final class Compiler
{
private readonly Definitions $definitions;
private readonly Parser $parser;
Expand Down Expand Up @@ -73,35 +79,14 @@ public function compile(string $class = 'Container'): string

foreach ($this->map as $id => $hash) {
$definition = $this->definitions->get($id);
$body[] = new ClassMethod(
name: "_{$hash}",
subNodes: [
'flags' => ReflectionMethod::IS_PRIVATE,
'params' => [],
'stmts' => [
new Return_(
expr: $this->definition($definition, $id),
),
],
],
);
$body[] = $this->method($id, $hash, $this->definition($definition, $id));
}

while ($this->postponed) {
$id = array_shift($this->postponed);
$body[] = new ClassMethod(
name: "_{$this->map[$id]}",
subNodes: [
'flags' => ReflectionMethod::IS_PRIVATE,
'params' => [],
'stmts' => [
new Return_(
expr: $this->definition(new Obj($id), $id),
),
],
],
);
$body[] = $this->method($id, $this->map[$id], $this->definition(new Obj($id), $id));
}
// $this->definition(new Obj($id), $id)

foreach ($this->map as $id => $hash) {
$map[] = new ArrayItem(
Expand Down Expand Up @@ -186,16 +171,23 @@ private function env(Env $definition): Expr

private function ref(Ref $definition): Expr
{
return new MethodCall(
var: new Variable(name: 'this'),
name: new Identifier(name: 'get'),
args: [
new Node\Arg(
value: new String_(
value: $definition->id,
return new Coalesce(
left: new ArrayDimFetch(
var: new PropertyFetch(
var: new Variable(name: 'container'),
name: new Identifier(name: 'resolved'),
),
dim: new String_(value: $definition->id),
),
right: new StaticCall(
class: new Name(name: 'self'),
name: "_{$this->map[$definition->id]}",
args: [
new Arg(
value: new Variable(name: 'container'),
),
)
],
],
)
);
}

Expand Down Expand Up @@ -271,9 +263,20 @@ class: new FullyQualified(name: $definition->instantiator),
return new FuncCall(
name: new Closure_(
subNodes: [
'static' => true,
'params' => [
new Param(
var: new Variable(name: 'container'),
),
],
'stmts' => $stmts,
],
)
),
args: [
new Arg(
value: new Variable(name: 'container'),
),
],
);
}

Expand Down Expand Up @@ -501,4 +504,34 @@ private function autowire(ReflectionParameter $rp): Expr

return $this->definition(new Ref($id));
}

private function method(string $id, string $hash, Expr $definition): ClassMethod
{
return new ClassMethod(
name: "_{$hash}",
subNodes: [
'flags' => 12,
'params' => [
new Param(
var: new Variable(name: 'container'),
type: new FullyQualified(name: ContainerInterface::class),
),
],
'stmts' => [
new Return_(
expr: new Assign(
var: new ArrayDimFetch(
var: new PropertyFetch(
var: new Variable(name: 'container'),
name: new Identifier(name: 'resolved'),
),
dim: new String_(value: $id),
),
expr: $definition,
),
),
],
],
);
}
}
5 changes: 2 additions & 3 deletions src/Compiler/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ public function get(string $id)
}

$hash = self::MAP[$id] ?? throw new \Norvica\Container\Exception\NotFoundException("Definition '{$id}' not found.");
$method = "_{$hash}";

return $this->resolved[$id] = $this->{$method}();
return self::{"_{$hash}"}($this);
}

public function has(string $id): bool
{
return isset(self::MAP[$id]);
}
};
}
16 changes: 13 additions & 3 deletions src/Configurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Norvica\Container;

use Closure;
use Norvica\Container\Compiler\ContainerCompiler;
use Norvica\Container\Compiler\Compiler;
use Norvica\Container\Definition\Definitions;
use Norvica\Container\Definition\Obj;
use Norvica\Container\Definition\Ref;
Expand All @@ -28,6 +28,7 @@ final class Configurator
private string|null $filename = null;
private string|null $dir = null;
private int $state;
private bool $autowiring = true;

public function __construct(
Definitions|null $definitions = null,
Expand Down Expand Up @@ -88,6 +89,13 @@ public function ref(string $id, string $ref): self
return $this;
}

public function autowiring(bool $autowiring = true): self
{
$this->autowiring = $autowiring;

return $this;
}

public function snapshot(string $dir, string $class = 'Container'): self
{
if ($this->state > self::INITIALIZING) {
Expand Down Expand Up @@ -153,7 +161,7 @@ public function container(): ContainerInterface
}

if ($this->state === self::COMPILING) {
$compiler = new ContainerCompiler($this->definitions);
$compiler = new Compiler($this->definitions);
$this->write($this->dir, $this->filename, $compiler->compile($this->class));
$this->state = self::LOADING;
}
Expand All @@ -162,7 +170,9 @@ public function container(): ContainerInterface
require_once $this->filename;
$this->state = self::INITIALIZED;

return $this->container = new Container(new Definitions(), new ($this->class)());
return $this->container = $this->autowiring
? new Container(new Definitions(), new ($this->class)(), $this->autowiring)
: new ($this->class)();
}

$this->state = self::INITIALIZED;
Expand Down
59 changes: 39 additions & 20 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ final class Container implements ContainerInterface
public function __construct(
private readonly Definitions $definitions,
private readonly ContainerInterface|null $compiled = null,
private bool $autowiring = true,
) {
}

Expand Down Expand Up @@ -76,7 +77,7 @@ public function get(string $id): mixed
$this->resolvingStarted($id);

// if ID is a class name, try to construct it, even if it's not registered explicitly
if (!$this->definitions->has($id)) {
if (!$this->definitions->has($id) && $this->autowiring) {
if (!class_exists($id)) {
throw new NotFoundException("Definition '{$id}' not found.");
}
Expand Down Expand Up @@ -132,25 +133,38 @@ private function resolve(mixed $definition): mixed
if ($definition instanceof Obj) {
// pure class name suggests we just use a constructor
if (is_string($definition->instantiator) && class_exists($definition->instantiator)) {
$rc = new ReflectionClass($definition->instantiator);
if ($rc->hasMethod('__construct')) {
$parameters = $this->parameters($rc->getMethod('__construct'), $definition->arguments);
if ($this->autowiring) {
$rc = new ReflectionClass($definition->instantiator);
if ($rc->hasMethod('__construct')) {
$parameters = $this->parameters(
$definition->arguments,
$rc->getMethod('__construct'),
);
} else {
$parameters = [];
}
} else {
$parameters = [];
$parameters = $this->parameters($definition->arguments);
}

$instance = new ($definition->instantiator)(...$parameters);
} else {
// call factory method
$closure = $this->closure($definition->instantiator);
$parameters = $this->parameters(new ReflectionFunction($closure), $definition->arguments);
$parameters = $this->parameters(
$definition->arguments,
$this->autowiring ? new ReflectionFunction($closure) : null,
);
$instance = $closure(...$parameters);
}

// perform defined calls on instance
foreach ($definition->calls as $call) {
$closure = $this->closure([$instance, $call->method]);
$parameters = $this->parameters(new ReflectionFunction($closure), $call->arguments);
$parameters = $this->parameters(
$call->arguments,
$this->autowiring ? new ReflectionFunction($closure) : null,
);
$closure(...$parameters);
}

Expand All @@ -160,7 +174,10 @@ private function resolve(mixed $definition): mixed
if ($definition instanceof Run) {
// execute closure
$closure = $this->closure($definition->instantiator);
$parameters = $this->parameters(new ReflectionFunction($closure), $definition->arguments);
$parameters = $this->parameters(
$definition->arguments,
$this->autowiring ? new ReflectionFunction($closure) : null,
);

return $closure(...$parameters);
}
Expand All @@ -178,24 +195,26 @@ private function closure(Closure|callable|array|string $callable): Closure
return $callable(...);
}

private function parameters(ReflectionMethod|ReflectionFunction $reflection, array $arguments): array
private function parameters(array $arguments, ReflectionMethod|ReflectionFunction|null $reflection = null): array
{
$resolved = array_map($this->resolve(...), $arguments);

foreach ($reflection->getParameters() as $i => $rp) {
if ($rp->isVariadic()) {
break;
}
if ($reflection !== null) {
foreach ($reflection->getParameters() as $i => $rp) {
if ($rp->isVariadic()) {
break;
}

if (isset($resolved[$i]) || isset($resolved[$rp->getName()])) {
continue;
}
if (isset($resolved[$i]) || isset($resolved[$rp->getName()])) {
continue;
}

if ($rp->isDefaultValueAvailable()) {
continue;
}
if ($rp->isDefaultValueAvailable()) {
continue;
}

$resolved[$rp->getName()] = $this->guess($rp);
$resolved[$rp->getName()] = $this->guess($rp);
}
}

return $resolved;
Expand Down
8 changes: 8 additions & 0 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Norvica\Container\Definition\Obj;
use Norvica\Container\Definition\Ref;
use Norvica\Container\Definition\Env;
use Norvica\Container\Definition\Run;
use Norvica\Container\Definition\Val;

if (!function_exists('Norvica\Container\env')) {
Expand All @@ -30,6 +31,13 @@ function obj(callable|array|string $instantiator, mixed ...$arguments): Obj
}
}

if (!function_exists('Norvica\Container\run')) {
function run(callable|array $instantiator, mixed ...$arguments): Run
{
return new Run($instantiator, ...$arguments);
}
}

if (!function_exists('Norvica\Container\val')) {
function val(Env|array|string|int|float|bool|null $value): Val
{
Expand Down
23 changes: 23 additions & 0 deletions tests/Fixtures/Combined/DateRange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Tests\Norvica\Container\Fixtures\Combined;

use DateTime;

final class DateRange
{
public readonly DateTime $start;
public readonly DateTime $end;

/**
* @param DateTime[] $dates
*/
public function __construct(
array $dates,
) {
$this->start = $dates[0];
$this->end = $dates[1];
}
}
Loading

0 comments on commit eb90bb2

Please sign in to comment.