Dependency Injection

PointArt includes a Reflection-based dependency injection container. Use #[Wired] on properties and #[Service] on classes to wire your application together automatically.

Namespaces PointStart\Attributes — Wired, Service
PointStart\Core — Container

Property Injection with #[Wired]

Mark a property with #[Wired] to have the container inject it before any method is called:

#[Router(name: 'user', path: '/user')]
class UserController {
    #[Wired]
    private UserRepository $userRepository;

    #[Route('/list', HttpMethod::GET)]
    public function index(): string {
        // $userRepository is already available here
        $users = $this->userRepository->findAll();
        return Renderer::render('user.list', ['users' => $users]);
    }
}

#[Wired] Parameters

ParameterTypeRequiredDescription
required bool No When true (default), the dependency is eagerly resolved. When false, it is skipped if unavailable
// Required dependency (default) — throws if not resolvable
#[Wired]
private UserRepository $userRepository;

// Optional dependency — skipped if unavailable
#[Wired(required: false)]
private ?LogService $logger = null;

Services with #[Service]

Mark a class with #[Service] to register it as a singleton in the container — one instance shared across the entire request:

#[Service('myService')]
class MyService {
    public function doSomething(): string {
        return 'done';
    }
}

#[Service] Parameters

ParameterTypeRequiredDescription
namestringNoLogical name for the service. Default: ''

How the Container Works

The container resolves dependencies lazily — instances are created only when first requested:

  1. A controller is needed to handle a route
  2. The container inspects the controller's #[Wired] properties via Reflection
  3. Each dependency is resolved recursively (its dependencies are resolved first)
  4. Private properties are set via Reflection — no public setters needed
  5. Instances of #[Service] classes are cached as singletons
Repository Auto-Detection The container detects Repository subclasses automatically. Since repository subclasses are abstract, the container calls Repository::make() instead of new to generate a concrete implementation at runtime.

What Gets Scanned

The ClassLoader scans three directories on the first request:

Results are cached to cache/registry.ser. Classes are never loaded eagerly — spl_autoload_register ensures a class file is only required when that class is first instantiated at dispatch time.

Next: ORM →