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, ServicePointStart\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
| Parameter | Type | Required | Description |
|---|---|---|---|
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
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Logical name for the service. Default: '' |
How the Container Works
The container resolves dependencies lazily — instances are created only when first requested:
- A controller is needed to handle a route
- The container inspects the controller's
#[Wired]properties via Reflection - Each dependency is resolved recursively (its dependencies are resolved first)
- Private properties are set via Reflection — no public setters needed
- 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:
app/components/— controllers and servicesapp/models/— entity classesapp/repositories/— repository classes
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 →