Upgrade v3 to v4: container->get() by class or string

Hi,

I’m migrating my code from v3 to v4.
I’ve setup a new DI\Container in my code.

And use the class name as the key as it’s suggested :

/**
 * 'clientInputValidator' (old name)
 */
ClientInputValidator::class => function (ContainerInterface $c)
{
  return new ClientInputValidator($c->get(LoggerInterface::class));
},

and thus, I need to update my code from

$inputValidator = $this->clientInputValidator

to
$inputValidator = $this->get(ClientInputValidator::class)

but this add the need to add in my php files
use \RedCrossQuest\Service\ClientInputValidator;

everywhere i need to use the service, while I don’t see the added value and which makes a bit harder the conversion to v4
I don’t have completion, can’t set a type to inputValidator.
I don’t see the advantage of using this way of naming the services in my container.

Also, I can’t have two instance of a same class/interface in my container with this method. (since the class/interface is the key and both have the same class/interface)

Any thought on this ?

Hi @paquerette

I need to update my code from
$inputValidator = $this->clientInputValidator
to $inputValidator = $this->get(ClientInputValidator::class)

This is not true, it’s the opposite. Please don’t refactor “back” to the service locator anti-pattern.

Instead declare all class dependencies in the constructor and let the dependency injection container inject it for you: Autowiring

And you even don’t need to add definitions for ALL services classes. You can remove this definition if you make use of constructor injection: ClientInputValidator::class => function (ContainerInterface $c)

Pseudo example:

final class ClientInputValidator
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function validate(array $data): ValidationResult
    {
         // ...
    }
}

Ok, that works with the ClientInputValidator which is a class.

but my routes are just callback functions with no constructor.

So I should refactor my code to use a class instead of a callback function?
(that’s quite some work)

I’m making some implementation test following what’s done here :

And it looks like it’s the correct way to go, thanks for the hint.

Any hint on how to specify my settings array to the constructor of my class ?

Let’s do it! :slight_smile:

but my routes are just callback functions with no constructor.

Closures (functions) as routing handlers are quite “expensive” because PHP has to create all closures for each request. The use of class names is more lightweight, faster and scales better for larger applications.

And it looks like it’s the correct way to go, thanks for the hint.

:+1:

1 Like

I used the following to inject the application settings :

composer require doctrine/annotations

  /**
   * @Inject("settings")
   * @var array settings
   */
  private $settings;

And I also needed to add

$app->addBodyParsingMiddleware();

Otherwise the body/parsedbody was null… which I think is a bug for getBody().

Any hint on how to specify my settings array to the constructor of my class ?

I would recommend using constructor injection to inject the configuration. You could use a generic or a class specific Configuration class for this purpose.

* @Inject("settings")

This looks like “black magic” to me (and the IDE and phpstan) :slight_smile:

1 Like

I’ve succeeded in the migration from v3 to v4.
That’s quite some work.

i’ve described the steps here:

Using callable classes really help to have more clean code, more functionality, less redundant code.

Thanks for the help !