PHP code example of suhock / php-dependency-injection
1. Go to this page and download the library: Download suhock/php-dependency-injection library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
suhock / php-dependency-injection example snippets
$container = new Suhock\DependencyInjection\Container();
$container->addSingletonClass(MyApplication::class)
// Add the rest of your dependencies...
->get(MyApplication::class)
->run();
use Suhock\DependencyInjection\Container;
$container = new Container();
$container
// Autowire the constructor
->addSingletonClass(MyApplication::class)
// Manually construct an instance with factory
->addSingletonFactory(Logger::class, fn () => new FileLogger('myapp.log'))
// Alias an interface to an implementing type
->addTransientImplementation(HttpClient::class, CurlHttpClient::class)
// Add optional values with a mutator after autowiring the constructor
->addTransientClass(
CurlHttpClient::class,
function (CurlHttpClient $client, Logger $logger): void {
$client->addLogger($logger);
}
);
class MyApplication
{
// The constructor arguments will be provided by the container
public function __construct(
private readonly HttpClient $client,
private readonly Logger $logger
) {
}
}
$container->addSingletonInstance(Container::class, $container);
class MyRouter
{
public function __construct(
private readonly Container $container
) {
}
public function routeRequest(string $method, string $path): void
{
$controllerClassName = $this->getControllerClassName($path);
$controller = $this->container->get($controllerClassName);
$controller->handleRequest($method);
}
}
callable<TClass> Mutator(TClass $instance, [object|null ...]): void;
class Container
{
function addSingletonClass<TClass>(
string<TClass> $className,
Mutator<TClass>|null $mutator = null
): static;
function addTransientClass<TClass>(
string<TClass> $className,
Mutator<TClass>|null $mutator = null
): static;
}
use Suhock\DependencyInjection\Autowire;
class CurlHttpClient
{
#[Autowire]
public function setLogger(Logger $logger): void {
$this->logger = $logger;
}
// ...
}
class Container
{
function addSingletonImplementation<TClass, TImpl of TClass>(
string<TClass> $className,
string<TImpl> $implementationClassName
): static;
function addTransientImplementation<TClass, TImpl of TClass>(
string<TClass> $className,
string<IImpl> $implementationClassName
): static;
}
$container->addSingletonClass(HttpClient::class, CurlHttpClient::class);
/*
* The container will throw an UnresolvedDependencyException because it does
* not know how to provide an instance of CurlHttpClient.
*/
$container->get(HttpClient::class);
callable<TClass> FactoryMethod([object|null ...]): TClass;
class Container
{
function addSingletonFactory<TClass>(
string<TClass> $className,
FactoryMethod<TClass> $factory
): static;
function addTransientFactory<TClass>(
string<TClass> $className,
FactoryMethod<TClass> $factory
): static;
}
$container->addSingletonFactory(
Mailer::class,
fn (AppConfig $config) => new Mailer($config->mailerTransport)
);
$container->addTransientFactory(
Logger::class,
fn (FileWriter $writer) => new class($writer) implements Logger {
public function __construct(private readonly FileWriter $writer)
{
}
public function log(string $message): void
{
$this->writer->writeLine($message);
}
}
);
class Container
{
function addSingletonInstance<TClass>(
string<TClass> $className,
TClass $instance
): static;
}
$request = new Request($_SERVER, $_GET, $_POST, $_COOKIE);
$container->addSingletonInstance(Request::class, $request);
$container->addSingletonNamespace('Http');
/*
* The container will provide an instance of CurlHttpClient by autowiring the
* constructor because the class is in the Http namespace.
*/
$curlClient = $container->get(Http\CurlHttpClient::class);
$container->addSingletonImplementation(
Http\HttpClient::class,
Http\CurlHttpClient::class
);
/*
* The container will know to autowire CurlHttpClient for HttpClient because we
* specified the interface-implementation mapping.
*/
$httpClient = $container->get(Http\HttpClient::class);
callable<TClass> ImplementationFactory<TImpl of TClass>(
string<TImpl> $className,
[object|null ...]
): TImpl;
class Container
{
function addSingletonInterface<TInterface>(
string<TInterface> $className,
ImplementationFactory<TClass>|null $factory = null
): static;
function addTransientInterface<TInterface>(
string<TInterface> $className,
ImplementationFactory<TClass>|null $factory = null
): static;
}
$container->addSingletonInterface(
EntityNameProvider::class,
/**
* @template T of EntityNameProvider
* @var class-string<T> $className
* @return T
*/
fn (string $className, EntityManager $em) =>
$em->getRepository($className::getEntityName())
);
/*
* The container will query the EntityManager for a UserRepository.
*/
$userRepository = $container->get(UserRepository::class);
class UserRepository extends EntityRepository implements EntityNameProvider
{
public static function getEntityName(): string
{
return User::class;
}
}
$container->addSingletonAttribute(
EntityName::class,
fn (string $className, EntityName $attribute, EntityManager $em) =>
$em->getRepository($attribute->getName())
);
/*
* The container will query the EntityManager for a UserRepository.
*/
$userRepository = $container->get(UserRepository::class);
#[EntityName(User::class)]
class UserRepository extends EntityRepository
{
}
#[Attribute(Attribute::TARGET_CLASS)]
class EntityName
{
public function __construct(
private readonly string $name
) {
}
public function getName(): string
{
return $this->name;
}
}
class Container
{
public function add<TClass>(
string<TClass> $className,
LifetimeStrategy<TClass> $lifetimeStrategy,
InstanceProvider $instanceProvider
): static;
public function addSingleton<TClass>(
string<TClass> $className,
InstanceProvider $instanceProvider
): static;
public function addTransient<TClass>(
string<TClass> $className,
InstanceProvider $instanceProvider
): static;
}
callable LifetimeStrategyFactory<TClass>(
string<TClass> $className
): LifetimeStrategy<TClass>;
class Container
{
public function addContainer(
ContainerInterface $container,
LifetimeStrategyFactory $lifetimeStrategyFactory
): static;
public function addSingletonContainer(
ContainerInterface $container
): static;
public function addTransientContainer(
ContainerInterface $container
): static;
}
use Suhock\DependencyInjection\Context\ContextContainerFactory;
use Suhock\DependencyInjection\Context\Context;
/*
* Strings or enums can be used as identifiers for contexts. To help ease
* analysis and future refactorings, enums or string-typed constants are
* recommended.
*/
enum MyContexts {
case Default;
case Admin;
}
$container = ContextContainerFactory::createForDefaultContainer();
/*
* Build the Default context's container.
*/
$container->context(MyContexts::Default)
->addSingletonClass(MyApplication::class)
->addTransientImplementation(HttpClient::class, CurlHttpClient::class)
->addSingletonFactory(
Settings::class,
fn () => JsonSettings::fromFile('default.json')
);
/*
* Build the Admin context's container.
*/
$container->context(MyContexts::Admin)
->addSingletonFactory(
Settings::class,
fn () => JsonSettings::fromFile('admin.json')
);
$container
/*
* Make Default the default, fallback context.
* Stack: Default
*/
->push(MyContexts::Default)
/*
* Fetch the application and run it.
*/
->get(MyApplication::class)
->run();
/*
* Stack: Default, Admin
*
* The container will search the Admin context then the Default context for
* each dependency in the following class.
*/
#[Context(MyContexts::Admin)]
class AdminEditDefaultSettingsController {
/*
* Stack: Default, Admin
*
* Since no context is explicitly specified, the stack is inherited as-is
* from the class.
*/
public function __construct(
/*
* Stack: Default, Admin
*
* The container will resolve $settings using the Settings factory in
* the Admin context, since Admin is at the top of the context stack.
*/
private readonly Settings $settings,
/*
* Stack: Default, Admin, Default
*
* The container will resolve $defaultSettings using the Settings
* factory in the Default context, since the attribute below will
* place Default at the top of the context stack for this parameter.
*/
#[Context(MyContexts::Default)]
private readonly Settings $defaultSettings,
/*
* Stack: Default, Admin
*
* The container will first attempt to resolve $httpClient using the
* Admin context. However, since HttpClient does not exist in the
* the Admin context, the container will resolve it using the factory
* in the Default context.
*/
private readonly HttpClient $httpClient
) {
}
}
callable<TResult of mixed> InjectableFunction([... mixed]): TResult;
class Injector
{
public function call<TResult>(
InjectableFunction<TResult> $function,
array<int|string, mixed> $params = []
): TResult;
public function instantiate<TClass>(
string<TClass> $className,
array<int|string, mixed> $params = []
): TClass;
}
use Suhock\DependencyInjection\Container;
use Suhock\DependencyInjection\ContainerInjector;
// Create the container and build it
$container = new Container();
// ... build the container ...
// Create an injector backed by the container
$injector = new ContainerInjector($container);
// Fetch the application router from the container
$router = $container->get(Router::class);
// Get the appropriate controller from the request path
$controller = $router->getControllerFromRequest($_SERVER);
// Call the controller's handleGet() method, injecting the indicated parameter
// values in addition to any additional dependencies in the parameter list.
$page = $injector
->call(
$controller->handleGet(...),
map_query_to_assoc_param_array($_GET)
)
// Then, call the render() function on the return value.
$page->render();
class ProjectListController
{
public function handleGet(
// Parameter below will be injected from the container.
ProjectRepository $projectRepository,
// Parameter below will be populated from the value provided in the
// $injector->call() parameter array. The default value will be used if
// the key 'filter' is not present in the array.
string $filter = ''
): PageInterface {
$projects = $projectRepository->query($filter);
return new ProjectListPage($projects);
}
}
interface PageInterface {
public function render(): void;
}
class MyApplication
{
public function __construct(
private readonly HttpClient $httpClient
) {
}
}
class MyApplication
{
public function __construct(
private readonly ?HttpClient $httpClient
) {
}
}
class MyApplication
{
public function __construct(
private readonly HttpClient $httpClient,
private readonly string $homeUrl = '',
private readonly int $timeout = 0,
private readonly array $otherOptions = []
) {
}
}
class MyApplication
{
public function __construct(
private readonly HttpClient|GopherClient|string $client
) {
}
}
class MyApplication
{
public function __construct(
private readonly HttpClient&Serializable $httpClient
) {
}
}
/* Do NOT do this! */
class MyApiCaller
{
public function __construct(
private readonly Container $container
) {
}
public function callApi(): HttpResponse
{
$httpClient = $this->container->get(HttpClient::class);
return $httpClient->get('https://www.example.com/api');
}
}
/* Do this instead! */
class MyApiCaller
{
public function __construct(
private readonly HttpClient $httpClient
) {
}
public function callApi(): HttpResponse
{
return $this->httpClient->get('https://www.example.com/api');
}
}
/* Refactor this! */
public function myFragileBloatedFunction(...$args)
{
// ...
// $httpClient = new CurlHttpClient();
// $logger = new FileLogger('myapp.log');
// $httpClient->setLogger($logger);
// Replaced the above duplicated construction logic with a call to the
// container
$httpClient = getAppContainer()->get(HttpClient::class);
$result = $httpClient->get('https://www.example.com/api/' . $args[42]);
// ...
}
/* Eliminate all references to the singleton container and remove this! */
function getAppContainer(): Container
{
static $container;
return $container ??= new Container();
}
/*
* Build your container from the singleton container for now.
* Replace with direct construction once refactoring is complete.
*/
$container = getAppContainer();
$container->addSingletonClass(MyApplication::class);
// ...
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.