0

I created a logger service that uses a config entity for configuration: https://drupal.org/project/dblog_persistent

Unfortunately, it appears that this module is not installable on a new site, because Drupal attempts to instantiate the logger service before actually installing the config entity type it depends on - the result is a PluginNotFoundException.

There has to be some way to prevent services from being instantiated until the module is installed, or not?

This is the class and constructor definition:

<?php

namespace Drupal\dblog_persistent\Logger;

use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LogMessageParserInterface;
use Drupal\dblog\Logger\DbLog;
use Drupal\dblog_persistent\DbLogPersistentStorageInterface;

/**
 * Class DbLogPersistent
 *
 * @package Drupal\dblog_persistent\Logger
 */
class DbLogPersistent extends DbLog {

  /**
   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
   */
  protected $channelStorage;

  /**
   * @var \Drupal\dblog_persistent\DbLogPersistentStorageInterface
   */
  private $storage;

  /**
   * @var \Drupal\dblog_persistent\Entity\ChannelInterface[]
   */
  private $channels;

  public function __construct(Connection $connection,
                              LogMessageParserInterface $parser,
                              EntityTypeManagerInterface $manager,
                              DbLogPersistentStorageInterface $storage) {
    parent::__construct($connection, $parser);
    // The following causes an error on installation:
    $this->channelStorage = $manager->getStorage('dblog_persistent_channel');
    $this->storage = $storage;
  }

And the service definition:

services:
  dblog_persistent.storage:
    class: Drupal\dblog_persistent\DbLogPersistentStorage
    arguments:
      - '@database'
  logger.dblog_persistent:
    class: Drupal\dblog_persistent\Logger\DbLogPersistent
    arguments:
      - '@database'
      - '@logger.log_message_parser'
      - '@entity_type.manager'
      - '@dblog_persistent.storage'
    tags:
      - { name: logger }

As a hack, I could surround every function in the logger with ifs that ensure it doesn't run unless installation is complete, but that's pretty dirty. Is there at least some kind of null entity storage that the constructor could fall back on, to minimize the amount of defensive programming?

1

I've revised my approach here several times, but so far the cleanest solution in my opinion is:

Store the entity_type.manager service, not the storage. When the storage is used, place the whole chain $this->manager->getStorage()->loadMultiple() into a try/catch.

This avoids having to handle the special case in two places: Once in the constructor (when acquiring the storage might fail), and again in the loading function (when the storage might not exist, because it failed in the constructor).

<?php

namespace Drupal\dblog_persistent\Logger;

use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LogMessageParserInterface;
use Drupal\dblog\Logger\DbLog;
use Drupal\dblog_persistent\DbLogPersistentStorageInterface;

/**
 * Class DbLogPersistent
 *
 * @package Drupal\dblog_persistent\Logger
 */
class DbLogPersistent extends DbLog {

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $manager;

  /**
   * @var \Drupal\dblog_persistent\DbLogPersistentStorageInterface
   */
  protected $storage;

  /**
   * @var \Drupal\dblog_persistent\Entity\ChannelInterface[]
   */
  private $channels;

  public function __construct(Connection $connection,
                              LogMessageParserInterface $parser,
                              EntityTypeManagerInterface $manager,
                              DbLogPersistentStorageInterface $storage) {
    parent::__construct($connection, $parser);
    $this->manager = $manager;
    $this->storage = $storage;
  }

  protected function getChannels() {
    if ($this->channels === NULL) {
      try {
        $this->channels = $this->manager
          ->getStorage('dblog_persistent_channel')
          ->loadMultiple();
      }
      catch (PluginNotFoundException $e) {
        $this->channels = [];
      }
    }
    return $this->channels;
  }
}
4
  • ->getStorage() throws multiple exception types, your call should be catch (PluginNotFoundException | InvalidPluginDefinitionException $e) note this is PHP 7.1+ – Ryan Hartman Apr 2 '20 at 18:27
  • @RyanHartman This isn't quite correct; InvalidPluginDefinitionException can only be thrown by this method if the entity type definition doesn't have a storage handler class, which config entity types have hardcoded (api.drupal.org/api/drupal/…). Such an exception would imply a major regression bug somewhere, which we would definitely not want to silently catch and ignore at this point. :) – Christoph Burschka Apr 3 '20 at 7:49
  • That's a good point. So how can I get my IDE to stop yelling at me about that exception. I have been wondering why it's always popping up. In my instance though it's not always config it's anything using getStorage(). – Ryan Hartman Apr 6 '20 at 5:12
  • 1
    @RyanHartman that's a good question which mostly depends on best practices for exceptions you know "can't" happen in practice (see stackoverflow.com/questions/15587530/…); maybe I'd add a @throws annotation for it? It could also be caught and rethrown as a runtimeexception; the only incorrect behavior imho would be to catch and swallow it silently. – Christoph Burschka Apr 6 '20 at 9:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.