Prejsť na obsah

AI blog · DotApp PHP Framework 2.0

Dependency injection in DotApp PHP Framework

The container is the booted DotApp instance you receive as initialize($dotApp). Register with $dotApp->bind($key, $fn) (new instance each resolve) or $dotApp->singleton($key, $fn) (once). Fetch with $dotApp->resolve($key) — that call throws if the key is unbound. Controller type-hints are filled only when the route has no trailing !. Hot Shop pages keep the bang and construct facades inside the method. This article is a complete Shop catalog service: bind in initialize(), inject without !, skip DI with ! and with NoDI.

Common mistakes

Wrong Right
Injector::bind() / Injector::singleton() Broken (typo dotAapp()). Use $dotApp->bind / singleton
Type-hint a service on Shop:Home@index! Trailing ! skips DI. Drop the bang, or call Renderer::new() inside
Assume resolve('missing') returns null It throws. Bind first, or wrap in try/catch
Register application routes in index.php so “DI is global” Bind in the module’s initialize($dotApp)
Hand-write a second container One kernel. Facades already wrap the parts you need

When to inject

A Shop service you own (catalog lookup, pricing) that several controllers share. Do not inject Renderer on every hot AJAX handler — use Renderer::new() and !. Callable grammar: Callable strings. Controllers: Controllers and Response.

Container API

Call Returns
$dotApp->bind($key, $resolver) void — new instance every resolve
$dotApp->singleton($key, $resolver) void — created once
$dotApp->resolve($key) object — throws if unbound
$dotApp->module($name) Module DI wrapper — throws if the module is unregistered
new NoDI($callable) Wrapper that skips reflection for a closure on the router

The framework already registers DotApp::class as a singleton. Custom facades subclass \Dotsystems\App\Parts\Facade, set $component and $allowedMethods (['*'] is allowed). A method not in $allowedMethods throws \BadMethodCallException.

Complete Shop catalog bind

File: app/modules/Shop/Libraries/Catalog.php (a class you own — not the Docs catalog). Then initialize() and two controller styles.


<?php
namespace Dotsystems\App\Modules\Shop\Libraries;

use Dotsystems\App\Parts\DB;

class Catalog
{
    public function byId(int $id): ?array
    {
        $rows = DB::module('RAW')->q(function ($qb) use ($id) {
            $qb->raw('SELECT id, title FROM shop_items WHERE id = :id LIMIT 1', ['id' => $id]);
        })->all();
        return $rows[0] ?? null;
    }
}
    

public function initialize($dotApp)
{
    Config::module('Shop', 'prefix') ?? Config::module('Shop', 'prefix', '/shop');

    $dotApp->singleton('shop.catalog', function () {
        return new \Dotsystems\App\Modules\Shop\Libraries\Catalog();
    });

    $p = Config::module('Shop', 'prefix');
    Router::get($p . '/item/{id:i}', 'Shop:Home@item');
    Router::get($p . '/', 'Shop:Home@index!', Router::STATIC_ROUTE);
}
    

public static function item($request, \Dotsystems\App\Modules\Shop\Libraries\Catalog $catalog)
{
    $id = (int) ($request->matchData()['id'] ?? 0);
    $row = $catalog->byId($id);
    if ($row === null) {
        return new \Dotsystems\App\Parts\Response(404, 'Not found');
    }
    return \Dotsystems\App\Parts\Renderer::new()
        ->module('Shop')
        ->setView('item')
        ->setViewVar('item', $row)
        ->renderView();
}

public static function index($request)
{
    $catalog = \Dotsystems\App\DotApp::dotApp()->resolve('shop.catalog');
    // ...
}
    

Shop:Home@item has no !, so the Catalog type-hint is injected. Shop:Home@index! skips DI — resolve by key if you still need the service, or construct it. Closures on the router that must skip reflection: wrap with new \Dotsystems\App\Parts\NoDI(function ($request) { ... }).

Facades wrap DotApp properties

A custom facade subclasses \Dotsystems\App\Parts\Facade. $component is a property name on the kernel (the same style as built-in parts), not a bind() key. $allowedMethods is the list of methods you expose; ['*'] allows every method that exists on that object. A call outside the list throws \BadMethodCallException. Shop code that only needs a catalog class should stay on bind / resolve / type-hints — do not invent a new property on DotApp.

FAQ

Should every route skip DI?

Hot AJAX and form posts usually yes (! + facades). Shared Shop services on a few GET pages can keep type-hints.

Why is Injector documented in old snippets?

Do not call it. Injector::bind / singleton fail. The kernel methods are the API.

What does $dotApp->module('Shop') return?

A module DI wrapper. It throws if Shop is not registered. It is not Config::module.

Does middleware get type-hints?

Module middleware with #Shop:AuthGate@check! skips DI. Keep check($request). Middleware.

Must I try/catch resolve()?

If the key might be missing, yes — it throws. Prefer binding in initialize() so a miss is a programmer error.

DotApp::dotApp() vs initialize($dotApp)?

Same instance after boot. DotApp::dotApp() throws if the kernel is not booted.

See also