district09/php_package_qa-drupal

View on GitHub
src/GrumPHP/ConfigFileMerger.php

Summary

Maintainability
A
55 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Digipolisgent\QA\Drupal\GrumPHP;

use GrumPHP\Task\Behat;
use GrumPHP\Task\Phpcs;
use GrumPHP\Task\PhpMd;
use GrumPHP\Task\PhpStan;
use GrumPHP\Task\Phpunit;
use GrumPHP\Task\TaskInterface;
use Nette\Neon\Neon;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Yaml\Yaml;

/**
 * Create a config file, as required by the task, on-the-fly.
 *
 * This will create a config file on-the-fly based on:
 * - The example config files in qa-drupal.
 * - Combined or replaced by the config file in the root of the project.
 *
 * NOTE: Config files are stored permanantly as they can be required by IDE
 *       tools.
 */
final class ConfigFileMerger
{

    /**
     * Configuration file types.
     */
    private const FILETYPE_XML = 'xml';
    private const FILETYPE_YAML = 'yaml';
    private const FILETYPE_NEON = 'neon';

    /**
     * Mapping of task types and their configuration files.
     *
     * @var array
     */
    private $taskInfo = [
        Behat::class => [
            'filename' => 'behat',
            'extension' => 'yml',
            'type' => self::FILETYPE_YAML,
        ],
        Phpcs::class => [
            'filename' => 'phpcs',
            'extension' => 'xml',
            'type' => self::FILETYPE_XML,
        ],
        PhpMd::class => [
            'filename' => 'phpmd',
            'extension' => 'xml',
            'type' => self::FILETYPE_XML,
        ],
        PhpStan::class => [
            'filename' => 'phpstan',
            'extension' => 'neon',
            'type' => self::FILETYPE_NEON,
        ],
        Phpunit::class => [
            'filename' => 'phpunit',
            'extension' => 'xml',
            'type' => self::FILETYPE_XML,
        ],
    ];

    /**
     * The symfony filesystem.
     *
     * @var \Symfony\Component\Filesystem\Filesystem
     */
    private $filesystem;

    /**
     * Class constructor.
     */
    public function __construct()
    {
        $this->filesystem = new Filesystem();
    }

    /**
     * Create the task configuration file.
     *
     * @param \GrumPHP\Task\TaskInterface $task
     *   The GrumPHP task.
     * @param bool $isExtension
     *   The files need to be merged for an extension.
     */
    public function mergeTaskConfig(
        TaskInterface $task,
        bool $isExtension
    ): void {
        $taskInfo = $this->getTaskConfigFileInfo($task);
        if (!$taskInfo) {
            return;
        }

        // Candidate configuration files.
        $candidates = $this->getConfigFileCandidates($taskInfo, $isExtension);

        // Search for the candidates and merge or copy them.
        $config = null;

        foreach ($candidates as $env_var => $file) {
            // Ignore if configured to skip or if the file is missing.
            if (!empty($_SERVER[$env_var]) || !$this->filesystem->exists($file)) {
                continue;
            }

            // Load the configuration file.
            $loaded = $this->loadTaskConfigFile($taskInfo['type'], $file);

            // Just copy the file if it isn't readable.
            if ($loaded === false) {
                $this->filesystem->copy($file, $taskInfo['grumphp']);
                return;
            }

            // Merge the configuration.
            $config = $config === null
                ? $loaded
                : array_replace_recursive($loaded, $config);
        }

        // Save the merged configuration.
        if ($config !== null) {
            $this->saveTaskConfigFile($taskInfo, $config);
        }
    }

    /**
     * Get some information about the task configuration file.
     *
     * @param \GrumPHP\Task\TaskInterface $task
     *   The GrumPHP task.
     *
     * @return array|null
     *   The task configuration info (filename, extension, type and name of the
     *   temporary merged file for GrumPHP) as associative array.
     */
    private function getTaskConfigFileInfo(TaskInterface $task)
    {
        $taskClass = get_class($task);
        if (empty($this->taskInfo[$taskClass])) {
            return null;
        }

        $info = $this->taskInfo[$taskClass];
        $info['grumphp'] = sprintf(
            '%s.qa-drupal.%s',
            $info['filename'],
            $info['extension']
        );

        return $info;
    }

    /**
     * Get the candidate configuration files to merge from.
     *
     * @param array $taskInfo
     *   The task information to get the candidates for.
     * @param bool $isExtension
     *   Is the task run in an extension project.
     */
    private function getConfigFileCandidates(
        array $taskInfo,
        bool $isExtension
    ): array {
        // Candidate configuration files.
        $key = strtoupper($taskInfo['filename']) . '_SKIP_';
        $type = ($isExtension ? 'extension' : 'site');
        $path = dirname(__FILE__, 3) . '/configs/';

        return [
            $key . 'LOCAL' => $taskInfo['filename'] . '.local.' . $taskInfo['extension'],
            $key . 'PROJECT' => $taskInfo['filename'] . '.' . $taskInfo['extension'],
            $key . 'PACKAGE_TYPE' => $path . $taskInfo['filename'] . '-' . $type . '.' . $taskInfo['extension'],
            $key . 'PACKAGE_GLOBAL' => $path . $taskInfo['filename'] . '.' . $taskInfo['extension'],
        ];
    }

    /**
     * Read and parse a task configuration file.
     *
     * @param string $type
     *   The file type.
     * @param string $file
     *   Path to the file.
     *
     * @return array|false
     *   The configuration data or false if not supported.
     */
    private function loadTaskConfigFile($type, $file)
    {
        switch ($type) {
            case self::FILETYPE_YAML:
                return Yaml::parseFile($file);

            case self::FILETYPE_NEON:
                return Neon::decode(file_get_contents($file));
        }

        return false;
    }

    /**
     * Write a config file.
     *
     * @param array $taskInfo
     *   The task info.
     * @param array|null $data
     *   The configuration data.
     */
    private function saveTaskConfigFile($taskInfo, ?array $config)
    {
        $type = $taskInfo['type'];
        $file = $taskInfo['grumphp'];

        switch ($type) {
            case self::FILETYPE_YAML:
                $config = Yaml::dump($config);
                break;

            case self::FILETYPE_NEON:
                $config = Neon::encode($config, Neon::BLOCK);
                break;
        }

        $this->filesystem->dumpFile($file, $config);
    }
}