Problem/Motivation

FormBase and ControllerBase both implement a logger() method.

We shouldn't repeat ourselves.

Also, relying on those base classes for a convenient logger channel leads to poor dependency injection.

Proposed resolution

Implement a LoggerChannelTrait so we can all use our local, correct, dependency injection implementation while benefitting from a handy convenience method: logger().

Remaining tasks

User interface changes

API changes

Data model changes

Comments

Mile23 created an issue. See original summary.

mile23’s picture

Issue summary: View changes
Issue tags: +Traits
StatusFileSize
new4.91 KB

Added LoggerChannelTrait, used it in ControllerBase and FormBase.

mile23’s picture

Status: Active » Needs review
mile23’s picture

Assigned: mile23 » Unassigned
mile23’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 2: 2729643_2.patch, failed testing.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new5.21 KB
new1.45 KB

ControllerBase had getLogger(). FormBase had logger(). getLogger() wins for being more descriptive, so LoggerChannelTrait::getLogger() exists.

FormBase::logger() now just calls LoggerChannelTrait::getLogger(), with a note in docs about how it's a compatibility shim.

If this trait is desired by maintainers, we can go through and change calls to logger() with getLogger() and remove FormBase::logger() in a subsequent patch.

Status: Needs review » Needs work

The last submitted patch, 7: 2729643_5.patch, failed testing.

neclimdul’s picture

+++ b/core/lib/Drupal/Core/Form/FormBase.php
@@ -193,17 +188,13 @@ private function container() {
+    $this->getLogger($channel);

Missing return.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new5.45 KB
new806 bytes

Ewps.

Added return. :-) Also added a @todo.

neclimdul’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Form/FormBase.php
@@ -192,9 +192,16 @@ private function container() {
+   * @todo Deprecate this method in favor of LoggerChannelTrait::getLogger().

Good call however I think @todo's need to come with issue links now. I would be OK with deprecating this now actually targeted at 9.x removal but if you want to open a discussion I would open the issue and link it.

dawehner’s picture

Great small patch!

+++ b/core/lib/Drupal/Core/Form/FormBase.php
@@ -193,17 +188,20 @@ private function container() {
-  protected function logger($channel) {
...
+  public function logger($channel) {

It is weird that logger needs to be public now

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new5.33 KB
new570 bytes

OK, I took out the @todo because I think the 'don't use this' is enough, and because this issue is really yak shaving for #2729625: Inject services into BookAdminEditForm and ultimately #2729597: [meta] Replace \Drupal with injected services where appropriate in core. We don't want to have to re-implement logger() every time we decide FormBase is bad. Which it is. :-) #2674508: Improve docs for how to properly add container injection into a class that extends FormBase

Nice catch on public vs. protected.

Addressed here.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

It all looks good to me. @dawehner also likes this patch. The problems from comment #11 and #12 are fixed.

catch’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/lib/Drupal/Core/Logger/LoggerChannelTrait.php
@@ -0,0 +1,55 @@
+      $this->loggerFactory = \Drupal::service('logger.factory');

The two implementations this replaces get the logger from the container directly. Can we also do that here, and just enforce that the classes that use it are container aware?

mile23’s picture

Assigned: Unassigned » mile23
Status: Needs review » Needs work

The two implementations this replaces get the logger from the container directly. Can we also do that here, and just enforce that the classes that use it are container aware?

Ab.
So.
Lutely.

I was following the example of the other service traits for consistency.

I would be more than happy to open follow-ups to modify the other traits in the same way. :-)

mile23’s picture

Assigned: mile23 » Unassigned
Status: Needs work » Needs review
StatusFileSize
new7.18 KB
new2.72 KB

Used assert() to enforce the existence of the service.

Since this was me learning assert(), there's also a test. :-)

Status: Needs review » Needs work

The last submitted patch, 17: 2729643_17.patch, failed testing.

mile23’s picture

OK, so that list of failed tests is a nice list of action items for #2733703: [plan] Service traits should require IoC injection which I just created.

Converting back to \Drupal for now, and adding a @todo about #2733703: [plan] Service traits should require IoC injection

The plan might be to use \Drupal and then switch over to requiring the injected service once the classes which use the trait have been converted to IoC.

Maintainers can of course have a differing idea. :-)

catch’s picture

OK so that was worth a try, but also it wasn't what I suggested:

The two implementations this replaces get the logger from the container directly. Can we also do that here, and just enforce that the classes that use it are container aware?

So we'd assume that classes using the trait are container aware, and access $this->container instead of using \Drupal.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new5.5 KB
new914 bytes

Yah, I didn't get what you meant, but the reason is that there's no $this->container, or at least there shouldn't be. :-)

The trait wraps a service that's already been injected, and if it's not there, \Drupal is the backup. The problem with that is the service dependency becomes a magic one.

So we might solve that by requiring a container, but then we lose the many benefits of inversion of control, mainly enabling SOLID in more places.

So if we want this trait to be strict about it's users having already injected the dependency, then we'd assert on the specific dependency, rather than the container. In this case, we don't care where the LoggerChannelFactoryInterface came from, only that we have one.

That means our trait only has one dependency: LoggerChannelFactoryInterface.

That's better than having one explicit but non-specific dependency (the container), plus a magic one.

Reworked the patch from #19 because I forgot to remove the assertion test.

mile23’s picture

StatusFileSize
new1.66 KB

Erp... Wrong interdiff.

catch’s picture

This should only be used in application-level code that's already container aware though - as the two examples converting to it are. For injected logger service you're probably writing a service yourself, which can just inject directly.

For me the explicit container dependency is better than the \Drupal + container dependency. But we might need a third opinion here.

mile23’s picture

This should only be used in application-level code that's already container aware though - as the two examples converting to it are.

You might want to research that.

There's no such thing as ContainerAwareInterface in Drupal, which is a good thing.

FormBase implements ContainerInjectionInterface, and doesn't store the container and doesn't give you a way to access the container.

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Form%21Fo...

ControllerBase gives you an accessor for \Drupal which a) makes no sense because it's marked private and why don't those other methods just call \Drupal? And b) is not a part of any interface.

So it's all magic voodoo. :-)

I am willing to do the work to untangle all this, as I have been doing.

But in order to start, I need a trait instead of re-implementing logger() all the time.

catch’s picture

grep -r "ContainerAwareInterface" *

It's from Symfony. Lots of our classes implement it.

But you're right, not these two.

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Controlle... is the offending method for anyone following along.

So yes that's weird, and this patch doesn't introduce any more \Drupal calls than we already have.

mile23’s picture

Yes indeedy ContainerAwareInterface: 21 files implement it. Then we have 22 files with 'use ContainerAwareTrait'. Take a look at EntityTypeManager::createHandlerInstance(), where we check for the existence of trait methods. That's the kind of code smell I'm talking about, and why we should do things like assert that the dependency is already discovered as per #17.

Anyway...

So what we have in patch #21 is similar to the other service traits, in that if the user didn't initialize the service (bad user, bad!), the trait will grab it from \Drupal. As the todo points out, this is a halfway measure and we might potentially replace that with an assertion or with something else entirely.

We also use the trait in two base classes, ControllerBase and FormBase, which seems like a good place to start. We re-implement FormBase::logger() to use the trait's getLogger().

Once this is in we can then re-roll #2729625: Inject services into BookAdminEditForm for the log service, and move forward on something like #2729597: [meta] Replace \Drupal with injected services where appropriate in core without re-implementing logger discovery in every class that uses it.

The patch in #21 contains a @todo pointing to this issue: #2733703: [plan] Service traits should require IoC injection which is where we really should have had the conversation starting in #20. :-)

Plus contrib can now use the logger trait. :-)

Anyone care to review?

jibran’s picture

Should we add tests for it?

+++ b/core/lib/Drupal/Core/Logger/LoggerChannelTrait.php
@@ -0,0 +1,60 @@
+   * @todo Require the use of injected services:
+   *   https://www.drupal.org/node/2733703

This is not applicable any more.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@jibran: The todo is just what needs to be done to fix the problem with the injected services. We are now using Drupal::service('something') and that needs to be replaced.

The patch is the same as the one from comment #13 with two small comments added about container injection and the todo. The problem that @catch has with the container injection are to be addressed in the followup (#2733703: [plan] Service traits should require IoC injection). So for me it is back to RTBC.

jibran’s picture

Status: Reviewed & tested by the community » Needs review

How about we should add a LoggerChannelInterface as well? Just like ContainerAwareTrait and ContainerAwareInterface and it would look like this.

interface LoggerChannelInterface {
    /**
     * Sets thethe logger channel factory..
     *
     * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface|null $logger_factory
     *     The logger channel factory service.
     */
    public function setLoggerFactory(LoggerChannelFactoryInterface $logger_factory = NULL);
}

Now every class which uses LoggerChannelTrait has to implement LoggerChannelInterface and we don't have to set logger in getter.

mile23’s picture

@jibran: It's a different thing. We want to emulate traits like StringTranslationTrait and LinkGeneratorTrait. These do not supply interfaces.

ContainerAwareTrait exists so you have a default way of implementing ContainerAwareInterface, so that you can store the container. We don't want to supply a default method for injecting specific dependencies (in this case, logger factory) for controllers or forms. We only want a way to avoid re-implementing boilerplate for *using* those dependencies.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@jibran: Mile23 has responded to your remark 20 days ago. And he explained why he did not did it the way you would like it. I think that you had enough time to respond. so I will put the status back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed f7b235c and pushed to 8.2.x. Thanks!

diff --git a/core/lib/Drupal/Core/Logger/LoggerChannelTrait.php b/core/lib/Drupal/Core/Logger/LoggerChannelTrait.php
index bbde289..d3a8e85 100644
--- a/core/lib/Drupal/Core/Logger/LoggerChannelTrait.php
+++ b/core/lib/Drupal/Core/Logger/LoggerChannelTrait.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Core\Logger;
 
-use Drupal\Core\Logger\LoggerChannelFactoryInterface;
-
 /**
  * Wrapper methods for the logger factory service.
  *

The trait is the same namespace so the use is not necessary.

mile23’s picture

Status: Fixed » Reviewed & tested by the community

Clicking on that cgit link... http://cgit.drupalcode.org/drupal/commit/?id=f7b235c

"Bad object id: f7b235c"

Not pushed yet?

  • alexpott committed eac023b on 8.2.x
    Issue #2729643 by Mile23, catch: Create LoggerChannelTrait
    
mile23’s picture

Status: Reviewed & tested by the community » Fixed

Awesome. Thanks. :-)

Status: Fixed » Closed (fixed)

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