Completed
Push — 8.x-1.x ( 5d8f74...bc9e07 )
by Frédéric G.
28s queued 11s
created

QaCheckManager::initInternalFunctions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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