# Dependency injection

By default, this template application uses a devanych/di-container, which is a simple and lightweight container using autowiring that implements PSR-11. Creation of the container and injection of dependencies is done in the file config/container.php, and the Psr\Container\ContainerInterface is used in other contexts.

You can easily change the container to your preferred PSR-11 implementation. To do this, install the required library and change the contents of the file config/container.php.

# Example usage with Devanych\Di

Let's set the required dependencies to create the application in the simplest way.

$container = new Devanych\Di\Container([
    'debug' => true,
    HttpSoft\Basis\Application::class => function (Psr\Container\ContainerInterface $container) {
        return new HttpSoft\Basis\Application(
            $container->get(HttpSoft\Router\RouteCollector::class),
            $container->get(HttpSoft\Emitter\EmitterInterface::class),
            $container->get(HttpSoft\Runner\MiddlewarePipelineInterface::class),
            $container->get(HttpSoft\Runner\MiddlewareResolverInterface::class),
            new HttpSoft\Basis\Handler\NotFoundJsonHandler($container->get('debug'))
        );
    },
    HttpSoft\Router\RouteCollector::class => HttpSoft\Router\RouteCollector::class,
    HttpSoft\Emitter\EmitterInterface::class => fn() => new HttpSoft\Emitter\SapiEmitter(),
    HttpSoft\Runner\MiddlewarePipelineInterface::class => fn() => new HttpSoft\Runner\MiddlewarePipeline(),
    HttpSoft\Runner\MiddlewareResolverInterface::class => fn($c) => new HttpSoft\Runner\MiddlewareResolver($c);
]);

Note that autowiring will be used to create an instance of HttpSoft\Router\RouteCollector, since the value is just a class. The container will iterate over all constructor dependencies and try to resolve them. If autowiring is not needed, then for best performance, just wrap the object creation with an anonymous function.

HttpSoft\Router\RouteCollector::class => fn() => new HttpSoft\Router\RouteCollector()

For convenience, you can create and use factories that implement the Devanych\Di\FactoryInterface.

use Devanych\Di\FactoryInterface;
use HttpSoft\Basis\Application;
use HttpSoft\Basis\Handler\NotFoundJsonHandler;
use HttpSoft\Emitter\EmitterInterface;
use HttpSoft\Router\RouteCollector;
use HttpSoft\Runner\MiddlewarePipelineInterface;
use HttpSoft\Runner\MiddlewareResolverInterface;
use Psr\Container\ContainerInterface;

final class ApplicationFactory implements FactoryInterface
{
    public function create(ContainerInterface $container): Application
    {
        return new Application(
            $container->get(RouteCollector::class),
            $container->get(EmitterInterface::class),
            $container->get(MiddlewarePipelineInterface::class),
            $container->get(MiddlewareResolverInterface::class),
            new NotFoundJsonHandler($container->get('config')['debug'])
        );
    }
}

The factory, as a dependency value, when created, will always return not its own object, but an object created by the create() method.

// With autowiring
HttpSoft\Basis\Application::class => ApplicationFactory::class,
// Without autowiring
HttpSoft\Basis\Application::class => fn() => new ApplicationFactory(),

It is better to put complex object creation logic in factories. The final code for setting dependencies has become much smaller.

$container = new Devanych\Di\Container([
    'debug' => true,
    HttpSoft\Basis\Application::class => fn() => new ApplicationFactory(),
    HttpSoft\Router\RouteCollector::class => fn() => new HttpSoft\Router\RouteCollector(),
    HttpSoft\Emitter\EmitterInterface::class => fn() => new HttpSoft\Emitter\SapiEmitter(),
    HttpSoft\Runner\MiddlewarePipelineInterface::class => fn() => new HttpSoft\Runner\MiddlewarePipeline(),
    HttpSoft\Runner\MiddlewareResolverInterface::class => fn($c) => new HttpSoft\Runner\MiddlewareResolver($c);
]);

By default, several factories were created in the app template:

# Using a container when processing routes

/**
 * @var Psr\Container\ContainerInterface $container
 */

// Creating an application object.
$app = $container->get(HttpSoft\Basis\Application::class);

// Add routes.
$app->get('home', '/', HomeHandler::class);
$app->get('list', '/page/{id}', PageAction::class)->tokens(['id' =>  '\d+']);

// Run application.
$app->run(HttpSoft\ServerRequest\ServerRequestCreator::create());

The example of adding routes uses fictitious class names (HomeAction and PageAction) as handlers/actions. This approach allows you to organize the processing of each route in a separate class. These classes implement the Psr\Http\Server\RequestHandlerInterface, read more about handlers, see in HttpSoft\Runner\MiddlewareResolver.

use HttpSoft\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class HomeAction implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return new JsonResponse(['name' => 'HomeAction']);
    }
}

Handler/action objects will be created through the container, so they can accept dependencies in the constructor.

use HttpSoft\Basis\Exception\NotFoundHttpException;
use HttpSoft\Basis\Response\PrepareJsonDataTrait;
use HttpSoft\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class PageAction implements RequestHandlerInterface
{
    use PrepareJsonDataTrait;

    private PageRepository $pages;

    public function __construct(PageRepository $pages)
    {
        $this->pages = $pages;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        /** @var JsonSerializable|null $page */
        if (!$page = $this->pages->findById((int) $request->getAttribute('id'))) {
            throw new NotFoundHttpException();
        }

        return new JsonResponse($this->prepareJsonData($page));
    }
}

Read more about routing here.