A modern PHP Dependency Injection Container that brings IDE autocompletion, type safety, and zero-configuration to dependency injection. Unlike traditional containers that use string-based service keys, Middle-DI generates strongly-typed methods during development, then caches optimized code for zero-overhead production performance.
The issue with traditional containers:
$database = $container->get('database'); // What type? Runtime errors possible
$userService = $container->get('user.service'); // No IDE support, typos discovered lateThe Middle-DI solution:
$database = $container->getDatabase(); // Returns PDO, full IDE support
$userService = $container->getUserService(); // Returns UserService, compile-time safeSimilar to Pimple's simplicity but with modern type safety and zero runtime overhead.
Middle-DI uses compile-time code generation during development to transform your simple container definition into an optimized singleton container.
Simple Conventions:
- Services:
get*()methods return singletons, accept optionalstring $nameparameter only - Factories:
new*()methods create fresh instances, accept any parameters
Your Definition:
class Container
{
// Services: singletons
public function getDatabase(): PDO
{
return new PDO($this->config('db.dsn'));
}
public function getUserService(): UserService
{
return new UserService($this->getDatabase());
}
// Factories: fresh instances
public function newUser(string $username, array $roles = []): User
{
return new User($username, $roles);
}
}Generated for Production (cached, opcache-optimized):
class Container__Compiled extends Container
{
private array $__services = [];
private function __service(string $method, ?string $instanceName = null)
{
$suffix = is_null($instanceName) ? '' : '.' . $instanceName;
return $this->__services[$method . $suffix] ?? ($this->__services[$method . $suffix] = parent::{$method}($instanceName));
}
public function getDatabase(?string $instanceName = null): PDO
{
return $this->__service('getDatabase', $instanceName);
}
public function getUserService(?string $instanceName = null): UserService
{
return $this->__service('getUserService', $instanceName);
}
// newUser() remains unchanged - creates new instances
}<?php
use jschreuder\MiddleDi\DiCompiler;
class Container
{
public function getDatabase(): PDO
{
return new PDO('mysql:host=localhost;dbname=app');
}
public function getUserRepository(): UserRepositoryInterface
{
return new UserRepository($this->getDatabase());
}
public function newUser(string $username): User
{
return new User($username);
}
}
// Compile and use
$container = (new DiCompiler(Container::class))->compile()->newInstance();
// Services: same instances (singletons)
$db1 = $container->getDatabase();
$db2 = $container->getDatabase();
var_dump($db1 === $db2); // true
// Factories: different instances every time
$user1 = $container->newUser('alice');
$user2 = $container->newUser('bob');
var_dump($user1 === $user2); // falseSupport multiple configurations of the same service type:
class Container
{
public function getDatabase(?string $name = null): PDO
{
$dsn = match($name) {
'readonly' => 'mysql:host=slave;dbname=app',
'analytics' => 'mysql:host=analytics;dbname=app',
default => 'mysql:host=master;dbname=app'
};
return new PDO($dsn);
}
}
// Usage
$primary = $container->getDatabase(); // Default primary DB
$readonly = $container->getDatabase('readonly'); // Readonly replica
$analytics = $container->getDatabase('analytics'); // Analytics DBCache compiled containers for maximum performance:
use jschreuder\MiddleDi\DiCachedCompiler;
$compiler = new DiCachedCompiler(
new DiCompiler(Container::class),
new SplFileObject('var/cache/container.php', 'c+')
);
$container = $compiler->compile()->newInstance($config);In production, this runs as pure opcached PHP with zero container overhead. The generated code has no dependency on Middle-DI itself - it is just pure PHP.
Use the included ConfigTrait for clean configuration handling:
use jschreuder\MiddleDi\ConfigTrait;
class Container
{
use ConfigTrait;
public function getDatabase(): PDO
{
return new PDO(
$this->config('db.dsn'),
$this->config('db.username'),
$this->config('db.password')
);
}
}
$config = [
'db.dsn' => 'mysql:host=localhost;dbname=app',
'db.username' => 'user',
'db.password' => 'pass'
];
$container = $compiler->compile()->newInstance($config);No YAML, XML, or array configuration. Just write PHP methods with clear return types. The get vs new prefix tells Middle-DI everything it needs to know.
Complete autocompletion, type hints, and "Go to Definition" support. Refactoring tools work perfectly. No more guessing what $container->get('service_name') returns.
Development-time compilation generates cached PHP files. Production runs pure opcached code that compiles to native PHP speed with full opcache optimization.
All dependencies are strongly typed. Catch errors during development, not in production.
Services remain completely decoupled from the container. No annotations, no string dependencies, no special interfaces—just regular PHP classes.
Add advanced functionality using the decorator pattern:
$compiler = new CircularDependencyCompiler(
new DiCachedCompiler(
new DiCompiler(Container::class),
new SplFileObject('var/cache/container.php', 'c+')
)
);This is how the DiCachedCompiler was implemented, which is currently the only included decorator.
- PHP 8.3 or higher
Perfect for projects wanting Pimple's simplicity with modern IDE support and optimal production performance.