Problem/Motivation

As the module evolves, we can expect new services to be added, service dependencies to change, etc.

The "classic" way to inject dependencies into a class is to use arguments to the constructor (i.e.: public function __construct($dependency1) { $this->dependency1 = $dependency1; }. But if you want to add a new dependency, or delete a dependency, then you have to change the function signature of the constructor (which is often public).

Technically, Drupal core's backwards-compatibility policy considers service object constructors to be @internal; however, in practice, changing the signature of service object constructors can occasionally cause fatal errors after a module upgrade, which isn't ideal.

Fortunately, PHP provides an alternate way to inject dependencies that doesn't involve changing function signatures, and thus avoids fatal errors on module upgrades...

class Foo {
  /* Omit the constructor, i.e.: to inherit it from its base class */
  public static function create(ContainerInterface $container) {
    $instance = new static():
    $instance->dependency1 = $container->get('dependency1');
    return $instance;
  }
}

... to distinguish these from the classic-style of dependency injection, this issue will refer to this as the "new-style of dependency injection".

At time-of-writing, the 2.x branch of structure_sync has 4 instances of classic-style dependency injection:

  1. \Drupal\structure_sync\Controller\MenuLinksController::__construct()
  2. \Drupal\structure_sync\Form\BlocksSyncForm::__construct()
  3. \Drupal\structure_sync\Form\MenuSyncForm::__construct()
  4. \Drupal\structure_sync\Form\TaxonomiesSyncForm::__construct()

There is talk of creating a 3.0.x branch in #3517435: Drop support for D8, D9, D10.0-10.3.

We can't change constructor signatures in the 2.x branch; but we can prepare to change the signatures in the 3.0.x branch by deprecating the use of classic-style dependency injection.

Proposed resolution

For the 4 services listed above...

  1. start using the new-style dependency injection for those services; and;
  2. stop doing anything with the parameters passed into those constructors, and mark those parameters as deprecated

... this will allow us to remove those constructors in a 3.0.x branch.

Remaining tasks

  1. Write a merge request to start using new-style dependency injection and deprecate classic-style dependency injection in the 2.x branch
  2. Review and feedback - by @spiderman in #4
  3. RTBC and feedback - skipped; maintainers worked on this issue
  4. Commit
  5. File a follow-up ticket for the 2.x branch to deprecate the constructors in BlocksController, TaxonomiesController, and (soon to be) MenuLinksController - by being consistent about how we refer to $this->config, and $this->entityTypeManager, we could get rid of the rest of the code in those controllers - #3519479: Deprecate the constructors in BlocksController, TaxonomiesController, and MenuLinksController
  6. File a follow-up ticket for the 3.0.x branch - #3519483: Remove constructors and extra constructor parameters from Controllers and Form classes
  7. Release a 2.0.9 version - Deferred for #3519479: Deprecate the constructors in BlocksController, TaxonomiesController, and MenuLinksController

User interface changes

None.

API changes

None.

Data model changes

None.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mparker17 created an issue. See original summary.

mparker17’s picture

Issue summary: View changes
Status: Active » Needs review

Reviews welcome!

spiderman’s picture

Status: Needs review » Reviewed & tested by the community

I've reviewed this issue in some detail, looking carefully at the code changes, and also running tests locally on my branch. This was partly an exercise to re-familiarize myself with this project codebase, but also validated that the MR @mparker17 put together is solid.

Out of curiosity, I went looking for documentation about the "new style" dependency injection technique we're moving toward here. It took a bit of digging, but I believe what we're talking about is a move from "constructor injection" to "setter injection": https://symfony.com/doc/current/service_container/calls.html.

The reasoning for this makes sense to me, and I believe the create() pattern ensures the downside of this approach (namely, that you can't guarantee your dependencies are present upon being constructed) are mitigated, although I couldn't find much to back this up (is there something in Symfony or Drupal's Kernel or some such that ensures create() is always called?)

At any rate, this all looks great to me, and I'm marking as RTBC. I will file the follow-up issues now, and I think it's safe to commit this change anytime :)

mparker17’s picture

Issue summary: View changes

I was about to merge this and noticed a new phpcs message...

The deprecation-version 'structure_sync:2.x' does not match the lower-case machine-name standard: drupal:n.n.n or project:n.x-n.n or project:n.x-n.n-label[n] or project:n.n.n or project:n.n.n-label[n] (Drupal.Semantics.FunctionTriggerError.TriggerErrorVersion)

... so I fixed it in another commit (changed "structure_sync:2.x" to "structure_sync:2.0.9", i.e.: the next version); and verified it made the message go away, and there were no new errors, so I'm going to merge this.

mparker17’s picture

Issue summary: View changes
Status: Reviewed & tested by the community » Fixed

Crediting and updating issue summary

  • mparker17 committed 24b1a436 on 2.x
    Issue #3518008 by mparker17, spiderman: Deprecate passing extra...
dydave’s picture

Great job @mparker17 and @spiderman! 🥳

Thanks a lot for the great documentation in this ticket and the very clean approach taken in the merge request to deprecate previous API signatures. It's definitely a great example and source of inspiration to me in terms of steps you have taken to drop support for certain versions 👍

Glad to see the project moving forward!
Thanks again for all the great work to keep the module well maintained! 🙏

mparker17’s picture

Issue summary: View changes

Thanks @dydave for the kind words! :)

Thanks @spiderman for filing a follow-up, #3519479: Deprecate the constructors in BlocksController, TaxonomiesController, and MenuLinksController

spiderman’s picture

Issue summary: View changes

Thanks @mparker17! I've also filed the other follow-up you mentioned, #3519483: Remove constructors and extra constructor parameters from Controllers and Form classes and ticked that off here.

I'm wondering if it makes sense to get #3519479: Deprecate the constructors in BlocksController, TaxonomiesController, and MenuLinksController in before cutting a new release, or if you prefer to cut 2.0.9 here, and then the further deprecations in a 2.0.10 release?

mparker17’s picture

Issue summary: View changes

@spiderman, thank you very much!

I'm wondering if it makes sense to get #3519479: Deprecate the constructors in BlocksController, TaxonomiesController, and MenuLinksController in before cutting a new release, or if you prefer to cut 2.0.9 here, and then the further deprecations in a 2.0.10 release?

I think it's worth it to get #3519479 in before cutting a new release.

spiderman’s picture

Issue summary: View changes

Ok great, in that case, I'll strike that task from the list and put it on #3519479: Deprecate the constructors in BlocksController, TaxonomiesController, and MenuLinksController instead.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.