Issues (36)

src/Traits/HelperTrait.php (5 issues)

1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Biurad opensource projects.
5
 *
6
 * @copyright 2019 Biurad Group (https://biurad.com/)
7
 * @license   https://opensource.org/licenses/BSD-3-Clause License
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Flange\Traits;
14
15
use Composer\InstalledVersions;
16
use Laminas\Escaper\Escaper;
17
use Nette\Utils\FileSystem;
18
use Rade\DI\Exceptions\ContainerResolutionException;
19
use Rade\DI\Exceptions\ServiceCreationException;
20
use Rade\DI\Extensions\ExtensionBuilder;
21
use Symfony\Component\Config\Exception\LoaderLoadException;
22
use Symfony\Component\Config\Loader\LoaderResolverInterface;
23
24
trait HelperTrait
25
{
26
    private array $loadedExtensionPaths = [], $loadedModules = [], $moduleExtensions = [];
27
28
    public function isDebug(): bool
29
    {
30
        return $this->parameters['debug'];
31
    }
32
33
    /**
34
     * Determine if the application is running in the console.
35
     */
36
    public function isRunningInConsole(): bool
37
    {
38
        return \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true);
39
    }
40
41
    /**
42
     * Determine if the application is in vagrant environment.
43
     */
44
    public function inVagrantEnvironment(): bool
45
    {
46
        return ('/home/vagrant' === \getenv('HOME') || 'VAGRANT' === \getenv('VAGRANT')) && \is_dir('/dev/shm');
47
    }
48
49
    /**
50
     * The delegated config loaders instance.
51
     */
52
    public function getConfigLoader(): LoaderResolverInterface
53
    {
54
        return $this->parameters['config.builder.loader_resolver'] ?? $this->get('config.loader_resolver');
0 ignored issues
show
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

54
        return $this->parameters['config.builder.loader_resolver'] ?? $this->/** @scrutinizer ignore-call */ get('config.loader_resolver');
Loading history...
55
    }
56
57
    /**
58
     * Context specific methods for use in secure output escaping.
59
     */
60 6
    public function escape(string $encoding = null): Escaper
61
    {
62 6
        return new Escaper($encoding);
63
    }
64
65
    /**
66
     * Mounts a service provider like controller taking in a parameter of application.
67
     *
68
     * @param callable(\Rade\DI\Container) $controllers
69
     */
70
    public function mount(callable $controllers): void
71
    {
72
        $controllers($this);
73
    }
74
75
    /**
76
     * Loads a set of container extensions.
77
     *
78
     * Example for extensions:
79
     * [
80
     *    PhpExtension::class,
81
     *    CoreExtension::class => -1,
82
     *    [ProjectExtension::class, ['%project.dir%']],
83
     * ]
84
     *
85
     * @param array<int,mixed>    $extensions
86
     * @param array<string,mixed> $configs    the default configuration for all extensions
87
     * @param string|null         $outputDir  Enable Generating ConfigBuilders to help create valid config
88
     */
89 1
    public function loadExtensions(array $extensions, array $configs = [], string $outputDir = null): void
90
    {
91 1
        $builder = new ExtensionBuilder($this, $configs);
0 ignored issues
show
$this of type Flange\Traits\HelperTrait is incompatible with the type Rade\DI\Container 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

91
        $builder = new ExtensionBuilder(/** @scrutinizer ignore-type */ $this, $configs);
Loading history...
92
93 1
        if (null !== $outputDir) {
94
            $builder->setConfigBuilderGenerator($outputDir);
95
        }
96
97 1
        $builder->load(\array_merge($extensions, $this->moduleExtensions));
98
    }
99
100
    /**
101
     * Loads a set of modules from module directory.
102
     *
103
     * This method should be called before the loadExtensions method.
104
     */
105
    public function loadModules(string $moduleDir, string $prefix = null, string $configName = 'config'): void
106
    {
107
        // Get list modules available in application
108
        foreach (\scandir($moduleDir) as $directory) {
109
            if ('.' === $directory || '..' === $directory) {
110
                continue;
111
            }
112
113
            // Check if file parsed is a directory (module need to be a directory)
114
            if (!\is_dir($directoryPath = \rtrim($moduleDir, '\/').'/'.$prefix.$directory.'/')) {
115
                continue;
116
            }
117
118
            // Load module configuration file
119
            if (!\file_exists($configFile = $directoryPath.$configName.'.json')) {
120
                continue;
121
            }
122
123
            // Load module
124
            $moduleLoad = new \Flange\Module($directoryPath, \json_decode(\file_get_contents($configFile), true) ?? []);
125
126
            if (!\array_key_exists($directory, $this->loadedExtensionPaths)) {
127
                $this->loadedExtensionPaths[$directory] = $directoryPath;
128
            }
129
130
            if (!$moduleLoad->isEnabled()) {
131
                continue;
132
            }
133
134
            if (!empty($extensions = $moduleLoad->getExtensions())) {
135
                $this->moduleExtensions = \array_merge($this->moduleExtensions, $extensions);
136
            }
137
138
            $this->loadedModules[$directory] = $moduleLoad;
139
        }
140
    }
141
142
    /**
143
     * Loads a resource.
144
     *
145
     * @param mixed $resource the resource can be anything supported by a config loader
146
     *
147
     * @return mixed
148
     *
149
     * @throws \Exception If something went wrong
150
     */
151
    public function load($resource, string $type = null)
152
    {
153
        if (\is_string($resource)) {
154
            $resource = $this->parameter($resource);
0 ignored issues
show
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

154
            /** @scrutinizer ignore-call */ 
155
            $resource = $this->parameter($resource);
Loading history...
155
156
            if ('@' === $resource[0]) {
157
                $resource = $this->getLocation($resource);
158
            }
159
        }
160
161
        if (false === $loader = $this->getConfigLoader()->resolve($resource, $type)) {
162
            throw new LoaderLoadException($resource, null, 0, null, $type);
163
        }
164
165
        return $loader->load($resource, $type);
166
    }
167
168
    /**
169
     * Returns the file path for a given service extension or class name resource.
170
     *
171
     * A Resource can be a file or a directory. The resource name must follow the following pattern:
172
     * "@CoreExtension/path/to/a/file.something"
173
     *
174
     * where CoreExtension is the name of the service extension or class,
175
     * and the remaining part is the relative path to a file or directory.
176
     *
177
     * We recommend using composer v2, as this method depends on it or use $baseDir parameter.
178
     *
179
     * @return string The absolute path of the resource
180
     *
181
     * @throws \InvalidArgumentException    if the file cannot be found or the name is not valid
182
     * @throws \RuntimeException            if the name contains invalid/unsafe characters
183
     * @throws ContainerResolutionException if the service provider is not included in path
184
     */
185
    public function getLocation(string $path, string $baseDir = 'src')
186
    {
187
        if (\str_contains($path, '..')) {
188
            throw new \RuntimeException(\sprintf('File name "%s" contains invalid characters (..).', $path));
189
        }
190
191
        if ('@' === $path[0]) {
192
            [$bundleName, $path] = \explode('/', \substr($path, 1), 2);
193
194
            if (isset($this->loadedExtensionPaths[$bundleName])) {
195
                return $this->loadedExtensionPaths[$bundleName].'/'.$path;
196
            }
197
198
            if (null !== $extension = $this->getExtension($bundleName)) {
0 ignored issues
show
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

198
            if (null !== $extension = $this->/** @scrutinizer ignore-call */ getExtension($bundleName)) {
Loading history...
199
                $bundleName = $extension::class;
200
            }
201
202
            try {
203
                $ref = new \ReflectionClass($bundleName);
204
                $directory = \str_replace('\\', '/', \dirname($ref->getFileName()));
205
            } catch (\ReflectionException $e) {
206
                throw new ContainerResolutionException(\sprintf('Resource path is not supported for %s', $bundleName), 0, $e);
207
            }
208
209
            if ($pos = \strpos($directory, $baseDir)) {
210
                $directory = \substr($directory, 0, $pos + \strlen($baseDir));
211
212
                if (!\file_exists($directory)) {
213
                    $directory = \substr_replace($directory, '', $pos, 3);
214
                }
215
216
                return ($this->loadedExtensionPaths[$bundleName] = \substr($directory, 0, $pos + \strlen($baseDir))).'/'.$path;
0 ignored issues
show
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

216
                return ($this->loadedExtensionPaths[$bundleName] = \substr(/** @scrutinizer ignore-type */ $directory, 0, $pos + \strlen($baseDir))).'/'.$path;
Loading history...
217
            }
218
219
            if (\class_exists(InstalledVersions::class)) {
220
                $rootPath = InstalledVersions::getRootPackage()['install_path'] ?? false;
221
222
                if ($rootPath && $rPos = \strpos($rootPath, 'composer')) {
223
                    $rootPath = \substr($rootPath, 0, $rPos);
224
225
                    if (!$pos = \strpos($directory, $rootPath)) {
226
                        throw new \UnexpectedValueException(\sprintf('Looks like package "%s" does not live in composer\'s directory "%s".', InstalledVersions::getRootPackage()['name'], $rootPath));
227
                    }
228
229
                    $parts = \explode('/', \substr($directory, $pos));
230
                    $directory = InstalledVersions::getInstallPath($parts[1].'/'.$parts[2]);
231
232
                    if (null !== $directory) {
233
                        return ($this->loadedExtensionPaths[$bundleName] = FileSystem::normalizePath($directory)).'/'.$path;
234
                    }
235
                }
236
            }
237
        }
238
239
        throw new \InvalidArgumentException(\sprintf('Unable to find file "%s".', $path));
240
    }
241
242
    /**
243
     * Load up a module(s) (A.K.A plugin).
244
     *
245
     * @return \Flange\Module|array<string,\Flange\Module>
246
     */
247
    public function getModule(string $directory = null)
248
    {
249
        if (null === $directory) {
250
            return $this->loadedModules;
251
        }
252
253
        if (!isset($this->loadedModules[$directory])) {
254
            throw new ServiceCreationException(\sprintf('Failed to load module %s, from modules root path.', $directory));
255
        }
256
257
        return $this->loadedModules[$directory];
258
    }
259
}
260