peakphp/framework

View on GitHub
src/Config/Processor/IniProcessor.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Peak\Config\Processor;

use Peak\Blueprint\Common\ResourceProcessor;
use Peak\Common\Traits\ArrayMergeRecursiveDistinct;
use Peak\Config\Exception\ProcessorException;

use function error_get_last;
use function parse_ini_string;
use function stripos;
use function strpos;
use function substr;
use function trim;

class IniProcessor implements ResourceProcessor
{
    use ArrayMergeRecursiveDistinct;

    /**
     * @var array
     */
    private $content;

    /**
     * @param mixed $data
     * @return array
     * @throws ProcessorException
     */
    public function process($data): array
    {
        $this->load($data);
        return $this->content;
    }

    /**
     * Loads in the ini file specified in filename, and returns the settings in
     * it as an associative multi-dimensional array
     * @param string $data
     * @throws ProcessorException
     */
    public function load(string $data)
    {
        // we silence error(s) so we can catch them and throw a proper exception after
        $data = @parse_ini_string($data, true);

        // fail if there was an error while processing the specified ini file
        if ($data === false) {
            $error = error_get_last();
            throw new ProcessorException(__CLASS__.' fail to parse data: '.$error['message']);
        }

        // reset the result array
        $this->content = [];

        // loop through each section
        foreach ($data as $section => $contents) {
            $this->processSection($section, $contents);
        }
    }

    /**
     * Process contents of the specified section
     *
     * @param  string $section Section name
     * @param  array $contents Section contents
     * @throws ProcessorException
     */
    private function processSection(string $section, array $contents)
    {
        // the section does not extend another section
        if (stripos($section, ':') === false) {
            $this->content[$section] = $this->processSectionContents($contents);
            return;
        }

        // section extends another section
        // extract section names
        list($ext_target, $ext_source) = explode(':', $section);
        $ext_target = trim($ext_target);
        $ext_source = trim($ext_source);

        // check if the extended section exists
        if (!isset($this->content[$ext_source])) {
            throw new ProcessorException(__CLASS__.': Unable to extend section ' . $ext_source . ', section not found');
        }

        // process section contents
        $this->content[$ext_target] = $this->processSectionContents($contents);

        // merge the new section with the existing section values
        $this->content[$ext_target] = $this->arrayMergeRecursiveDistinct(
            $this->content[$ext_source],
            $this->content[$ext_target]
        );
    }

    /**
     * Process contents of a section
     *
     * @param  array $contents Section contents
     * @return array
     */
    private function processSectionContents(array $contents)
    {
        $result = [];

        // loop through each line and convert it to an array
        foreach ($contents as $path => $value) {
            // convert all a.b.c.d to multi-dimensional arrays
            $process = $this->processContentEntry($path, $value);
            // merge the current line with all previous ones
            $result = $this->arrayMergeRecursiveDistinct($result, $process);
        }

        return $result;
    }

    /**
     * Convert a.b.c.d paths to multi-dimensional arrays
     *
     * @param  string $path Current ini file's line's key
     * @param  mixed  $value Current ini file's line's value
     * @return array
     */
    private function processContentEntry($path, $value)
    {
        $pos = strpos($path, '.');

        if ($pos === false) {
            return [
                $path => $value
            ];
        }

        $key = substr($path, 0, $pos);
        $path = substr($path, $pos + 1);

        return [
            $key => $this->processContentEntry($path, $value)
        ];
    }
}