PHP-DI / PHP-DI

The dependency injection container for humans
https://php-di.org
MIT License
2.67k stars 320 forks source link

Add support for class_alias() #438

Open 0b10011 opened 8 years ago

0b10011 commented 8 years ago

class_alias() doesn't seem to be taken into account with manually specified definitions (possibly broken elsewhere as well).

class Test {}
class_alias('Test', 'TestAlias');

$builder = new DI\ContainerBuilder();
$builder->useAutowiring(false);
$builder->addDefinitions([
    'TestAlias' => new TestAlias(),
]);
$container = $builder->build();

// O:4:"Test":0:{}
$test = $container->get('Test');

// Throws DI\NotFoundException("No entry or class found for 'TestAlias'")
$testAlias = $container->get('TestAlias');

I've gotten this to work with 0b10011/PHP-DI@5d97d3316e97b0befe0ad2ff1c40d7fca7f1f69b (includes a test), but there's likely a better way that can add support for class_alias() everywhere.

mnapoli commented 8 years ago

Thanks for the report and the test case. I'm not sure if that's something that supported by other containers and if PHP-DI should support it. The main thing I'm afraid is the performance impact of any solution. Maybe if there's a fast and clean solution we could add support for it?

0b10011 commented 8 years ago

I think the big problem is that the provided name gets changed at some point within PHP-DI to the actual class's name. So even though "TestAlias" is provided to PHP-DI for the definition name, and "TestAlias" is the class that's initiated, "Test" is the only name that can retrieve the proper class from PHP-DI.

So PHP-DI must be manually getting the actual class name at some point, yes?

mnapoli commented 7 years ago

Closing this issue for now as it's very specific and unclear what would be the correct course of action. Let's reopen later if we have something new on the subject.

deftbrain commented 5 years ago

If it might help somebody ... I just ran into the following issue: the Twig classes have namespaced aliases (example). We use these aliases and trying to start to use PHP-DI in our projects. We added the following defenitions:

// The Twig definition
\Twig\Environment::class => function () {
    // Some adjustment ...
    return new \Twig\Environment(...);
},
\SomeFactory::class => function (\Twig\Environment $twig) {
    // Here I've got a Twig object created by PHP-DI automatically not using
    // the Twig factory defined above becasue the class alias '\Twig\Environment'
    //  was resolved into the original 'Twig_Environment' there:
    // https://github.com/PHP-DI/PHP-DI/blob/b9a58e575543103cd2055e50f733f2d363fb64af/src/Invoker/FactoryParameterResolver.php#L56
    return new \SomeFactory($twig);
}

$someFactoryInstanceWithUnexpectedTwigObjectInjected = $container->get(\SomeFactory::class);
$expectedTwigObject = $container->get(\Twig\Environment::class);

The workaround is linking the original class name to the alias \Twig_Environment::class => DI\get(\Twig\Environment::class) but would it be better to use $reflectionParameter->getType() instead of $reflectionParameter->getClass()->name in resolvers such as FactoryParameterResolver to make container's behaviour more obvious?

mnapoli commented 5 years ago

Oh crap Twig is a pretty important project, that's too bad it behaves like that :/

would it be better to use $reflectionParameter->getType() instead of $reflectionParameter->getClass()->name in resolvers such as FactoryParameterResolver

That's interesting, could you explain what that would change because it's not obvious to me?

deftbrain commented 5 years ago

That's interesting, could you explain what that would change because it's not obvious to me?

For now, the FactoryParameterResolver uses the original class name of the dependency getting it via $parameterClass->name - 'Twig_Environment'. But if we replace $parameterClass->name with $parameter->getType() the container will use the FQN of the same class that was specified via type hinting in the signature of the factory method i.e. Twig\Environment. An abstract example showing the difference:

<?php

namespace Test;

class A {}
class_alias(A::class, B::class);

function test(B $param) {}

$reflectionParameter = new \ReflectionParameter('Test\test', 'param');

// Output: Test\A
echo $reflectionParameter->getClass()->name . PHP_EOL;

// Output: Test\B
echo $reflectionParameter->getType();
mnapoli commented 5 years ago

Awesome! That's what we want then :)

sunxyw commented 1 year ago

For those who want to easily make PHP-DI support class_alias, you can simply create a DefintionSource to do so.

class AliasDefinitionSource implements DefinitionSource
{
    public function getDefinition(string $name): ?Definition
    {
        if (!class_exists($name)) {
            return null;
        }
        $ref = (new \ReflectionClass($name))->getName();
        // If the reflection class name is different from the class name, it is an alias, and we can simply refer to the actual class
        return $ref === $name ? null : new Reference($ref);
    }

    public function getDefinitions(): array
    {
        return [];
    }
}

And add it to your builder:

$builder = new ContainerBuilder();
$builder->addDefinitions(
    new AliasDefinitionSource(),
    new DI\Definition\Source\DefinitionArray([]),
);
$container = $builder->build();

class A
{
    public function __construct(public string $name) {}
}

class_alias(A::class, 'C');

class B
{
    public function __construct(private C $c) {}

    public function getName(): string
    {
        return $this->c->name;
    }
}

$container->set(A::class, new A('test'));

// output: test
echo $container->call([B::class, 'getName']) . PHP_EOL;