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
12 changes: 4 additions & 8 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,11 @@ jobs:
fail-fast: false
matrix:
# normal, highest, non-dev installs
php-version: [ '8.2' ]
php-version: [ '8.4' ]
dependency-versions: [ 'highest' ]
include:
# testing lowest PHP version with the lowest dependencies
- php-version: '8.2'
dependency-versions: 'lowest'

# testing dev versions with the highest PHP
- php-version: '8.2'
- php-version: '8.4'
dependency-versions: 'highest'

steps:
Expand All @@ -31,7 +27,7 @@ jobs:
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
coverage: "xdebug"
php-version: "${{ matrix.php-version }}"

- name: "Composer install"
Expand All @@ -40,5 +36,5 @@ jobs:
dependency-versions: "${{ matrix.dependency-versions }}"
composer-options: "--prefer-dist --no-progress"

- name: "RUn tests"
- name: "Run tests"
run: "composer coverage-html"
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
vendor
composer.lock
.phpunit.result.cache
.phpunit.cache
.php-cs-fixer.cache
test-coverage-report
phpunit.xml
.php-cs-fixer.php
phpstan.neon
phpstan.neon
tests/Unit/var
3 changes: 2 additions & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF
]
],
'phpdoc_to_comment' => false, // отключаем
))
->setRiskyAllowed(true)
->setFinder($finder);
14 changes: 7 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
],
"require": {
"php": "^8.2",
"psr/container": "^2.0"
"psr/container": "^2"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.29",
"friendsofphp/php-cs-fixer": "^3.13",
"phpstan/phpstan": "^1.9",
"phpunit/php-code-coverage": "^9.2",
"phpunit/phpunit": "^9.5",
"vimeo/psalm": "^5.2"
"ergebnis/composer-normalize": "^2",
"friendsofphp/php-cs-fixer": "^3",
"phpstan/phpstan": "^2",
"phpunit/php-code-coverage": "^10 || ^11 || ^12",
"phpunit/phpunit": "^10 || ^11 || ^12",
"vimeo/psalm": "^6"
},
"suggest": {
"micro/autowire": "Autowire helper for dependency injection"
Expand Down
21 changes: 9 additions & 12 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.3/phpunit.xsd"
bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<coverage>
<include>
<directory suffix=".php">src/</directory>
</include>
</coverage>
stopOnFailure="false"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false">
<testsuites>
<testsuite name="Micro Component: Dependency Injection Unit Test Suite">
<directory>tests/Unit</directory>
Expand All @@ -23,4 +15,9 @@
<php>
<env name="APP_ENV" value="dev-test"/>
</php>
<source>
<include>
<directory suffix=".php">src/</directory>
</include>
</source>
</phpunit>
27 changes: 21 additions & 6 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

use Micro\Component\DependencyInjection\Exception\ServiceNotRegisteredException;
use Micro\Component\DependencyInjection\Exception\ServiceRegistrationException;
use Psr\Container\ContainerInterface;

/**
* @author Stanislau Komar <head.trackingsoft@gmail.com>
Expand All @@ -26,12 +25,12 @@
private array $services = [];

/**
* @var array<class-string, callable(Container): object>
* @var array<class-string, callable>
*/
private array $servicesRaw = [];

/**
* @var array<class-string, array<int, array<callable(object, Container): object>>>
* @var array<class-string, array<int, array<int, callable>>>
*/
private array $decorators = [];

Expand All @@ -45,15 +44,16 @@
*
* @psalm-return T
*/
#[\Override]
public function get(string $id): object
{
if (!empty($this->services[$id])) {
return $this->services[$id];

Check warning on line 51 in src/Container.php

View workflow job for this annotation

GitHub Actions / PHP_CodeSniffer

MixedReturnStatement

src/Container.php:51:20: MixedReturnStatement: Could not infer a return type (see https://psalm.dev/138)
}

$this->initializeService($id);

return $this->services[$id];

Check warning on line 56 in src/Container.php

View workflow job for this annotation

GitHub Actions / PHP_CodeSniffer

MixedReturnStatement

src/Container.php:56:16: MixedReturnStatement: Could not infer a return type (see https://psalm.dev/138)
}

/**
Expand All @@ -61,15 +61,18 @@
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
#[\Override]
public function has(string $id): bool
{
return !empty($this->servicesRaw[$id]) || !empty($this->services[$id]);
return \array_key_exists($id, $this->servicesRaw)
|| \array_key_exists($id, $this->services);
}

#[\Override]
public function register(string $id, callable $service, bool $force = false): void
{
if ($this->has($id) && !$force) {
throw new ServiceRegistrationException(sprintf('Service "%s" already registered', $id));
throw new ServiceRegistrationException(\sprintf('Service "%s" already registered', $id));
}

$this->servicesRaw[$id] = $service;
Expand All @@ -78,11 +81,23 @@
/**
* @psalm-suppress InvalidPropertyAssignmentValue
*/
#[\Override]
public function decorate(string $id, callable $service, int $priority = 0): void
{
if (!\array_key_exists($id, $this->decorators)) {
/**
* @psalm-suppress PropertyTypeCoercion
*
* @phpstan-ignore-next-line
*/
$this->decorators[$id] = [];
}

/**
* @psalm-suppress PropertyTypeCoercion
*
* @phpstan-ignore-next-line
*/
$this->decorators[$id][$priority][] = $service;
}

Expand All @@ -93,12 +108,12 @@
*/
protected function initializeService(string $serviceId): void
{
if (empty($this->servicesRaw[$serviceId])) {
if (!isset($this->servicesRaw[$serviceId])) {
throw new ServiceNotRegisteredException($serviceId);
}

$raw = $this->servicesRaw[$serviceId];
$service = $raw($this);

Check warning on line 116 in src/Container.php

View workflow job for this annotation

GitHub Actions / PHP_CodeSniffer

MixedAssignment

src/Container.php:116:9: MixedAssignment: Unable to determine the type that $service is being assigned to (see https://psalm.dev/032)
$this->services[$serviceId] = $service;

if (!\array_key_exists($serviceId, $this->decorators)) {
Expand Down
119 changes: 119 additions & 0 deletions src/ContainerCompiled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Micro framework package.
*
* (c) Stanislau Komar <kost@micro-php.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Micro\Component\DependencyInjection;

use Micro\Component\DependencyInjection\Exception\ServiceRegistrationException;
use Micro\Component\DependencyInjection\Proxy\ProxyBuilderInterface;
use Micro\Component\DependencyInjection\Proxy\ProxyClassNameGeneratorInterface;
use Psr\Container\ContainerInterface as PsrContainerInterface;

/** @psalm-suppress UnusedClass */
final class ContainerCompiled extends Container implements ContainerRegistryCompiledInterface
{
private bool $isCompiled;

private PsrContainerInterface $decorated;

public function __construct(
private readonly ProxyClassNameGeneratorInterface $classNameGenerator,
private readonly ProxyBuilderInterface $proxyBuilder,
?PsrContainerInterface $decorated = null,
) {
$this->isCompiled = false;
$this->decorated = $decorated ?? new Container();
}

/**
* @template T of object
*
* @param class-string<T> $id
*
* @psalm-suppress MoreSpecificImplementedParamType
*
* @return T
*/
#[\Override]
public function get(string $id): object
{
$proxyClass = $this->classNameGenerator->createProxyClassName($id);
if (!class_exists($proxyClass)) {
/* @psalm-suppress MixedReturnStatement */
return $this->decorated->get($id);

Check warning on line 52 in src/ContainerCompiled.php

View workflow job for this annotation

GitHub Actions / PHP_CodeSniffer

MixedReturnStatement

src/ContainerCompiled.php:52:20: MixedReturnStatement: Could not infer a return type (see https://psalm.dev/138)
}

/**
* @var T $instance
*
* @psalm-suppress MixedMethodCall
*/
$instance = new $proxyClass($this);

return $instance;
}

#[\Override]
public function has(string $id): bool
{
return $this->decorated->has($id);
}

#[\Override]
public function register(string $id, callable $service, bool $force = false): void
{
if (!$this->decorated instanceof ContainerRegistryInterface) {
throw new ServiceRegistrationException('Container can not decorate service because container does not implement '.ContainerRegistryInterface::class);
}

if ($this->isCompiled) {
throw new ServiceRegistrationException('Can not register service because container already compiled.');
}

if (interface_exists($id) || class_exists($id)) {
$this->proxyBuilder->add($id);
}

/** @psalm-suppress ArgumentTypeCoercion */
$this->decorated->register($id, $service, $force);
}

/**
* @psalm-suppress InvalidPropertyAssignmentValue
*/
#[\Override]
public function decorate(string $id, callable $service, int $priority = 0): void
{
if (!$this->decorated instanceof ContainerDecoratorInterface) {
throw new ServiceRegistrationException('Container can not decorate service because container does not implement '.ContainerDecoratorInterface::class);
}

if ($this->isCompiled) {
throw new ServiceRegistrationException('Can not register service because container already compiled.');
}

if (interface_exists($id) || class_exists($id)) {
$this->proxyBuilder->add($id);
}

/** @psalm-suppress ArgumentTypeCoercion */
$this->decorated->decorate($id, $service, $priority);
}

#[\Override]
public function compile(): void
{
$this->proxyBuilder->build();

$this->isCompiled = true;
}
}
3 changes: 2 additions & 1 deletion src/ContainerDecoratorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ interface ContainerDecoratorInterface
/**
* @template T of object
*
* @param class-string<T> $id
* @param class-string<T>|non-empty-string $id
* @param callable(T, ContainerInterface): T $service
*/
public function decorate(string $id, callable $service, int $priority = 0): void;
}
37 changes: 37 additions & 0 deletions src/ContainerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Micro framework package.
*
* (c) Stanislau Komar <kost@micro-php.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Micro\Component\DependencyInjection;

interface ContainerInterface extends \Psr\Container\ContainerInterface
{
/**
* @param class-string $id
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
#[\Override]
public function has(string $id): bool;

/**
* @template T of object
*
* @param class-string<T> $id
*
* @psalm-return T
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
#[\Override]
public function get(string $id): mixed;
}
20 changes: 20 additions & 0 deletions src/ContainerRegistryCompiledInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Micro framework package.
*
* (c) Stanislau Komar <kost@micro-php.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Micro\Component\DependencyInjection;

interface ContainerRegistryCompiledInterface extends ContainerRegistryInterface
{
/** @psalm-suppress PossiblyUnusedMethod */
public function compile(): void;
}
Loading