Extract function to load services from services file

Extracts to ContainerBuilder::loadServices(), removes service alias
support and add unit tests.

Signed-off-by: Maurício Meneghini Fauth <mauricio@mfauth.net>
This commit is contained in:
Maurício Meneghini Fauth 2026-01-07 22:37:05 -03:00
parent f7f22adaa8
commit dc1a890126
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
5 changed files with 100 additions and 119 deletions

View File

@ -2,41 +2,17 @@
declare(strict_types=1);
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Reference;
use function is_string;
use PhpMyAdmin\Container\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $configurator): void {
$services = $configurator->services();
/** @param array<string, string|array{class: string, arguments?: array<string>, factory?: callable}> $servicesFile */
$loadServices = static function (array $servicesFile, ServicesConfigurator $services): void {
foreach ($servicesFile as $serviceName => $service) {
if (is_string($service)) {
$services->alias($serviceName, $service);
continue;
}
$theService = $services->set($serviceName, $service['class']);
if (isset($service['factory'])) {
$theService->factory($service['factory']);
}
if (! isset($service['arguments'])) {
continue;
}
foreach ($service['arguments'] as &$argumentName) {
$argumentName = new Reference($argumentName);
}
$theService->args($service['arguments']);
}
};
/** @var array<string, array{class: string, arguments?: array<string>, factory?: callable}> $servicesFile */
$servicesFile = include ROOT_PATH . 'app/services.php';
$loadServices($servicesFile, $services);
ContainerBuilder::loadServices($servicesFile, $services);
/** @var array<string, array{class: string, arguments?: array<string>, factory?: callable}> $servicesFile */
$servicesFile = include ROOT_PATH . 'app/services_controllers.php';
$loadServices($servicesFile, $services);
ContainerBuilder::loadServices($servicesFile, $services);
};

View File

@ -6,72 +6,6 @@ parameters:
count: 11
path: app/constants.php
-
message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#'
identifier: foreach.nonIterable
count: 1
path: app/services_loader.php
-
message: '#^Cannot access offset ''arguments'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: app/services_loader.php
-
message: '#^Cannot access offset ''class'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: app/services_loader.php
-
message: '#^Cannot access offset ''factory'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: app/services_loader.php
-
message: '#^Parameter \#1 \$arguments of method Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServiceConfigurator\:\:args\(\) expects array, mixed given\.$#'
identifier: argument.type
count: 1
path: app/services_loader.php
-
message: '#^Parameter \#1 \$factory of method Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServiceConfigurator\:\:factory\(\) expects array\|Closure\|string\|Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ReferenceConfigurator\|Symfony\\Component\\ExpressionLanguage\\Expression, mixed given\.$#'
identifier: argument.type
count: 1
path: app/services_loader.php
-
message: '#^Parameter \#1 \$id of class Symfony\\Component\\DependencyInjection\\Reference constructor expects string, mixed given\.$#'
identifier: argument.type
count: 1
path: app/services_loader.php
-
message: '#^Parameter \#1 \$id of method Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServicesConfigurator\:\:alias\(\) expects string, \(int\|string\) given\.$#'
identifier: argument.type
count: 1
path: app/services_loader.php
-
message: '#^Parameter \#1 \$id of method Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServicesConfigurator\:\:set\(\) expects string\|null, \(int\|string\) given\.$#'
identifier: argument.type
count: 1
path: app/services_loader.php
-
message: '#^Parameter \#1 \$servicesFile of closure expects array, mixed given\.$#'
identifier: argument.type
count: 2
path: app/services_loader.php
-
message: '#^Parameter \#2 \$class of method Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServicesConfigurator\:\:set\(\) expects string\|null, mixed given\.$#'
identifier: argument.type
count: 1
path: app/services_loader.php
-
message: '#^Access to constant FORMAT_HTTP on an unknown class OpenID_Message\.$#'
identifier: class.notFound
@ -1590,6 +1524,12 @@ parameters:
count: 1
path: src/Console/History.php
-
message: '#^Parameter \#1 \$factory of method Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ServiceConfigurator\:\:factory\(\) expects array\|Closure\|string\|Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ReferenceConfigurator\|Symfony\\Component\\ExpressionLanguage\\Expression, callable\(\)\: mixed given\.$#'
identifier: argument.type
count: 1
path: src/Container/ContainerBuilder.php
-
message: '#^Binary operation "\.\=" between mixed and '' \-\> '' results in an error\.$#'
identifier: assignOp.invalid

View File

@ -1,27 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.13.1@1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51">
<file src="app/services_loader.php">
<MixedArgument>
<code><![CDATA[$argumentName]]></code>
<code><![CDATA[$service['arguments']]]></code>
<code><![CDATA[$service['class']]]></code>
<code><![CDATA[$service['factory']]]></code>
</MixedArgument>
<MixedArgumentTypeCoercion>
<code><![CDATA[$serviceName]]></code>
<code><![CDATA[$serviceName]]></code>
</MixedArgumentTypeCoercion>
<MixedArrayAccess>
<code><![CDATA[$service['arguments']]]></code>
<code><![CDATA[$service['arguments']]]></code>
<code><![CDATA[$service['class']]]></code>
<code><![CDATA[$service['factory']]]></code>
</MixedArrayAccess>
<MixedAssignment>
<code><![CDATA[$argumentName]]></code>
<code><![CDATA[$service]]></code>
</MixedAssignment>
</file>
<file src="src/Advisory/Advisor.php">
<ArgumentTypeCoercion>
<code><![CDATA[$pattern]]></code>
@ -676,6 +654,11 @@
<code><![CDATA[! $maxTime]]></code>
</RiskyTruthyFalsyComparison>
</file>
<file src="src/Container/ContainerBuilder.php">
<ArgumentTypeCoercion>
<code><![CDATA[$service['factory']]]></code>
</ArgumentTypeCoercion>
</file>
<file src="src/Controllers/BrowseForeignersController.php">
<PossiblyUnusedReturnValue>
<code><![CDATA[Response]]></code>

View File

@ -7,7 +7,9 @@ namespace PhpMyAdmin\Container;
use Psr\Container\ContainerInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder as SymfonyContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use const ROOT_PATH;
@ -34,4 +36,25 @@ final class ContainerBuilder
return $container;
}
/** @param array<string, array{class: string, arguments?: array<string>, factory?: callable}> $services */
public static function loadServices(array $services, ServicesConfigurator $servicesConfigurator): void
{
foreach ($services as $serviceName => $service) {
$serviceConfigurator = $servicesConfigurator->set($serviceName, $service['class']);
if (isset($service['factory'])) {
$serviceConfigurator->factory($service['factory']);
}
if (! isset($service['arguments'])) {
continue;
}
foreach ($service['arguments'] as &$argument) {
$argument = new Reference($argument);
}
$serviceConfigurator->args($service['arguments']);
}
}
}

View File

@ -4,12 +4,19 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Container;
use PhpMyAdmin\Config;
use PhpMyAdmin\Container\ContainerBuilder;
use PhpMyAdmin\Current;
use PhpMyAdmin\Database\Events;
use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\FlashMessenger;
use PhpMyAdmin\Tests\AbstractTestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\DependencyInjection\ContainerBuilder as SymfonyContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use function array_keys;
use function array_map;
@ -52,4 +59,56 @@ final class ContainerBuilderTest extends AbstractTestCase
array_merge(array_keys($services), array_keys($controllerServices)),
);
}
public function testLoadServices(): void
{
$container = new SymfonyContainerBuilder();
$loader = self::createStub(PhpFileLoader::class);
$instanceof = [];
$servicesConfigurator = new ServicesConfigurator($container, $loader, $instanceof);
$services = [
Config::class => ['class' => Config::class, 'factory' => [Config::class, 'getInstance']],
DatabaseInterface::class => [
'class' => DatabaseInterface::class,
'factory' => [DatabaseInterface::class, 'getInstance'],
'arguments' => [Config::class],
],
Events::class => ['class' => Events::class, 'arguments' => [DatabaseInterface::class, Config::class]],
FlashMessenger::class => ['class' => FlashMessenger::class],
];
ContainerBuilder::loadServices($services, $servicesConfigurator);
$definitions = $container->getDefinitions();
self::assertArrayHasKey(Config::class, $definitions);
self::assertSame(Config::class, $definitions[Config::class]->getClass());
self::assertSame([Config::class, 'getInstance'], $definitions[Config::class]->getFactory());
self::assertSame([], $definitions[Config::class]->getArguments());
self::assertArrayHasKey(DatabaseInterface::class, $definitions);
self::assertSame(DatabaseInterface::class, $definitions[DatabaseInterface::class]->getClass());
self::assertSame(
[DatabaseInterface::class, 'getInstance'],
$definitions[DatabaseInterface::class]->getFactory(),
);
self::assertEquals(
[new Reference(Config::class)],
$definitions[DatabaseInterface::class]->getArguments(),
);
self::assertArrayHasKey(Events::class, $definitions);
self::assertSame(Events::class, $definitions[Events::class]->getClass());
self::assertNull($definitions[Events::class]->getFactory());
self::assertEquals(
[new Reference(DatabaseInterface::class), new Reference(Config::class)],
$definitions[Events::class]->getArguments(),
);
self::assertArrayHasKey(FlashMessenger::class, $definitions);
self::assertSame(FlashMessenger::class, $definitions[FlashMessenger::class]->getClass());
self::assertNull($definitions[FlashMessenger::class]->getFactory());
self::assertSame([], $definitions[FlashMessenger::class]->getArguments());
}
}