Skip to content

Commit

Permalink
[FEATURE] Support autowired LoggerInterface injection
Browse files Browse the repository at this point in the history
Logger instances may be required to be available during object
construction, LoggerAwareInterface isn't an option in that case.
Therefore LoggerInterface as constructor argument is now
autowired (if the service is configured to use autowiring)
and instantiated with an object-specific logger.

We add a symfony compiler pass that prepares an inline factory which
fetches the appropriate Logger instance from the LogManager.
One of the TYPO3 core classes that require this is the Scheduler.

Scheduler used to contain a safety net which fetches a logger instance
from LogManager in case the logger is already needed during the
constructor invocation. In that case the logger was not available, as it
was injected via setter injection (setLogger) later
(per LoggerAwareInterface).

Releases: master
Resolves: #95044
Change-Id: I57539a731a305da6abc092d2383e2d2f4c1d985c
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/65449
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Nikita Hovratov <nikita.h@live.de>
Tested-by: Benjamin Franzke <bfr@qbus.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Nikita Hovratov <nikita.h@live.de>
Reviewed-by: Benjamin Franzke <bfr@qbus.de>
  • Loading branch information
bnf committed Sep 3, 2021
1 parent 9ccd931 commit 85917dd
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 11 deletions.
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\CMS\Core\DependencyInjection;

use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogManager;

/**
* Looks for constructor arguments that request LoggerInterface and registers a lookup to the LogManager
*/
class LoggerInterfacePass extends AbstractRecursivePass
{
/**
* @param mixed $value
* @param bool $isRoot
* @return mixed
*/
protected function processValue($value, $isRoot = false)
{
$value = parent::processValue($value, $isRoot);

if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
return $value;
}

$constructor = $reflectionClass->getConstructor();
if ($constructor === null) {
return $value;
}

$arguments = $value->getArguments();
foreach ($reflectionClass->getConstructor()->getParameters() as $index => $parameter) {
$name = '$' . $parameter->getName();

if (isset($arguments[$name]) || isset($arguments[$index])) {
continue;
}

if (!$parameter->hasType()) {
continue;
}

$type = $parameter->getType();
if (!($type instanceof \ReflectionNamedType && $type->getName() === LoggerInterface::class)) {
continue;
}

$logger = new Definition(Logger::class);
$logger->setFactory([new Reference(LogManager::class), 'getLogger']);
$logger->setArguments([$value->getClass()]);
$logger->setShared(false);

$value->setArgument($name, $logger);
}

return $value;
}
}
1 change: 1 addition & 0 deletions typo3/sysext/core/Configuration/Services.php
Expand Up @@ -19,6 +19,7 @@

$containerBuilder->addCompilerPass(new DependencyInjection\SingletonPass('typo3.singleton'));
$containerBuilder->addCompilerPass(new DependencyInjection\LoggerAwarePass('psr.logger_aware'));
$containerBuilder->addCompilerPass(new DependencyInjection\LoggerInterfacePass());
$containerBuilder->addCompilerPass(new DependencyInjection\MfaProviderPass('mfa.provider'));
$containerBuilder->addCompilerPass(new DependencyInjection\SoftReferenceParserPass('softreference.parser'));
$containerBuilder->addCompilerPass(new DependencyInjection\ListenerProviderPass('event.listener'));
Expand Down
@@ -0,0 +1,40 @@
.. include:: ../../Includes.txt

=============================================================
Feature: #95044 - Support autowired LoggerInterface injection
=============================================================

See :issue:`95044`

Description
===========

Logger instances may be required to be available during object
construction, LoggerAwareInterface isn't an option in that case.
Therefore :php:`Psr\Log\LoggerInterface` as constructor argument
is now autowired (if the service is configured to use autowiring)
and instantiated with an object-specific logger.


Impact
======

Services are no longer required to use
:php:`Psr\Log\LoggerAwareInterface` and :php:`Psr\Log\LoggerAwaireTrait`,
but can add a constructor argument :php:`Psr\Log\LoggerInterface` instead.

Example:

.. code-block:: php
use Psr\Log\LoggerInterface;
class MyClass {
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
}
.. index:: PHP-API, ext:core
15 changes: 4 additions & 11 deletions typo3/sysext/scheduler/Classes/Scheduler.php
Expand Up @@ -16,14 +16,11 @@
namespace TYPO3\CMS\Scheduler;

use Doctrine\DBAL\Exception as DBALException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
Expand All @@ -32,9 +29,9 @@
/**
* TYPO3 Scheduler. This class handles scheduling and execution of tasks.
*/
class Scheduler implements SingletonInterface, LoggerAwareInterface
class Scheduler implements SingletonInterface
{
use LoggerAwareTrait;
protected LoggerInterface $logger;

/**
* @var array $extConf Settings from the extension manager
Expand All @@ -44,8 +41,9 @@ class Scheduler implements SingletonInterface, LoggerAwareInterface
/**
* Constructor, makes sure all derived client classes are included
*/
public function __construct()
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
// Get configuration from the extension manager
$this->extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('scheduler');
if (empty($this->extConf['maxLifetime'])) {
Expand Down Expand Up @@ -479,11 +477,6 @@ public function isValidTaskObject($task)
*/
public function log($message, $status = 0, $code = '')
{
// this method could be called from the constructor (via "cleanExecutionArrays") and no logger is instantiated
// by then, that's why check if the logger is available
if (!($this->logger instanceof LoggerInterface)) {
$this->setLogger(GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__));
}
$messageTemplate = '[scheduler]: {code} - {original_message}';
// @todo Replace these magic numbers with constants or enums.
switch ((int)$status) {
Expand Down

0 comments on commit 85917dd

Please sign in to comment.