Skip to content

Commit

Permalink
implement class and annotation scanner as a container config
Browse files Browse the repository at this point in the history
  • Loading branch information
frederikbosch committed May 22, 2024
1 parent 9540a2a commit 4d90da2
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 365 deletions.
12 changes: 6 additions & 6 deletions docs/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,13 @@ Suppose you create a `#[Route]` attribute and define it inside a `Controller` cl

```php
#[\Attribute]
use Aura\Di\Attribute\DefineInterface;
use Aura\Di\Resolver\Resolver;
use Aura\Di\AttributeConfigInterface;
use Aura\Di\Injection\Lazy;
use Aura\Di\Injection\LazyLazy;
use Aura\Di\Injection\LazyNew;
use Aura\Di\Resolver\Resolver;

class Route implements DefineInterface {
class Route implements AttributeConfigInterface {
public function __construct(private string $method, private string $uri) {
}

Expand All @@ -212,13 +212,13 @@ class Route implements DefineInterface {
new RealRoute(
$this->method,
$this->uri,
handler: new LazyLazy(
new InvokableHandler(new LazyLazy(
$resolver,
new Lazy([
LazyNew::fromClassName($reflector->getDeclaringClass()),
$reflector->getName()
])
)
))
)
);
}
Expand All @@ -235,7 +235,7 @@ class Controller {

class RouterFactory {
public function __construct(
#[Value('router')]
#[Value('routes')]
private array $routes
) {
}
Expand Down
72 changes: 54 additions & 18 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ The methodology for creating a compiled container is similar to creating a confi

```php
use Aura\Di\ContainerBuilder;
use Aura\Di\Resolver\AnnotatedResolverFactory;

$config_classes = [
new Aura\Router\_Config\Common(),
Expand Down Expand Up @@ -159,6 +158,58 @@ $di->params[VendorClass::class] = [
];
```

## Scan for classes and annotations

The `ContainerConfigClassScanner` scans the passed directories for classes and annotations. You will need that if you
want to [modify the container using attributes](attributes.md#modify-the-container-using-attributes). The classes inside
the passed namespaces will be compiled into blueprints, making sure all the required meta-data is there to create an
instance of the class.

```php
$builder = new ContainerBuilder();
$config_classes = [
new \MyApp\Config1,
new \MyApp\Config2,
new ContainerConfigClassScanner(
[$rootDir . '/app/src'], // these directories should be scanned for classes and annotations
['MyApp\\'], // classes inside these namespaces should be compiled
)
];

$di = $builder->newCompiledInstance($config_classes);
```

When using the `ContainerConfigClassScanner`, make sure to serialize and cache the container output. If you do
not do that, directories will be scanned every instance of the container.

If your attribute cannot implement the `AttributeConfigInterface`, e.g. the attribute is defined in an external package,
you can pass a map with the class name of the attribute and an implementation of `AttributeConfigInterface`.

```php
use Aura\Di\AttributeConfigInterface;

new ContainerConfigClassScanner(
[$rootDir . '/app/src'], // these directories should be scanned for classes and annotations
['MyApp\\'], // classes inside these namespaces should be compiled,
[
Symfony\Component\Routing\Attribute\Route::class => new SymfonyRouteAttributeConfig()
]
)

class SymfonyRouteAttributeConfig implements AttributeConfigInterface
{
public function define(Container $di, \ReflectionAttribute $attribute, \Reflector $annotatedTo): void
{
if ($annotatedTo instanceof ReflectionMethod) {
$route = $attribute->newInstance();
$di->values['routes'][] = new Symfony\Component\Routing\Route(
// ...
);
}
}
}
```

## Compiled objects inside the container

There might be other objects that you want to compile before serializing the container. A good example might be a
Expand All @@ -173,6 +224,7 @@ before the container is compiled.
An implementation might look as follows:

```php
use Aura\Di\Attribute\Value;
use Aura\Di\ContainerCompileInterface;

class RouterContainerConfig implements ContainerCompileInterface
Expand All @@ -194,7 +246,7 @@ class RouterContainerConfig implements ContainerCompileInterface

class MyRouterFactory {
public function __construct(
#[\Aura\Di\Attribute\Value('routes')]
#[Value('routes')]
private array $routes
) {
}
Expand All @@ -209,20 +261,4 @@ class MyRouterFactory {
return $router;
}
}
```

## ContainerBuilder and annotations

If we want to [modify the container using attributes](attributes.md#modify-the-container-using-attributes), we have to
pass the `AnnotatedResolverFactory` to the _ContainerBuilder_. The `AnnotatedResolverFactory` scans the passed directories
for classes and annotations. The classes inside the passed namespaces will be compiled.

```php
new ContainerBuilder(
new AnnotatedResolverFactory(
new ResolverFactory(), // default resolver factory
['Aura\Di\Fake'], // classes inside this namespace should be compiled
[__DIR__ . '/Fake'], // these directories should be scanned for classes and annotations
)
);
```
17 changes: 7 additions & 10 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,30 +65,27 @@ However, this is a relatively naive way to create objects with the _Container_.

A full-featured container can use [attributes](attributes.md) for injection and container modification. Moreover, for
maximum performance, we would have to compile the container, serialize it and save it to a cache layer like the filesystem.
Subsequent processes unserialize the

Subsequent processes unserialize the

```php
use Aura\Di\ContainerBuilder;
use Aura\Di\Resolver\AnnotatedResolverFactory;
use Aura\Di\ContainerConfigClassScanner;
use Aura\Di\Resolver\ResolverFactory;

$serializedContainerFile = '/var/compiled.ser';
$config_classes = [
new \MyApp\Config1,
new \MyApp\Config2,
new ContainerConfigClassScanner(
[$rootDir . '/app/src'], // these directories should be scanned for classes and annotations
['MyApp\\'], // classes inside these namespaces should be compiled
)
];

if (file_exists($serializedContainerFile)) {
$di = \unserialize(file_get_contents($serializedContainerFile));
} else {
$builder = new ContainerBuilder(
new AnnotatedResolverFactory(
new ResolverFactory(), // default resolver factory
['MyApp\\'], // classes inside this namespace should be compiled
[$rootDir . '/app/src'], // these directories should be scanned for classes and annotations
)
);
$builder = new ContainerBuilder();
$di = $builder->newCompiledInstance($config_classes);

$serialized = \serialize($di);
Expand Down
18 changes: 0 additions & 18 deletions src/Attribute/DefineInterface.php

This file was deleted.

16 changes: 16 additions & 0 deletions src/AttributeConfigInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
*
* This file is part of Aura for PHP.
*
* @license http://opensource.org/licenses/MIT MIT
*
*/

namespace Aura\Di;

interface AttributeConfigInterface
{
public function define(Container $di, \ReflectionAttribute $attribute, \Reflector $annotatedTo): void;
}
30 changes: 8 additions & 22 deletions src/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
*/
namespace Aura\Di;

use Aura\Di\Resolver\AutoResolver;
use Aura\Di\Resolver\Reflector;
use Aura\Di\Resolver\Resolver;
use Aura\Di\Resolver\ResolverFactory;
use Aura\Di\Resolver\ResolverFactoryInterface;

/**
*
Expand All @@ -29,25 +29,7 @@ class ContainerBuilder
* @const true
*
*/
const AUTO_RESOLVE = ResolverFactoryInterface::AUTO_RESOLVE;

/**
*
* A factory to create the resolver.
*
* @var ResolverFactoryInterface
*
*/
private ResolverFactoryInterface $resolverFactory;

public function __construct(?ResolverFactoryInterface $resolverFactory = null)
{
if ($resolverFactory === null) {
$resolverFactory = new ResolverFactory();
}

$this->resolverFactory = $resolverFactory;
}
const AUTO_RESOLVE = true;

/**
*
Expand All @@ -74,7 +56,11 @@ public function newInstance(bool $autoResolve = false): Container
*/
protected function newResolver(bool $autoResolve = false): Resolver
{
return $this->resolverFactory->newResolver($autoResolve);
if ($autoResolve) {
return new AutoResolver(new Reflector());
}

return new Resolver(new Reflector());
}

/**
Expand Down
Loading

0 comments on commit 4d90da2

Please sign in to comment.