Skip to content

[DependencyInjection] Update ResolveClassPass to check class existence #61215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 29, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,44 +33,44 @@ public function testProcess()
$container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32);
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);

$container->register('Test\public_service')
$container->register('test.public_service', 'stdClass')
->setPublic(true)
->addArgument(new Reference('Test\private_used_shared_service'))
->addArgument(new Reference('Test\private_used_non_shared_service'))
->addArgument(new Reference('Test\soon_private_service'))
->addArgument(new Reference('test.private_used_shared_service'))
->addArgument(new Reference('test.private_used_non_shared_service'))
->addArgument(new Reference('test.soon_private_service'))
;

$container->register('Test\soon_private_service')
$container->register('test.soon_private_service', 'stdClass')
->setPublic(true)
->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.42'])
;
$container->register('Test\soon_private_service_decorated')
$container->register('test.soon_private_service_decorated', 'stdClass')
->setPublic(true)
->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.42'])
;
$container->register('Test\soon_private_service_decorator')
->setDecoratedService('Test\soon_private_service_decorated')
->setArguments(['Test\soon_private_service_decorator.inner']);
$container->register('test.soon_private_service_decorator', 'stdClass')
->setDecoratedService('test.soon_private_service_decorated')
->setArguments(['test.soon_private_service_decorator.inner']);

$container->register('Test\private_used_shared_service');
$container->register('Test\private_unused_shared_service');
$container->register('Test\private_used_non_shared_service')->setShared(false);
$container->register('Test\private_unused_non_shared_service')->setShared(false);
$container->register('test.private_used_shared_service', 'stdClass');
$container->register('test.private_unused_shared_service', 'stdClass');
$container->register('test.private_used_non_shared_service', 'stdClass')->setShared(false);
$container->register('test.private_unused_non_shared_service', 'stdClass')->setShared(false);

$container->compile();

$expected = [
'Test\private_used_shared_service' => new ServiceClosureArgument(new Reference('Test\private_used_shared_service')),
'Test\private_used_non_shared_service' => new ServiceClosureArgument(new Reference('Test\private_used_non_shared_service')),
'Test\soon_private_service' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service')),
'Test\soon_private_service_decorator' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service_decorated')),
'Test\soon_private_service_decorated' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service_decorated')),
'test.private_used_shared_service' => new ServiceClosureArgument(new Reference('test.private_used_shared_service')),
'test.private_used_non_shared_service' => new ServiceClosureArgument(new Reference('test.private_used_non_shared_service')),
'test.soon_private_service' => new ServiceClosureArgument(new Reference('.container.private.test.soon_private_service')),
'test.soon_private_service_decorator' => new ServiceClosureArgument(new Reference('.container.private.test.soon_private_service_decorated')),
'test.soon_private_service_decorated' => new ServiceClosureArgument(new Reference('.container.private.test.soon_private_service_decorated')),
];

$privateServices = $container->getDefinition('test.private_services_locator')->getArgument(0);
unset($privateServices[\Symfony\Component\DependencyInjection\ContainerInterface::class], $privateServices[ContainerInterface::class]);

$this->assertEquals($expected, $privateServices);
$this->assertFalse($container->getDefinition('Test\private_used_non_shared_service')->isShared());
$this->assertFalse($container->getDefinition('test.private_used_non_shared_service')->isShared());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ public function process(ContainerBuilder $container): void
continue;
}
if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $id)) {
if ($definition instanceof ChildDefinition && !class_exists($id)) {
throw new InvalidArgumentException(\sprintf('Service definition "%s" has a parent but no class, and its name looks like an FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.', $id));
if (!class_exists($id) && !interface_exists($id)) {
$error = $definition instanceof ChildDefinition ?
'has a parent but no class, and its name looks like a FQCN. Either the class is missing or you want to inherit it from the parent service' :
'name looks like a FQCN but the class does not exist';

throw new InvalidArgumentException("Service definition \"{$id}\" {$error}. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.");
}

$definition->setClass($id);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public function testResolveClassFromId($serviceId)

public static function provideValidClassId()
{
yield ['Acme\UnknownClass'];
yield [CaseSensitiveClass::class];
}

Expand All @@ -62,7 +61,7 @@ public static function provideInvalidClassId()
public function testNonFqcnChildDefinition()
{
$container = new ContainerBuilder();
$parent = $container->register('App\Foo', null);
$parent = $container->register('App\Foo.parent', 'App\Foo');
$child = $container->setDefinition('App\Foo.child', new ChildDefinition('App\Foo'));

(new ResolveClassPass())->process($container);
Expand All @@ -74,7 +73,7 @@ public function testNonFqcnChildDefinition()
public function testClassFoundChildDefinition()
{
$container = new ContainerBuilder();
$parent = $container->register('App\Foo', null);
$parent = $container->register('foo.parent', 'App\Foo');
$child = $container->setDefinition(self::class, new ChildDefinition('App\Foo'));

(new ResolveClassPass())->process($container);
Expand All @@ -86,11 +85,21 @@ public function testClassFoundChildDefinition()
public function testAmbiguousChildDefinition()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service definition "App\Foo\Child" has a parent but no class, and its name looks like an FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.');
$this->expectExceptionMessage('Service definition "App\Foo\Child" has a parent but no class, and its name looks like a FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.');
$container = new ContainerBuilder();
$container->register('App\Foo', null);
$container->register('app.foo', 'App\Foo');
$container->setDefinition('App\Foo\Child', new ChildDefinition('App\Foo'));

(new ResolveClassPass())->process($container);
}

public function testInvalidClassNameDefinition()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service definition "Acme\UnknownClass" name looks like a FQCN but the class does not exist. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.');
$container = new ContainerBuilder();
$container->register('Acme\UnknownClass');

(new ResolveClassPass())->process($container);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1546,11 +1546,9 @@ public function testClassFromId()
{
$container = new ContainerBuilder();

$unknown = $container->register('Acme\UnknownClass');
$autoloadClass = $container->register(CaseSensitiveClass::class);
$container->compile();

$this->assertSame('Acme\UnknownClass', $unknown->getClass());
$this->assertEquals(CaseSensitiveClass::class, $autoloadClass->getClass());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
require_once __DIR__.'/../Fixtures/includes/classes.php';
require_once __DIR__.'/../Fixtures/includes/foo.php';
require_once __DIR__.'/../Fixtures/includes/foo_lazy.php';
require_once __DIR__.'/../Fixtures/includes/fixture_app_services.php';

class PhpDumperTest extends TestCase
{
Expand Down Expand Up @@ -1317,7 +1318,7 @@ public function testInlineSelfRef()
->setProperty('bar', $bar)
->addArgument($bar);

$container->register('App\Foo')
$container->register('App\Foo', 'App\Foo')
->setPublic(true)
->addArgument($baz);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

$container = new ContainerBuilder();

$container->register(\Foo\Foo::class)->setPublic(true);
$container->register(\Bar\Foo::class)->setPublic(true);
$container->register('Foo\Foo', \Foo\Foo::class)->setPublic(true);
$container->register('Bar\Foo', \Bar\Foo::class)->setPublic(true);

return $container;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
$container->register(HotPath\C1::class)->addTag('container.hot_path')->setPublic(true);
$container->register(HotPath\C2::class)->addArgument(new Reference(HotPath\C3::class))->setPublic(true);
$container->register(HotPath\C3::class);
$container->register(ParentNotExists::class)->setPublic(true);
$container->register(ParentNotExists::class, ParentNotExists::class)->setPublic(true);

return $container;
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace App;

class BarService
{
}

class Db
{
public Schema $schema;
}

class Bus
{
public Handler1 $handler1;
public Handler2 $handler2;

public function __construct(public Db $db)
{
}
}

class Handler1
{
public function __construct(
public Db $db,
public Schema $schema,
public Processor $processor,
) {
}
}

class Handler2
{
public function __construct(
public Db $db,
public Schema $schema,
public Processor $processor,
) {
}
}

class Processor
{
public function __construct(
public Registry $registry,
public Db $db,
) {
}
}

class Registry
{
public array $processor;
}

class Schema
{
public function __construct(public Db $db)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,13 @@ public function setOtherInstances($otherInstances)
$this->otherInstances = $otherInstances;
}
}

namespace Acme;

class Foo
{
}

class WithShortCutArgs
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Loader;

require_once __DIR__.'/../Fixtures/includes/AcmeExtension.php';
require_once __DIR__.'/../Fixtures/includes/fixture_app_services.php';

use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Builder\ConfigBuilderGenerator;
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy