Skip to content

Commit

Permalink
Merge pull request #1 from Peroxide-PHP/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
leoqbc authored Oct 28, 2023
2 parents a563b25 + ed660e2 commit 656197d
Show file tree
Hide file tree
Showing 11 changed files with 464 additions and 7 deletions.
127 changes: 126 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,128 @@
[eng](README.md) / [pt-br](README_PT.md)
# Peroxide/Container

A simple Dependency Injection container based on PSR-11 for APIs, with zero dependencies and low functionalities.
A straightforward Dependency Injection container, designed for use with APIs, adhering to the PSR-11 standard. It boasts minimal functionality and operates independently, free from external dependencies.

## Our filosophy
We are passionate about working with components that are as clean and simple as possible. **Peroxide\Container** is a fusion of inspiration drawn from libraries such as *Laminas\ServiceManager*, *Pimple*, and with a touch of *PHPCI*.

The great advantage is that we have no external dependencies. All configuration is achieved through PHP code using array configuration files. All you need to do is ensure that your framework supports PSR-11, set up the configuration, and you're ready to begin your coding journey.
## How to use it
### Instaling
```bash
composer require peroxide/container
```
---

## Starting your journey
**Peroxide\Container** is fully compliant with PSR-11, and it provides the following methods:

```php
# From PSR-11
public function get(string $id): object;
public function has(string $id): bool;

# From our interface SetDependency
public function set(string $id, callable $factory): void;
public function setInvokableClass(string $id, string $invocableClass): void;
```

### Create you configuration as *array*
```php
<?php
use Peroxide\DependencyInjection\Container;

$config = [
YourDependencyName::class => fn() => new YourDependencyName(),
YourDependency::class => YourDependencyFactoryClass::class,

// should be invokable class
ConcreteClass::class => new ConcreteClassFactory(),

// Or passing as reference string
ConcreteClass::class => ConcreteClassFactory::class
];

$container = new Container($config);

// how to get dependencies
$container->get(YourDependencyName::class);
$container->get(YourDependency::class);
$container->get(ConcreteClass::class);
```
### Creating your Factory Class
```php
use Psr\Container\ContainerInterface;
use Peroxide\DependencyInjection\Interfaces\ContainerFactory;

class ConcreteClassFactory implements ContainerFactory
{
public function __invoke(ContainerInterface $container): ConcreteClass
{
// config your dependency injection here
// you can compose your dependency
// return new ParentDependency($container->get(DependencyChild::class));
return new ConcreteClass();
}
}
```
It is also possible to set dependencies separately, after obtaining your container instance:
```php
use Peroxide\DependencyInjection\Container;

$container = new Container();

$container->set(DependencyPath::class, fn() => new DependencyInstance());
```

If the dependency doesn't exist, it will be created; otherwise, it will be replaced by the new factory.
## More configurations
To handle dependency injection within the container, you can easily use ```arrow function``` to compose your dependencies.
```php
$container = new Container([
// Dependency parent with dependency child

// all dependencies should be involved by a Closure(function() or fn())
Dependency::class => fn() => new Dependency(),

ParentDependency::class => function($container) {
return new ParentDependency(
$container->get(Dependency::class)
);
}
// or simply
ParentDependency::class => fn($c) => new ParentDependency($c->get(Dependency::class))
]);
```
You can also compose your configuration using the spread operator, as shown in the example:
```php
use Peroxide\DependencyInjection\Container;
# on 'dependencies.php' config file
$config1 = [ ... ];
$config2 = [ ... ];
return [...$config1, ...$config2];

// -------------------

$config = require __DIR__ . '/dependencies.php';

$container = new Container($config);
```
## How to deal with Singleton?
Just use the Singleton invocable class, here's an example:
```php
use Peroxide\DependencyInjection\Container;
use Peroxide\DependencyInjection\Invokables\Singleton;

$container = new Container([
// Dependency parent with dependency child
Dependency::class => new Singleton(fn() => new Dependency()),
ParentDependency::class => new Singleton(
fn($container) => new ParentDependency($container->get(Dependency::class))
)
]);
```
The ```Peroxide\DependencyInjection\Invokables\Singleton``` class serves as a wrapper to indicate to our container that we want this class to not create a new instance every time it is retrieved.

## Why can't I config parameters on container?
We believe that storing configuration values in the dependency container is unnecessary. Instead, each service should be configured using external environment data. By doing so, you can centralize your project's configuration.
126 changes: 126 additions & 0 deletions README_PT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
[eng](README.md) / [pt-br](README_PT.md)
# Peroxide/Container

Um contêiner de Injeção de Dependência direto, projetado para ser usado com APIs, aderindo ao padrão PSR-11. Ele oferece funcionalidade mínima e opera de forma independente, sem depender de recursos externos.
## Nossa filosofia
Somos apaixonados por trabalhar com componentes o mais limpos e simples possível. **Peroxide/Container** é uma fusão de inspiração proveniente de bibliotecas como *Laminas\ServiceManager*, *Pimple*, e um toque de *PHPCI*.

A grande vantagem é que não temos dependências externas. Toda a configuração é feita por meio de código PHP usando arquivos de configuração em forma de arrays. Tudo o que você precisa fazer é garantir que seu Framework suporte a PSR-11, configure a biblioteca e você estará pronto para começar sua jornada de codificação.

## Como usar
### Instalando
```bash
composer require peroxide/container
```
---

## Iniciando sua jornada
**Peroxide\Container** está totalmente em conformidade com a PSR-11 e oferece os seguintes métodos:

```php
# From PSR-11
public function get(string $id): object;
public function has(string $id): bool;

# From our interface SetDependency
public function set(string $id, callable $factory): void;
public function setInvokableClass(string $id, string $invocableClass): void;
```

### Crie sua configuração com arrays
```php
<?php
use Peroxide\DependencyInjection\Container;

$config = [
YourDependencyName::class => fn() => new YourDependencyName(),
YourDependency::class => YourDependencyFactoryClass::class,

// deve ser uma classe invocável
ConcreteClass::class => new ConcreteClassFactory(),

// Ou passe como uma referencia em string seu factory
ConcreteClass::class => ConcreteClassFactory::class
];

$container = new Container($config);

// como resgatar sua dependência pronta
$container->get(YourDependencyName::class);
$container->get(YourDependency::class);
$container->get(ConcreteClass::class);
```
### Criando sua classe Factory
```php
use Psr\Container\ContainerInterface;
use Peroxide\DependencyInjection\Interfaces\ContainerFactory;

class ConcreteClassFactory implements ContainerFactory
{
public function __invoke(ContainerInterface $container): ConcreteClass
{
// configure sua injeção de dependência aqui
// você pode compor sua dependência
// retorne new ParentDependency($container->get(DependencyChild::class));
return new ConcreteClass();
}
}
```
Também é possível definir dependências separadamente, após obter a instância do seu contêiner:
```php
use Peroxide\DependencyInjection\Container;

$container = new Container();

$container->set(DependencyPath::class, fn() => new DependencyInstance());
```

Se a dependência não existir, ela será criada; caso contrário, será substituída pela atual definição.
## Mais configurações
Para lidar com injeção de dependência dentro do contêiner, você pode facilmente usar uma ```arrow function``` para compor suas dependências.
```php
$container = new Container([
// todas as dependências devem ser envolvidas por uma Closure (função ou fn())
Dependency::class => fn() => new Dependency(),

ParentDependency::class => function($container) {
return new ParentDependency(
$container->get(Dependency::class)
);
}
// ou simplesmente
ParentDependency::class => fn($c) => new ParentDependency($c->get(Dependency::class))
]);
```
Você também pode compor sua configuração usando o operador de expansão, como mostrado no exemplo:
```php
use Peroxide\DependencyInjection\Container;
# no arquivo de configuração 'dependencies.php'
$config1 = [ ... ];
$config2 = [ ... ];
return [...$config1, ...$config2];

// -------------------
# em index.php
$config = require __DIR__ . '/dependencies.php';

$container = new Container($config);
```
## Como lidar com Singleton?
Basta usar a classe ```Singleton```, aqui está um exemplo:
```php
use Peroxide\DependencyInjection\Container;
use Peroxide\DependencyInjection\Invokables\Singleton;

$container = new Container([
// Dependência pai com dependência filha
Dependency::class => new Singleton(fn() => new Dependency()),
ParentDependency::class => new Singleton(
fn($container) => new ParentDependency($container->get(Dependency::class))
)
]);
```
A classe ```Peroxide\DependencyInjection\Invokables\Singleton``` atua como um invólucro para indicar ao nosso contêiner que desejamos que esta classe não crie uma nova instância toda vez que for solicitada.

## Por que não posso configurar parâmetros no contêiner?
Acreditamos que não é necessário armazenar valores de configuração no contêiner de dependência. Em vez disso, cada serviço deve ser configurado usando dados de ambiente externos (por exemplo .env). Fazendo isso, você centraliza a configuração do seu projeto.
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
"require-dev": {
"phpunit/phpunit": "^10.3"
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"tests": "phpunit"
},
Expand Down
2 changes: 2 additions & 0 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ public function setInvokableClass(string $id, string $invocableClass): void
if (false === class_exists($invocableClass)) {
throw new NotFoundException("Class '$id' isn't in project autoload");
}

$invocableObject = new $invocableClass($this);
if (true === is_callable($invocableObject)) {
$this->dependencies[$id] = $invocableObject;
return;
}

throw new NotInvokableClassException("Class '$id' has not a '__invoke' method.");
}

Expand Down
48 changes: 48 additions & 0 deletions src/Invokables/Singleton.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Peroxide\DependencyInjection\Invokables;

use Peroxide\DependencyInjection\Interfaces\ContainerFactory;
use Psr\Container\ContainerInterface;

use Closure;

class Singleton implements ContainerFactory
{
/**
* @var array<string, object>
*/
protected array $singletonObjects = [];

/**
* @var Closure $factoryAction
*/
protected Closure $factoryAction;

public function __construct(
callable $factory,
protected string $dependencyId = ''
) {
$this->dependencyId = uniqid();
$this->factoryAction = $factory;
}

public function has(string $dependencyId): bool
{
return isset($this->singletonObjects[$dependencyId]);
}

protected function store(string $dependencyName, callable $factoryAction, ContainerInterface $container): void
{
$this->singletonObjects[$dependencyName] = $factoryAction($container);
}

public function __invoke(ContainerInterface $container): object
{
if ($this->has($this->dependencyId)) {
return $this->singletonObjects[$this->dependencyId];
}
$this->store($this->dependencyId, $this->factoryAction, $container);
return $this->singletonObjects[$this->dependencyId];
}
}
Loading

0 comments on commit 656197d

Please sign in to comment.