Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
tests:
name: PHP ${{ matrix.php }} - ${{ matrix.dependencies }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3', '8.4']
dependencies: ['lowest', 'highest']

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring
coverage: none
tools: composer:v2

- name: Validate composer.json
run: composer validate --strict

- name: Install dependencies
uses: ramsey/composer-install@v3
with:
dependency-versions: ${{ matrix.dependencies }}

- name: Run PHPUnit
run: vendor/bin/phpunit

static-analysis:
name: Static analysis & code style
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
coverage: none
tools: composer:v2

- name: Install dependencies
uses: ramsey/composer-install@v3

- name: Run PHPStan
run: vendor/bin/phpstan analyse --no-progress

- name: Check code style
run: vendor/bin/php-cs-fixer fix --dry-run --diff
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
/.vscode/
/.vs/
/vendor/
/composer.lock
/composer.lock
/.phpunit.cache/
/.phpunit.result.cache
/.php-cs-fixer.cache
/.phpstan.cache/
19 changes: 19 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

$finder = PhpCsFixer\Finder::create()
->in([__DIR__ . '/src', __DIR__ . '/tests']);

return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR12' => true,
'declare_strict_types' => true,
'array_syntax' => ['syntax' => 'short'],
'no_unused_imports' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['class', 'function', 'const']],
'single_quote' => true,
'trailing_comma_in_multiline' => true,
])
->setFinder($finder);
186 changes: 145 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,79 +1,183 @@
# InitPHP Dependencies Container
# InitPHP Container

Simple Dependencies Container following PSR-11 standards.
Minimal [PSR-11](https://www.php-fig.org/psr/psr-11/) dependency injection container with reflection-based autowiring.

_Note :_ This is a pre-release version of the library currently available. Report potential bugs and feature requests to the issue section of this repo.
[![Latest Stable Version](https://poser.pugx.org/initphp/container/v/stable)](https://packagist.org/packages/initphp/container)
[![Total Downloads](https://poser.pugx.org/initphp/container/downloads)](https://packagist.org/packages/initphp/container)
[![License](https://poser.pugx.org/initphp/container/license)](https://packagist.org/packages/initphp/container)
[![PHP Version Require](https://poser.pugx.org/initphp/container/require/php)](https://packagist.org/packages/initphp/container)

The container resolves entries lazily. You can register values, factories and class bindings explicitly, or let the container autowire a class straight from its name by reading its constructor signature through reflection. Every resolved entry is cached, so the container behaves as a shared (singleton) registry.

## Requirements

- PHP 7.4 or higher
- [PSR-11 Container Interface Package](https://packagist.org/packages/psr/container) 2.0.2
- PHP 8.1 or higher
- [`psr/container`](https://packagist.org/packages/psr/container) `^2.0`

## Installation

```
composer require initphp/container:dev-main
```bash
composer require initphp/container
```

## Usage

Check the `Example` directory for an example usage.
## Quick Start

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

use InitPHP\Container\Container;

class UserModel
class Mailer
{
private string $name;
}

public function set(string $name)
{
$this->name = $name;
}

public function get()
class UserService
{
public function __construct(public Mailer $mailer)
{
return $this->name ?? null;
}
}

class User
$container = new Container();

// No registration needed: the container reads UserService's constructor,
// builds the Mailer dependency automatically and injects it.
$service = $container->get(UserService::class);

var_dump($service instanceof UserService); // bool(true)
var_dump($service->mailer instanceof Mailer); // bool(true)
```

## Usage

### Autowiring

When you call `get()` with an existing class name, the container instantiates it and recursively resolves every class-typed constructor argument:

```php
$service = $container->get(UserService::class);
```

The resolved instance is cached. Asking for the same identifier again returns the exact same object:

```php
$container->get(Mailer::class) === $container->get(Mailer::class); // true
```

### Storing values

`set()` accepts any value. Scalars, arrays and objects are returned as they were stored:

```php
$container->set('app.name', 'InitPHP');
$container->set('config', ['debug' => true]);
$container->set('logger', new FileLogger('/var/log/app.log'));

$container->get('app.name'); // 'InitPHP'
$container->get('config'); // ['debug' => true]
$container->get('logger'); // the same FileLogger instance
```

### Factories (closures)

Register a `Closure` to build an entry lazily. The closure receives the container and runs only once, the first time the entry is requested:

```php
use Psr\Container\ContainerInterface;

$container->set('pdo', function (ContainerInterface $c) {
return new PDO('sqlite::memory:');
});

$pdo = $container->get('pdo'); // closure runs here, result is cached
```

### Binding interfaces to implementations

Bind an interface (or any identifier) to a concrete class name so that both direct lookups and autowired dependencies resolve to the implementation:

```php
interface LoggerInterface
{
private $model;
}

public function __construct(UserModel $model)
{
$this->model = $model;
}
class FileLogger implements LoggerInterface
{
}

public function getModel()
class Report
{
public function __construct(public LoggerInterface $logger)
{
return $this->model;
}
}

$container = new Container();
$user = $container->get(\Example\User::class);
$model = $user->getModel();
$model->set('Muhammet');
echo $user->getModel()->get();
$container->set(LoggerInterface::class, FileLogger::class);

$container->get(LoggerInterface::class); // FileLogger instance
$container->get(Report::class)->logger; // the same FileLogger instance
```

## Contributing
### Checking for an entry

`has()` returns `true` when `get()` would not throw a `NotFoundException` — that is, the identifier is a registered entry or an existing class name:

```php
$container->has('app.name'); // true after set()
$container->has(UserService::class); // true (autowirable class)
$container->has('missing'); // false
```

## How resolution works

`get($id)` resolves in this order:

1. Return the cached instance if `$id` was resolved before.
2. If `$id` was registered with `set()`, build it (invoke the closure, instantiate the class name, or return the stored value) and cache it.
3. If `$id` is an existing class name, autowire it and cache it.
4. Otherwise throw `NotFoundException`.

Constructor parameters are resolved as follows: a class-typed parameter is fetched from the container; otherwise its default value is used; otherwise `null` is supplied for nullable parameters. If none of these apply, resolution fails.

## Exceptions

All exceptions live in `InitPHP\Container\Exception` and implement the relevant PSR-11 interface.

| Exception | Implements | Thrown when |
| --- | --- | --- |
| `NotFoundException` | `Psr\Container\NotFoundExceptionInterface` | The identifier is neither registered nor an existing class. |
| `DependencyIsNotInstantiableException` | `Psr\Container\ContainerExceptionInterface` | The target is an interface, abstract class, or has a non-public constructor. |
| `DependencyHasNoDefaultValueException` | `Psr\Container\ContainerExceptionInterface` | A constructor parameter cannot be autowired and has no default or nullable fallback. |
| `CircularDependencyException` | `Psr\Container\ContainerExceptionInterface` | A class depends on itself directly or through a cycle. |

> All contributions to this project will be published under the MIT License. By submitting a pull request or filing a bug, issue, or feature request, you are agreeing to comply with this waiver of copyright interest.
`ContainerException` is the base class for `NotFoundException` and the three resolution exceptions, so you can catch every container error with a single `catch (ContainerException $e)`.

## Documentation

In-depth guides with runnable examples live in the [`docs/`](./docs) directory:

- [Getting Started](./docs/getting-started.md)
- [Autowiring](./docs/autowiring.md)
- [Binding & Factories](./docs/binding-and-factories.md)
- [Exceptions & Error Handling](./docs/exceptions.md)
- [Limitations](./docs/limitations.md)

## Testing

```bash
composer test # PHPUnit
composer stan # PHPStan (max level)
composer cs-check # PHP-CS-Fixer (dry run)
```

## Contributing

1. Fork it ( https://github.com/initphp/container/fork )
2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am "Add some feature")
4. Push to the branch (git push origin my-new-feature)
5. Create a new Pull Request
Contributions are welcome. Please read the org-wide [CONTRIBUTING guide](https://github.com/InitPHP/.github/blob/main/CONTRIBUTING.md) before opening a pull request.

## Credits

- [Muhammet ŞAFAK](https://www.muhammetsafak.com.tr) <<info@muhammetsafak.com.tr>>

## License

Copyright &copy; 2022 [MIT License](./LICENSE)
Released under the [MIT License](./LICENSE).
52 changes: 43 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"name": "initphp/container",
"description": "Simple Dependencies Container following PSR-11 standards",
"description": "Minimal PSR-11 dependency injection container with reflection-based autowiring.",
"type": "library",
"keywords": [
"container",
"dependency-injection",
"di",
"psr-11",
"autowiring",
"ioc"
],
"license": "MIT",
"autoload": {
"psr-4": {
"InitPHP\\Container\\": "src/"
}
},
"authors": [
{
"name": "Muhammet ŞAFAK",
Expand All @@ -16,9 +19,40 @@
"homepage": "https://www.muhammetsafak.com.tr"
}
],
"minimum-stability": "stable",
"require": {
"php": ">=7.4",
"psr/container": "2.0.2"
"php": "^8.1",
"psr/container": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^10.5",
"phpstan/phpstan": "^2.0",
"friendsofphp/php-cs-fixer": "^3.64"
},
"provide": {
"psr/container-implementation": "2.0"
},
"autoload": {
"psr-4": {
"InitPHP\\Container\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"InitPHP\\Container\\Tests\\": "tests/"
},
"files": [
"tests/Fixtures/fixtures.php"
]
},
"minimum-stability": "stable",
"prefer-stable": true,
"config": {
"sort-packages": true
},
"scripts": {
"test": "phpunit",
"stan": "phpstan analyse",
"cs-check": "php-cs-fixer fix --dry-run --diff",
"cs-fix": "php-cs-fixer fix"
}
}
Loading
Loading