Skip to content
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

Breaking change in 4.11: Injector can't handle anonymous classes #10533

Closed
1 task
n8-dev opened this issue Oct 9, 2022 · 4 comments
Closed
1 task

Breaking change in 4.11: Injector can't handle anonymous classes #10533

n8-dev opened this issue Oct 9, 2022 · 4 comments

Comments

@n8-dev
Copy link

n8-dev commented Oct 9, 2022

Affected Version

4.11

Description

In our unit tests we use the Injector process to mock specific function returns.

With the change of 4.10 to 4.11 something has changed with the underlying Injector process, possibly the factory that gets used.

This could effect not just test cases.

When they used to run, now the follow error is returned

TypeError: class_exists(): Argument #1 ($class) must be of type string, class@anonymous given

Steps to Reproduce

Code sample:

A phpunit test

    public function testUserDoesNotExist(): void
    {
        Injector::inst()->load([
            foobarMiddleware::class => [
                'class' => new class {
                    // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter
                    public function getMemberIfFooBarr(string $accessToken): ?DataObject
                    {
                        return null;
                    }
                },
            ],
        ]);

        $request = $this->createRequest('POST', []);
        $controller = ThingController::create();
        $response = $controller->handleCreateThing($request);

        $this->assertSame(400, $response->getStatusCode());
        $this->assertSame('User does not exist', $response->getUserMessage());
    }
->handleCreateThing()

Simply triggers a
foobarMiddleware::create();

That class being

final class foobarMiddleware extends (some things that ultimately extend from HTTPMiddleware)
{

    use Injectable;

  ...
  // none of this code or the other extended files before HTTPMiddleware touch the Injectable functions
}

Acceptance criteria

  • You can use anonymous classes with Injector.

PRs

@n8-dev n8-dev changed the title 4.11 Injector::inst()->load(), TypeError: class_exists(): Argument #1 ($class) must be of type string, class@anonymous given Injector::inst()->load(), TypeError: class_exists(): Argument #1 ($class) must be of type string, class@anonymous given Oct 9, 2022
@kinglozzer
Copy link
Member

Looks like this may have been caused by #10265. I’m not sure if injecting anonymous classes like this was ever officially supported (?) but if this is the only piece of code that’s preventing it working then I’d be in favour of re-adding support:

public function create($class, array $params = [])
{
if (!class_exists($class ?? '')) {
throw new InjectorNotFoundException("Class {$class} does not exist");
}

If that is indeed the cause, we can probably just return $class if $class is already an object right?

@GuySartorelli
Copy link
Member

If $class is already an object, it may not have the params set on it. There's an argument to be made that create() should always create a new instance. There's another argument to be made that it should just set the param values on the existing object.

I think we'd have to retain the previous behaviour though, which would have been creating a new instance most likely.
In CMS 5 we can reduce the scope of support for this to explicitly only allow instances of anonymous classes. Instances of concrete classes shouldn't be supported going forward.

@GuySartorelli GuySartorelli changed the title Injector::inst()->load(), TypeError: class_exists(): Argument #1 ($class) must be of type string, class@anonymous given Breaking change in 4.11: Injector can't handle anonymous classes Oct 11, 2022
@emteknetnz emteknetnz self-assigned this Oct 19, 2022
@emteknetnz
Copy link
Member

emteknetnz commented Oct 19, 2022

If $class is already an object, it may not have the params set on it.

It can if we just go if (is_object($class)) { $class = get_class($class); };

note: the php anonymous class syntax will instantiate a new instance, rather than assign the class to a variable.

I think we'd have to retain the previous behaviour though, which would have been creating a new instance most likely

Yes, previous behaviour created new instances - https://github.com/silverstripe/silverstripe-framework/pull/10265/files#diff-a6f8392a1ea245fb07612de639deda3040d399bc87258fba6210693f2739d4e8L28

In CMS 5 we can reduce the scope of support for this to explicitly only allow instances of anonymous classes. Instances of concrete classes shouldn't be supported going forward.

It don't think we need to since we can just InjectionCreator::create() will always create a new instance. If desired though we could see if there's a class@anonymous in $class if is_object($class), though if we're going to allow this level of flexibility I don't see a big issue of also allows concrete classes instances to also be used (don't know why you would though)

@michalkleiner
Copy link
Contributor

The PR fixing this regression has been merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants