Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/http.svg)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord)](https://discord.gg/GSeTUeA)

Utopia DI library is simple and lite library for managing dependency injections. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io).
Utopia DI is a small dependency injection container with scoped resources. It is designed to stay simple while still covering the resource lifecycle used across the Utopia libraries. This library is maintained by the [Appwrite team](https://appwrite.io).

Although this library is part of the Utopia Framework project it is dependency free, and can be used as standalone with any other PHP project or framework.

Expand All @@ -15,25 +15,39 @@ Although this library is part of the Utopia Framework project it is dependency f
Install using Composer:

```bash
composer require utopia-php/http
composer require utopia-php/di
```


```php
require_once __DIR__.'/../vendor/autoload.php';

use Utopia\DI\Container;

TBD
$di = new Container();

$di->setResource('config', fn () => ['region' => 'eu-west-1']);
$di->setResource('db', fn (array $config) => new PDO(...), ['config']);
Comment thread
ChiragAgg5k marked this conversation as resolved.
Outdated

$db = $di->getResource('db', 'request-1');
```

Resources are cached per context. Definitions registered in the default `utopia` context are automatically available in other contexts, while context-specific definitions override the default for that request.

```php
$di->setResource('request-id', fn () => 'req-1', context: 'request-1');

$requestResources = $di->getResources(['db', 'request-id'], 'request-1');

$di->purge('request-1');
```

## System Requirements

Utopia HTTP requires PHP 8.1 or later. We recommend using the latest PHP version whenever possible.
Utopia DI requires PHP 8.2 or later. We recommend using the latest PHP version whenever possible.

## More from Utopia

Our ecosystem supports other thin PHP projects aiming to extend the core PHP Utopia HTTP.
Our ecosystem supports other thin PHP projects aiming to extend the core PHP Utopia libraries.

Each project is focused on solving a single, very simple problem and you can use composer to include any of them in your next project.

Expand All @@ -45,7 +59,7 @@ All code contributions - including those of people having commit access - must g

Fork the project, create a feature branch, and send us a pull request.

You can refer to the [Contributing Guide](https://github.com/utopia-php/http/blob/master/CONTRIBUTING.md) for more info.
You can refer to the [Contributing Guide](https://github.com/utopia-php/di/blob/master/CONTRIBUTING.md) for more info.

For security issues, please email security@appwrite.io instead of posting a public issue in GitHub.

Expand Down
197 changes: 150 additions & 47 deletions src/DI/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,127 +6,230 @@

class Container
{
public const DEFAULT_CONTEXT = 'utopia';

/**
* @var array
* @var array<string, array<string, Injection>>
*/
protected array $dependencies = [];

/**
* @var array
* @var array<string, array<string, mixed>>
*/
protected array $instances = [];

public function __construct()
{
$di = new Dependency();
$di->setName('di');
$di->setCallback(function () {
return $this;
});
$this->dependencies[$di->getName()] = $di;
}

/**
* Set a dependency.
*
* @param Dependency|Injection $dependency
* @param string $context
* @return self
*
* @throws Exception
*/
public function set(Dependency|Injection $dependency): self
public function set(Dependency|Injection $dependency, string $context = self::DEFAULT_CONTEXT): self
{
if ($dependency->getName() === 'di') {
throw new Exception("'di' is a reserved keyword.");
}

if (\array_key_exists($dependency->getName(), $this->instances)) {
unset($this->instances[$dependency->getName()]);
}
$this->dependencies[$context] ??= [];
$this->instances[$context] ??= [];

$this->dependencies[$dependency->getName()] = $dependency;
unset($this->instances[$context][$dependency->getName()]);
$this->dependencies[$context][$dependency->getName()] = $dependency;
Comment thread
ChiragAgg5k marked this conversation as resolved.
Outdated

return $this;
}

/**
* Get a dependency.
* Register a callable resource.
*
* @param string $name
* @param callable $callback
* @param string[] $dependencies
* @param string $context
* @return self
*/
public function setResource(string $name, callable $callback, array $dependencies = [], string $context = self::DEFAULT_CONTEXT): self
{
$resource = new Resource();
$resource
->setName($name)
->setCallback($callback)
;

foreach ($dependencies as $dependency) {
$resource->inject($dependency);
}

return $this->set($resource, $context);
}

/**
* Get a dependency.
*
* @param string $name
* @param string $context
* @param bool $fresh
* @return mixed
*
* @throws Exception
*/
public function get(string $name): mixed
public function get(string $name, string $context = self::DEFAULT_CONTEXT, bool $fresh = false): mixed
{
if (!\array_key_exists($name, $this->dependencies)) {
throw new Exception('Failed to find dependency: "' . $name . '"');
if ($name === 'di') {
return $this;
}

return $this->inject($this->dependencies[$name]);
$injection = $this->getDefinition($name, $context);

return $this->inject($injection, $context, $fresh);
}

/**
* Check if a dependency exists.
* Alias for get().
*
* @param string $name
* @param string $context
* @param bool $fresh
* @return mixed
*
* @throws Exception
*/
public function getResource(string $name, string $context = self::DEFAULT_CONTEXT, bool $fresh = false): mixed
{
return $this->get($name, $context, $fresh);
}

/**
* Resolve multiple dependencies for a context.
*
* @param string[] $names
* @param string $context
* @return array<string, mixed>
*
* @throws Exception
*/
public function getResources(array $names, string $context = self::DEFAULT_CONTEXT): array
{
$resources = [];

foreach ($names as $name) {
$resources[$name] = $this->get($name, $context);
}

return $resources;
}

/**
* Check if a dependency exists in the current or default context.
*
* @param string $name
* @param string $context
* @return bool
*/
public function has(string $name): bool
public function has(string $name, string $context = self::DEFAULT_CONTEXT): bool
{
return \array_key_exists($name, $this->dependencies);
if ($name === 'di') {
return true;
}

return isset($this->dependencies[$context][$name]) || isset($this->dependencies[self::DEFAULT_CONTEXT][$name]);
}

/**
* Resolve the dependencies of a given injection.
*
* @param Injection $injection
* @param string $context
* @param bool $fresh
*
* @return mixed
*
* @throws Exception
*/
public function inject(Injection $injection, bool $fresh = false): mixed // Route
public function inject(Injection $injection, string $context = self::DEFAULT_CONTEXT, bool $fresh = false): mixed
{
if (\array_key_exists($injection->getName(), $this->instances) && !$fresh) {
return $this->instances[$injection->getName()];
$this->instances[$context] ??= [];

if (\array_key_exists($injection->getName(), $this->instances[$context]) && !$fresh) {
return $this->instances[$context][$injection->getName()];
}

$arguments = [];

foreach ($injection->getDependencies() as $dependency) {
$arguments[] = $this->get($dependency, $context);
}

if (\array_key_exists($dependency, $this->instances)) {
$arguments[] = $this->instances[$dependency];
continue;
}

if (!\array_key_exists($dependency, $this->dependencies)) {
throw new Exception('Failed to find dependency: "' . $dependency . '"');
}
$resolved = \call_user_func_array($injection->getCallback(), $arguments);
$this->instances[$context][$injection->getName()] = $resolved;

$arguments[] = $this->get($dependency);
return $resolved;
}

/**
* Refresh a dependency instance.
*
* @param string $name
* @param string|null $context
* @return self
*/
public function refresh(string $name, ?string $context = null): self
{
if ($name === 'di') {
return $this;
}

$resolved = \call_user_func_array($injection->getCallback(), $arguments);
if ($context !== null) {
unset($this->instances[$context][$name]);

return $this;
}

$this->instances[$injection->getName()] = $resolved;
foreach (\array_keys($this->instances) as $instanceContext) {
unset($this->instances[$instanceContext][$name]);
}

return $resolved;
return $this;
}

/**
* Refresh a dependency
* Remove context-specific registrations and cached instances.
*
* @param string $name
* @param string $context
* @return self
* @throws Exception
*/
public function refresh(string $name): self
public function purge(string $context): self
{
if(\array_key_exists($name, $this->instances)) {
unset($this->instances[$name]);
if ($context === self::DEFAULT_CONTEXT) {
$this->instances[$context] = [];

return $this;
}

unset($this->dependencies[$context], $this->instances[$context]);

return $this;
}

/**
* @param string $name
* @param string $context
* @return Injection
*
* @throws Exception
*/
protected function getDefinition(string $name, string $context): Injection
{
if (isset($this->dependencies[$context][$name])) {
return $this->dependencies[$context][$name];
}

if (isset($this->dependencies[self::DEFAULT_CONTEXT][$name])) {
return $this->dependencies[self::DEFAULT_CONTEXT][$name];
}

throw new Exception('Failed to find dependency: "' . $name . '"');
}
}
7 changes: 7 additions & 0 deletions src/DI/Resource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Utopia\DI;

class Resource extends Dependency
{
}
Loading
Loading