Passed
Pull Request — 8.x-1.x (#11)
by Frédéric G.
01:15
created

MissingDependencies::stripFramework()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 21
rs 9.2222
cc 6
nc 10
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Drupal\qa\Plugin\QaCheck\System;
6
7
use Drupal\Core\DrupalKernel;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\DrupalKernel was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Drupal\Core\DrupalKernelInterface;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\DrupalKernelInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Drupal\Core\Extension\ModuleExtensionList;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Extension\ModuleExtensionList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Drupal\Core\Extension\ThemeExtensionList;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Extension\ThemeExtensionList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use Drupal\qa\Pass;
12
use Drupal\qa\Plugin\QaCheckBase;
13
use Drupal\qa\Plugin\QaCheckInterface;
14
use Drupal\qa\Result;
15
use ReflectionFunction;
16
use Symfony\Component\DependencyInjection\ContainerInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Depend...tion\ContainerInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
18
/**
19
 * MissingDependencies identifies undeclared dependencies in module code.
20
 *
21
 * @QaCheck(
22
 *   id = "system.missing_dependencies",
23
 *   label=@Translation("System: missing dependencies"),
24
 *   details=@Translation("Custom code is prone to using other custom code
25
 *   without declaring a dependency on it, making the code brittle.")
26
 * )
27
 */
28
class MissingDependencies extends QaCheckBase implements QaCheckInterface {
29
30
  /**
31
   * The element_list.module service.
32
   *
33
   * @var \Drupal\Core\Extension\ModuleExtensionList
34
   */
35
  protected $elm;
36
37
  /**
38
   * The extension_list.theme service.
39
   *
40
   * @var \Drupal\Core\Extension\ThemeExtensionList
41
   */
42
  protected $elt;
43
44
  /**
45
   * The user-space functions visible from code.
46
   *
47
   * @var array
48
   */
49
  protected $functions;
50
51
  /**
52
   * The kernel base root, usually also available as base_path() and $GLOBALS['app_root'].
53
   *
54
   * @var string
55
   */
56
  protected $root;
57
58
  /**
59
   * SystemUnusedExtensions constructor.
60
   *
61
   * @param array $configuration
62
   *   The plugin configuration.
63
   * @param string $id
64
   *   The plugin ID.
65
   * @param array $definition
66
   *   The plugin definition.
67
   * @param \Drupal\Core\Extension\ModuleExtensionList $elm
68
   *   The extension_list.module service.
69
   * @param \Drupal\Core\Extension\ThemeExtensionList $elt
70
   *   The extension_list.theme service.
71
   * @param \Drupal\Core\DrupalKernelInterface $kernel
72
   *   The kernel service.
73
   */
74
  public function __construct(
75
    array $configuration,
76
    string $id,
77
    array $definition,
78
    ModuleExtensionList $elm,
79
    ThemeExtensionList $elt,
80
    DrupalKernelInterface $kernel
81
  ) {
82
    parent::__construct($configuration, $id, $definition);
83
    $this->elm = $elm;
84
    $this->elt = $elt;
85
    $this->root = realpath($kernel->getAppRoot());
86
87
    $functions = get_defined_functions(TRUE)['user'];
88
    sort($functions);
89
    $this->functions = $functions;
90
  }
91
92
  /**
93
   * {@inheritdoc}
94
   */
95
  public static function create(
96
    ContainerInterface $container,
97
    array $configuration,
98
    $id,
99
    $definition
100
  ) {
101
    $kernel = $container->get('kernel');
102
    $elm = $container->get('extension.list.module');
103
    $elt = $container->get('extension.list.theme');
104
    return new static($configuration, $id, $definition, $elm, $elt, $kernel);
105
  }
106
107
  /**
108
   * Identify code loaded from outside the web root and vendor directory.
109
   *
110
   * This is not necessarily an issue, but warrants a manual verification.
111
   *
112
   * @return \Drupal\qa\Result
113
   *
114
   * @throws \ReflectionException
115
   */
116
  protected function checkExternal(): Result {
117
    // TODO support alternate layouts.
118
    $vendor = realpath($this->root . "/../vendor");
119
    $external = [];
120
    foreach ($this->functions as $func) {
121
      // TODO handle exceptions.
122
      $rf = new ReflectionFunction($func);
123
      $file = realpath($rf->getFileName());
124
      // TODO improve resilience, probably at the cost of computing time.
125
      if (strpos($file, $this->root) === FALSE && strpos($file, $vendor) === FALSE) {
126
        $external[$func] = $file;
127
        unset($this->functions[$func]);
128
      }
129
      $data = [
130
        'root' => $this->root,
131
        'vendor' => $vendor,
132
        'external' => $external,
133
      ];
134
    }
135
    $res = new Result('external', empty($external), $data);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data seems to be defined by a foreach iteration on line 120. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
136
    return $res;
137
138
  }
139
140
  protected function stripFramework(): void {
141
    // TODO support alternate layouts.
142
    $core = [
143
      'core/includes',
144
      'core/lib',
145
      '../vendor',
146
    ];
147
    foreach ($core as &$dir) {
148
      $dir = realpath($this->root . "/$dir");
149
    }
150
    foreach ($this->functions as $func) {
151
      // TODO handle exceptions.
152
      $rf = new ReflectionFunction($func);
153
154
      $file = realpath($rf->getFileName());
155
      foreach ($core as $dir) {
156
        if (strpos($file, $dir) === 0) {
157
          $key = array_search($func, $this->functions);
158
          if ($key >= 0) {
159
            unset($this->functions[$key]);
160
            continue 2;
161
          }
162
        }
163
      }
164
    }
165
  }
166
167
  protected function checkModules(): Result {
168
    $modulesByPaths = [];
169
    $internalFuncs = count($this->functions);
170
    $this->stripFramework();
171
    $coreFuncs = $internalFuncs - count($this->functions);
172
173
    foreach ($this->elm->getAllAvailableInfo() as $ext => $info) {
174
      $modulesByPaths[$this->elm->getPath($ext)] = $ext;
175
    }
176
177
    $rf = new ReflectionFunction('base_path');
178
    $file = $rf->getFileName();
0 ignored issues
show
Unused Code introduced by
The assignment to $file is dead and can be removed.
Loading history...
179
    foreach ($this->functions as $func) {
180
      $rf = new ReflectionFunction($func);
181
      $file = realpath($rf->getFileName());
182
      $data = [
183
      ];
184
    }
185
186
    $data = [
187
      'internal_functions' => $internalFuncs,
188
      'core_functions' => $coreFuncs,
189
    ];
190
    $res = new Result('modules', FALSE, $data);
191
    return $res;
192
  }
193
194
  public function run(): Pass {
195
    $pass = parent::run();
196
    // $pass->record($this->checkExternal());
197
    $pass->life->modify();
198
    $pass->record($this->checkModules());
199
    $pass->life->end();
200
    return $pass;
201
  }
202
}
203