biurad /
flange
| 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
Bug
introduced
by
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
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
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
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
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 |