biurad/flange

View on GitHub
src/Traits/HelperTrait.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php declare(strict_types=1);

/*
 * This file is part of Biurad opensource projects.
 *
 * @copyright 2019 Biurad Group (https://biurad.com/)
 * @license   https://opensource.org/licenses/BSD-3-Clause License
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Flange\Traits;

use Composer\InstalledVersions;
use Laminas\Escaper\Escaper;
use Nette\Utils\FileSystem;
use Rade\DI\Exceptions\ContainerResolutionException;
use Rade\DI\Exceptions\ServiceCreationException;
use Rade\DI\Extensions\ExtensionBuilder;
use Symfony\Component\Config\Exception\LoaderLoadException;
use Symfony\Component\Config\Loader\LoaderResolverInterface;

trait HelperTrait
{
    private array $loadedExtensionPaths = [], $loadedModules = [], $moduleExtensions = [];

    public function isDebug(): bool
    {
        return $this->parameters['debug'];
    }

    /**
     * Determine if the application is running in the console.
     */
    public function isRunningInConsole(): bool
    {
        return \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true);
    }

    /**
     * Determine if the application is in vagrant environment.
     */
    public function inVagrantEnvironment(): bool
    {
        return ('/home/vagrant' === \getenv('HOME') || 'VAGRANT' === \getenv('VAGRANT')) && \is_dir('/dev/shm');
    }

    /**
     * The delegated config loaders instance.
     */
    public function getConfigLoader(): LoaderResolverInterface
    {
        return $this->parameters['config.builder.loader_resolver'] ?? $this->get('config.loader_resolver');
    }

    /**
     * Context specific methods for use in secure output escaping.
     */
    public function escape(string $encoding = null): Escaper
    {
        return new Escaper($encoding);
    }

    /**
     * Mounts a service provider like controller taking in a parameter of application.
     *
     * @param callable(\Rade\DI\Container) $controllers
     */
    public function mount(callable $controllers): void
    {
        $controllers($this);
    }

    /**
     * Loads a set of container extensions.
     *
     * Example for extensions:
     * [
     *    PhpExtension::class,
     *    CoreExtension::class => -1,
     *    [ProjectExtension::class, ['%project.dir%']],
     * ]
     *
     * @param array<int,mixed>    $extensions
     * @param array<string,mixed> $configs    the default configuration for all extensions
     * @param string|null         $outputDir  Enable Generating ConfigBuilders to help create valid config
     */
    public function loadExtensions(array $extensions, array $configs = [], string $outputDir = null): void
    {
        $builder = new ExtensionBuilder($this, $configs);

        if (null !== $outputDir) {
            $builder->setConfigBuilderGenerator($outputDir);
        }

        $builder->load(\array_merge($extensions, $this->moduleExtensions));
    }

    /**
     * Loads a set of modules from module directory.
     *
     * This method should be called before the loadExtensions method.
     */
    public function loadModules(string $moduleDir, string $prefix = null, string $configName = 'config'): void
    {
        // Get list modules available in application
        foreach (\scandir($moduleDir) as $directory) {
            if ('.' === $directory || '..' === $directory) {
                continue;
            }

            // Check if file parsed is a directory (module need to be a directory)
            if (!\is_dir($directoryPath = \rtrim($moduleDir, '\/').'/'.$prefix.$directory.'/')) {
                continue;
            }

            // Load module configuration file
            if (!\file_exists($configFile = $directoryPath.$configName.'.json')) {
                continue;
            }

            // Load module
            $moduleLoad = new \Flange\Module($directoryPath, \json_decode(\file_get_contents($configFile), true) ?? []);

            if (!\array_key_exists($directory, $this->loadedExtensionPaths)) {
                $this->loadedExtensionPaths[$directory] = $directoryPath;
            }

            if (!$moduleLoad->isEnabled()) {
                continue;
            }

            if (!empty($extensions = $moduleLoad->getExtensions())) {
                $this->moduleExtensions = \array_merge($this->moduleExtensions, $extensions);
            }

            $this->loadedModules[$directory] = $moduleLoad;
        }
    }

    /**
     * Loads a resource.
     *
     * @param mixed $resource the resource can be anything supported by a config loader
     *
     * @return mixed
     *
     * @throws \Exception If something went wrong
     */
    public function load($resource, string $type = null)
    {
        if (\is_string($resource)) {
            $resource = $this->parameter($resource);

            if ('@' === $resource[0]) {
                $resource = $this->getLocation($resource);
            }
        }

        if (false === $loader = $this->getConfigLoader()->resolve($resource, $type)) {
            throw new LoaderLoadException($resource, null, 0, null, $type);
        }

        return $loader->load($resource, $type);
    }

    /**
     * Returns the file path for a given service extension or class name resource.
     *
     * A Resource can be a file or a directory. The resource name must follow the following pattern:
     * "@CoreExtension/path/to/a/file.something"
     *
     * where CoreExtension is the name of the service extension or class,
     * and the remaining part is the relative path to a file or directory.
     *
     * We recommend using composer v2, as this method depends on it or use $baseDir parameter.
     *
     * @return string The absolute path of the resource
     *
     * @throws \InvalidArgumentException    if the file cannot be found or the name is not valid
     * @throws \RuntimeException            if the name contains invalid/unsafe characters
     * @throws ContainerResolutionException if the service provider is not included in path
     */
    public function getLocation(string $path, string $baseDir = 'src')
    {
        if (\str_contains($path, '..')) {
            throw new \RuntimeException(\sprintf('File name "%s" contains invalid characters (..).', $path));
        }

        if ('@' === $path[0]) {
            [$bundleName, $path] = \explode('/', \substr($path, 1), 2);

            if (isset($this->loadedExtensionPaths[$bundleName])) {
                return $this->loadedExtensionPaths[$bundleName].'/'.$path;
            }

            if (null !== $extension = $this->getExtension($bundleName)) {
                $bundleName = $extension::class;
            }

            try {
                $ref = new \ReflectionClass($bundleName);
                $directory = \str_replace('\\', '/', \dirname($ref->getFileName()));
            } catch (\ReflectionException $e) {
                throw new ContainerResolutionException(\sprintf('Resource path is not supported for %s', $bundleName), 0, $e);
            }

            if ($pos = \strpos($directory, $baseDir)) {
                $directory = \substr($directory, 0, $pos + \strlen($baseDir));

                if (!\file_exists($directory)) {
                    $directory = \substr_replace($directory, '', $pos, 3);
                }

                return ($this->loadedExtensionPaths[$bundleName] = \substr($directory, 0, $pos + \strlen($baseDir))).'/'.$path;
            }

            if (\class_exists(InstalledVersions::class)) {
                $rootPath = InstalledVersions::getRootPackage()['install_path'] ?? false;

                if ($rootPath && $rPos = \strpos($rootPath, 'composer')) {
                    $rootPath = \substr($rootPath, 0, $rPos);

                    if (!$pos = \strpos($directory, $rootPath)) {
                        throw new \UnexpectedValueException(\sprintf('Looks like package "%s" does not live in composer\'s directory "%s".', InstalledVersions::getRootPackage()['name'], $rootPath));
                    }

                    $parts = \explode('/', \substr($directory, $pos));
                    $directory = InstalledVersions::getInstallPath($parts[1].'/'.$parts[2]);

                    if (null !== $directory) {
                        return ($this->loadedExtensionPaths[$bundleName] = FileSystem::normalizePath($directory)).'/'.$path;
                    }
                }
            }
        }

        throw new \InvalidArgumentException(\sprintf('Unable to find file "%s".', $path));
    }

    /**
     * Load up a module(s) (A.K.A plugin).
     *
     * @return \Flange\Module|array<string,\Flange\Module>
     */
    public function getModule(string $directory = null)
    {
        if (null === $directory) {
            return $this->loadedModules;
        }

        if (!isset($this->loadedModules[$directory])) {
            throw new ServiceCreationException(\sprintf('Failed to load module %s, from modules root path.', $directory));
        }

        return $this->loadedModules[$directory];
    }
}