QaCheckManager   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 14
eloc 52
c 2
b 0
f 1
dl 0
loc 189
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A createInstance() 0 4 1
A getPluginsByPackage() 0 8 2
A getPackageId() 0 6 2
A getVendorDir() 0 3 1
A initInternalFunctions() 0 15 3
A isInternal() 0 23 4
1
<?php
2
3
namespace Drupal\qa\Plugin;
4
5
use Drupal\Component\Discovery\DiscoveryException;
6
use Drupal\Core\DrupalKernelInterface;
7
use Drupal\Core\Extension\Extension;
8
use Drupal\Core\Extension\ModuleExtensionList;
9
use Drupal\Core\Plugin\DefaultPluginManager;
10
use Drupal\Core\Cache\CacheBackendInterface;
11
use Drupal\Core\Extension\ModuleHandlerInterface;
12
use Drupal\qa\Annotation\QaCheck;
13
use ReflectionFunction;
14
use Traversable;
15
16
/**
17
 * Provides the QA Check plugin manager.
18
 */
19
class QaCheckManager extends DefaultPluginManager {
20
21
  /**
22
   * The list of internal functions. Not loaded by default.
23
   *
24
   * @var array
25
   *
26
   * @see \Drupal\qa\Plugin\QaCheckManager::initInternalFunction()
27
   */
28
  public $internalFunctions;
29
30
  /**
31
   * The kernel base root, aka as base_path() and $GLOBALS['app_root'].
32
   *
33
   * @var string
34
   */
35
  public $root;
36
37
  /**
38
   * The vendor dir.
39
   *
40
   * @var string
41
   */
42
  protected $vendor;
43
44
  /**
45
   * The extension.list.module service.
46
   *
47
   * @var \Drupal\Core\Extension\ModuleExtensionList
48
   */
49
  protected $elm;
50
51
  /**
52
   * Constructs a new QaCheckManager object.
53
   *
54
   * @param \Traversable $namespaces
55
   *   An object that implements \Traversable which contains the root paths
56
   *   keyed by the corresponding namespace to look for plugin implementations.
57
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
58
   *   Cache backend instance to use.
59
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
60
   *   The module handler to invoke the alter hook with.
61
   * @param \Drupal\Core\DrupalKernelInterface $kernel
62
   *   The kernel service.
63
   * @param \Drupal\Core\Extension\ModuleExtensionList $elm
64
   *   The extension.list.module service.
65
   */
66
  public function __construct(
67
    Traversable $namespaces,
68
    CacheBackendInterface $cache_backend,
69
    ModuleHandlerInterface $module_handler,
70
    DrupalKernelInterface $kernel,
71
    ModuleExtensionList $elm
72
  ) {
73
    parent::__construct('Plugin/QaCheck', $namespaces, $module_handler,
74
      QaCheckInterface::class, QaCheck::class);
75
    $this->elm = $elm;
76
    $this->root = realpath($kernel->getAppRoot());
77
    $this->vendor = self::getVendorDir();
78
79
    $this->alterInfo('qa_check_info');
80
    $this->setCacheBackend($cache_backend, 'qa_check_plugins');
81
  }
82
83
  /**
84
   * {@inheritdoc}
85
   *
86
   * @param string $plugin_id
87
   *   The plugin id.
88
   * @param array $configuration
89
   *   The plugin configuration.
90
   *
91
   * @return \Drupal\qa\Plugin\QaCheckInterface
92
   *   Plugins implement this interface instead of being plain objects.
93
   *
94
   * @throws \Drupal\Component\Plugin\Exception\PluginException
95
   */
96
  public function createInstance($plugin_id, array $configuration = []) {
97
    /** @var \Drupal\qa\Plugin\QaCheckInterface $res */
98
    $res = parent::createInstance($plugin_id, $configuration);
99
    return $res;
100
  }
101
102
  /**
103
   * Extract the package ID from a QaCheck plugin ID.
104
   *
105
   * @param string $pluginId
106
   *   The QaCheck plugin ID.
107
   *
108
   * @return string
109
   *   The package ID.
110
   *
111
   * @throws \Drupal\Component\Discovery\DiscoveryException
112
   */
113
  public static function getPackageId(string $pluginId): string {
114
    $id = strtok($pluginId, '.');
115
    if ($id === FALSE) {
116
      throw new DiscoveryException("Ill-formed QaCheck plugin ID: ${pluginId}.");
117
    }
118
    return $id;
119
  }
120
121
  /**
122
   * Get the path to the vendor directory.
123
   *
124
   * Drupal projects use various layouts, so the position of the vendor
125
   * directory relative to the app_root is not fixed.
126
   *
127
   * @return string
128
   *   The absolute path to the vendor directory.
129
   */
130
  public static function getVendorDir(): string {
131
    $rf = new ReflectionFunction('composer\autoload\includefile');
132
    return dirname(dirname($rf->getFileName()));
133
  }
134
135
  /**
136
   * Return the check plugin definitions, indexed by their check package ID.
137
   *
138
   * @return array
139
   *   The definitions.
140
   */
141
  public function getPluginsByPackage(): array {
142
    $defs = $this->getDefinitions();
143
    $packages = [];
144
    foreach ($defs as $id => $def) {
145
      $packageId = self::getPackageId($id);
146
      $packages[$packageId][] = $def;
147
    }
148
    return $packages;
149
  }
150
151
  /**
152
   * Initialize the internal functions list.
153
   *
154
   * Internal functions are those provided by Drupal core except its modules,
155
   * and by vendor dependencies.
156
   *
157
   * @throws \ReflectionException
158
   */
159
  public function initInternalFunctions(): void {
160
    $all = get_defined_functions();
161
    $internal = $all['internal'];
162
    $user = [];
163
    foreach ($all['user'] as $func) {
164
      $rf = new \ReflectionFunction($func);
165
      $path = $rf->getFileName();
166
      $isInternal = $this->isInternal($path);
167
      if ($isInternal) {
168
        $user[] = $func;
169
      }
170
    }
171
    $merged = array_merge($internal, $user);
172
    sort($merged);
173
    $this->internalFunctions = array_flip($merged);
174
  }
175
176
  /**
177
   * Is the file located in the always loaded directories of Drupal ?
178
   *
179
   * @param string $file
180
   *   An absolute path.
181
   *
182
   * @return bool
183
   *   Is it ?
184
   */
185
  public function isInternal($file) {
186
    $internal = [
187
      $this->root . "/core/lib",
188
      $this->root . "/core/includes",
189
      $this->vendor,
190
    ];
191
192
    $list = $this->elm->getList();
193
    $required = [];
194
    array_walk($list, function (Extension $ext) use (&$required) {
195
      // XXX Undocumented API field.
196
      if (!empty($ext->info['required'])) {
0 ignored issues
show
Bug introduced by
The property info does not seem to exist on Drupal\Core\Extension\Extension.
Loading history...
197
        $required[] = $this->root . '/' . $ext->getPath();
198
      }
199
    });
200
    $internal = array_merge($internal, $required);
201
202
    foreach ($internal as $root) {
203
      if (strpos($file, $root) !== FALSE) {
204
        return TRUE;
205
      }
206
    }
207
    return FALSE;
208
  }
209
210
}
211