Passed
Push — master ( 190ea0...184cd2 )
by Divine Niiquaye
11:55
created

HelperTrait   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Test Coverage

Coverage 7.23%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 75
c 6
b 0
f 0
dl 0
loc 248
ccs 6
cts 83
cp 0.0723
rs 9.1199
wmc 41

11 Methods

Rating   Name   Duplication   Size   Complexity  
A isRunningInConsole() 0 3 1
A isDebug() 0 3 1
A inVagrantEnvironment() 0 3 3
A mount() 0 3 1
A escape() 0 3 1
A loadExtensions() 0 8 2
A getConfigLoader() 0 20 4
A load() 0 15 4
A getModule() 0 7 2
C getLocation() 0 55 13
B loadModules() 0 34 9

How to fix   Complexity   

Complex Class

Complex classes like HelperTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HelperTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade\Traits;
19
20
use Composer\InstalledVersions;
21
use Laminas\Escaper\Escaper;
22
use Nette\Utils\FileSystem;
23
use Rade\DI\Exceptions\ContainerResolutionException;
24
use Rade\DI\Exceptions\ServiceCreationException;
25
use Rade\DI\Extensions\ConfigExtension;
26
use Rade\DI\Extensions\ExtensionBuilder;
27
use Symfony\Component\Config\Exception\LoaderLoadException;
28
use Symfony\Component\Config\Loader\LoaderResolverInterface;
29
30
trait HelperTrait
31
{
32
    private array $loadedExtensionPaths = [], $loadedModules = [], $moduleExtensions = [];
33
34
    public function isDebug(): bool
35
    {
36
        return $this->parameters['debug'];
37
    }
38
39
    /**
40
     * Determine if the application is running in the console.
41
     */
42
    public function isRunningInConsole(): bool
43
    {
44
        return \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true);
45
    }
46
47
    /**
48
     * Determine if the application is in vagrant environment.
49
     */
50
    public function inVagrantEnvironment(): bool
51
    {
52
        return ('/home/vagrant' === \getenv('HOME') || 'VAGRANT' === \getenv('VAGRANT')) && \is_dir('/dev/shm');
53
    }
54
55
    /**
56
     * The delegated config loaders instance.
57
     */
58
    public function getConfigLoader(): LoaderResolverInterface
59
    {
60
        $configLoader = $this->services['config.loader_resolver'] ?? $this->privates['config.builder.loader_resolver'] ?? null;
61
62
        if (null === $configLoader) {
63
            if (isset($this->definitions['config.builder.loader_resolver'])) {
64
                $configLoader = $this->definitions['config.builder.loader_resolver'];
65
                unset($this->definitions['config.builder.loader_resolver']);
66
67
                return $this->privates['config.builder.loader_resolver'] = $configLoader;
0 ignored issues
show
Bug Best Practice introduced by
The property privates does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
68
            }
69
70
            if ($this->has('config.loader_resolver')) {
0 ignored issues
show
Bug introduced by
It seems like has() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

70
            if ($this->/** @scrutinizer ignore-call */ has('config.loader_resolver')) {
Loading history...
71
                return $this->get('config.loader_resolver');
0 ignored issues
show
Bug introduced by
It seems like get() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

71
                return $this->/** @scrutinizer ignore-call */ get('config.loader_resolver');
Loading history...
72
            }
73
74
            throw new ContainerResolutionException(\sprintf('Did you forgot to register the "%s" class?', ConfigExtension::class));
75
        }
76
77
        return $configLoader;
78
    }
79
80
    /**
81
     * Context specific methods for use in secure output escaping.
82
     */
83 6
    public function escape(string $encoding = null): Escaper
84
    {
85 6
        return new Escaper($encoding);
86
    }
87
88
    /**
89
     * Mounts a service provider like controller taking in a parameter of application.
90
     *
91
     * @param callable(\Rade\DI\AbstractContainer) $controllers
92
     */
93
    public function mount(callable $controllers): void
94
    {
95
        $controllers($this);
96
    }
97
98
    /**
99
     * Loads a set of container extensions.
100
     *
101
     * Example for extensions:
102
     * [
103
     *    PhpExtension::class,
104
     *    CoreExtension::class => -1,
105
     *    [ProjectExtension::class, ['%project.dir%']],
106
     * ]
107
     *
108
     * @param array<int,mixed>    $extensions
109
     * @param array<string,mixed> $config     the default configuration for all extensions
110
     * @param string|null         $outputDir  Enable Generating ConfigBuilders to help create valid config
111
     */
112 1
    public function loadExtensions(array $extensions, array $configs = [], string $outputDir = null): void
113
    {
114 1
        $builder = new ExtensionBuilder($this, $configs);
0 ignored issues
show
Bug introduced by
$this of type Rade\Traits\HelperTrait is incompatible with the type Rade\DI\AbstractContainer expected by parameter $container of Rade\DI\Extensions\ExtensionBuilder::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

114
        $builder = new ExtensionBuilder(/** @scrutinizer ignore-type */ $this, $configs);
Loading history...
115
116 1
        if (null !== $outputDir) {
117
            $builder->setConfigBuilderGenerator($outputDir);
118
        }
119 1
        $builder->load($extensions + $this->moduleExtensions);
120
    }
121
122
    /**
123
     * Loads a set of modules from module directory.
124
     *
125
     * This method should be called before the loadExtensions method.
126
     */
127
    public function loadModules(string $moduleDir, string $prefix = null): void
128
    {
129
        // Get list modules available in application
130
        foreach (\scandir($moduleDir) as $directory) {
131
            if ('.' === $directory || '..' === $directory) {
132
                continue;
133
            }
134
135
            // Check if file parsed is a directory (module need to be a directory)
136
            if (!\is_dir($directoryPath = \rtrim($moduleDir, '\/') . '/' . $prefix . $directory)) {
137
                continue;
138
            }
139
140
            // Load module configuration file
141
            if (!\file_exists($configFile = $directoryPath . '/config.json')) {
142
                continue;
143
            }
144
145
            // Load module
146
            $moduleLoad = new \Rade\Module($directoryPath, \json_decode($configFile, true));
147
148
            if (!\array_key_exists($directory, $this->loadedExtensionPaths)) {
149
                $this->loadedExtensionPaths[$directory] = $directoryPath;
150
            }
151
152
            if (!$moduleLoad->isEnabled()) {
153
                continue;
154
            }
155
156
            if (!empty($extensions = $moduleLoad->getExtensions())) {
157
                $this->moduleExtensions = \array_merge($this->moduleExtensions, $extensions);
158
            }
159
160
            $this->loadedModules[$directory] = $moduleLoad;
161
        }
162
    }
163
164
    /**
165
     * Loads a resource.
166
     *
167
     * @param mixed $resource the resource can be anything supported by a config loader
168
     *
169
     * @throws \Exception If something went wrong
170
     *
171
     * @return mixed
172
     */
173
    public function load($resource, string $type = null)
174
    {
175
        if (\is_string($resource)) {
176
            $resource = $this->parameter($resource);
0 ignored issues
show
Bug introduced by
It seems like parameter() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

176
            /** @scrutinizer ignore-call */ 
177
            $resource = $this->parameter($resource);
Loading history...
177
178
            if ('@' === $resource[0]) {
179
                $resource = $this->getLocation($resource);
180
            }
181
        }
182
183
        if (false === $loader = $this->getConfigLoader()->resolve($resource, $type)) {
184
            throw new LoaderLoadException($resource, null, 0, null, $type);
185
        }
186
187
        return $loader->load($resource, $type);
188
    }
189
190
    /**
191
     * Returns the file path for a given service extension or class name resource.
192
     *
193
     * A Resource can be a file or a directory. The resource name must follow the following pattern:
194
     * "@CoreExtension/path/to/a/file.something"
195
     *
196
     * where CoreExtension is the name of the service extension or class,
197
     * and the remaining part is the relative path to a file or directory.
198
     *
199
     * We recommend using composer v2, as this method depends on it or use $baseDir parameter.
200
     *
201
     * @param string $name
202
     *
203
     * @throws \InvalidArgumentException    if the file cannot be found or the name is not valid
204
     * @throws \RuntimeException            if the name contains invalid/unsafe characters
205
     * @throws ContainerResolutionException if the service provider is not included in path
206
     *
207
     * @return string The absolute path of the resource
208
     */
209
    public function getLocation(string $path, string $baseDir = 'src')
210
    {
211
        if (\str_contains($path, '..')) {
212
            throw new \RuntimeException(\sprintf('File name "%s" contains invalid characters (..).', $path));
213
        }
214
215
        if ('@' === $path[0]) {
216
            [$bundleName, $path] = \explode('/', \substr($path, 1), 2);
217
218
            if (isset($this->loadedExtensionPaths[$bundleName])) {
219
                return $this->loadedExtensionPaths[$bundleName] . '/' . $path;
220
            }
221
222
            if (null !== $extension = $this->getExtension($bundleName)) {
0 ignored issues
show
Bug introduced by
It seems like getExtension() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

222
            if (null !== $extension = $this->/** @scrutinizer ignore-call */ getExtension($bundleName)) {
Loading history...
223
                $bundleName = \get_class($extension);
224
            }
225
226
            try {
227
                $ref = new \ReflectionClass($bundleName);
228
                $directory = \str_replace('\\', '/', \dirname($ref->getFileName()));
229
            } catch (\ReflectionException $e) {
230
                throw new ContainerResolutionException(\sprintf('Resource path is not supported for %s', $bundleName), 0, $e);
231
            }
232
233
            if ($pos = \strpos($directory, $baseDir)) {
234
                $directory = \substr($directory, 0, $pos + \strlen($baseDir));
235
236
                if (!\file_exists($directory)) {
237
                    $directory = \substr_replace($directory, '', $pos, 3);
238
                }
239
240
                return ($this->loadedExtensionPaths[$bundleName] = \substr($directory, 0, $pos + \strlen($baseDir))) . '/' . $path;
0 ignored issues
show
Bug introduced by
It seems like $directory can also be of type array; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

240
                return ($this->loadedExtensionPaths[$bundleName] = \substr(/** @scrutinizer ignore-type */ $directory, 0, $pos + \strlen($baseDir))) . '/' . $path;
Loading history...
241
            }
242
243
            if (\class_exists(InstalledVersions::class)) {
244
                $rootPath = InstalledVersions::getRootPackage()['install_path'] ?? false;
245
246
                if ($rootPath && $rPos = \strpos($rootPath, 'composer')) {
247
                    $rootPath = \substr($rootPath, 0, $rPos);
248
249
                    if (!$pos = \strpos($directory, $rootPath)) {
250
                        throw new \UnexpectedValueException(\sprintf('Looks like package "%s" does not live in composer\'s directory "%s".', InstalledVersions::getRootPackage()['name'], $rootPath));
251
                    }
252
253
                    $parts = \explode('/', \substr($directory, $pos));
254
                    $directory = InstalledVersions::getInstallPath($parts[1] . '/' . $parts[2]);
255
256
                    if (null !== $directory) {
257
                        return ($this->loadedExtensionPaths[$bundleName] = FileSystem::normalizePath($directory)) . '/' . $path;
258
                    }
259
                }
260
            }
261
        }
262
263
        throw new \InvalidArgumentException(\sprintf('Unable to find file "%s".', $path));
264
    }
265
266
    /**
267
     * Load up a module(s) (A.K.A plugin).
268
     *
269
     * @return \Rade\Module|array<string,\Rade\Module>
270
     */
271
    public function getModule(string $directory = null)
272
    {
273
        if (null === $directory) {
274
            return $this->loadedModules;
275
        }
276
277
        return $this->loadedModules[$directory] ?? throw new ServiceCreationException(\sprintf('Failed to load module %s, from modules root path.', $directory));
278
    }
279
}
280