hendrikmaus/reynaldo

View on GitHub
src/Parser/RefractParser.php

Summary

Maintainability
A
2 hrs
Test Coverage
A
98%
<?php

namespace Hmaus\Reynaldo\Parser;

use Hmaus\Reynaldo\Elements\ApiParseResult;
use Hmaus\Reynaldo\Elements\AssetElement;
use Hmaus\Reynaldo\Elements\BaseElement;
use Hmaus\Reynaldo\Elements\DataStructureCategoryElement;
use Hmaus\Reynaldo\Elements\DataStructureElement;
use Hmaus\Reynaldo\Elements\HrefVariablesElement;
use Hmaus\Reynaldo\Elements\HttpRequestElement;
use Hmaus\Reynaldo\Elements\HttpResponseElement;
use Hmaus\Reynaldo\Elements\HttpTransactionElement;
use Hmaus\Reynaldo\Elements\HttpTransitionElement;
use Hmaus\Reynaldo\Elements\MasterCategoryElement;
use Hmaus\Reynaldo\Elements\MemberElement;
use Hmaus\Reynaldo\Elements\ObjectElement;
use Hmaus\Reynaldo\Elements\ParseResultElement;
use Hmaus\Reynaldo\Elements\ResourceElement;
use Hmaus\Reynaldo\Elements\ResourceGroupElement;

class RefractParser implements Parser
{
    /**
     * @var ApiParseResult
     */
    private $parseResult;

    /**
     * Map api elements to php classes
     * @var array
     */
    private $elementMap = [
        'resource' => ResourceElement::class,
        'transition' => HttpTransitionElement::class,
        'httpTransaction' => HttpTransactionElement::class,
        'httpRequest' => HttpRequestElement::class,
        'httpResponse' => HttpResponseElement::class,
        'asset' => AssetElement::class,
        'hrefVariables' => HrefVariablesElement::class,
        'member' => MemberElement::class,
        'dataStructure' => DataStructureElement::class,
        'object' => ObjectElement::class,
    ];

    public function parse(array $description)
    {
        $this->parseResult = new ParseResultElement($description);
        $this->iterate($description['content']);

        return $this->parseResult;
    }

    /**
     * Iterate given content array and call `process` for every element inside
     *
     * @param array $content
     * @param null|BaseElement $parent
     */
    private function iterate(array $content, $parent = null)
    {
        foreach ($content as $element) {
            $this->process($element, $parent);
        }
    }

    /**
     * Create php classes using raw element data; called recursively
     *
     * @param array $element
     * @param null|BaseElement $parent
     */
    private function process(array $element, $parent = null)
    {
        $apiElement = $element['element'];

        if (isset($this->elementMap[$apiElement])) {
            $this->processElement(
                $element,
                $this->elementMap[$apiElement],
                $parent
            );
        }

        if ($element['element'] === 'category') {
            // todo extend the element map to check for classes, then merge processCategory into processElement
            $this->processCategory($element, $parent);
        }
    }

    /**
     * Create new element from raw data and add it to its parent
     *
     * @param array $element Raw element data
     * @param string $class FQCN to use for creating an element
     * @param BaseElement $parent parent element to add the newly created one to
     * @param null|string $replaceAttribute usually, the new element will be added to the content
     *                                          of the given parent. There are some cases, hrefVariables for example,
     *                                          where the attributes actually contain elements as well.
     *                                          To add the new element to the attributes, pass the name of the
     *                                          key inside of attributes that should be replaced
     */
    private function processElement(array $element, $class, BaseElement $parent, $replaceAttribute = null)
    {
        $apiElement = new $class($element);

        if (!$replaceAttribute) {
            $parent->addContentElement($apiElement);
        } else {
            $parent->replaceAttributeWithElement($replaceAttribute, $apiElement);
        }

        if (isset($element['attributes'])) {
            foreach ($element['attributes'] as $attributeName => $attributeValue) {
                if (!isset($this->elementMap[$attributeName])) {
                    continue;
                }

                $this->processElement(
                    $attributeValue,
                    $this->elementMap[$attributeName],
                    $apiElement,
                    $attributeName
                );
            }
        }

        if (!isset($element['content'])) {
            return;
        }

        if (!is_array($element['content'])) {
            return;
        }

        $this->iterate($element['content'], $apiElement);
    }

    /**
     * Helper to create different php classes from element `category` which carries its actual meaning in its classes
     *
     * @param array $element
     * @param null|BaseElement $parent
     */
    private function processCategory(array $element, $parent = null)
    {
        if ($this->isMasterCategory($element)) {
            $this->createCategory($element, MasterCategoryElement::class, $this->parseResult);
        }

        if ($this->isResourceGroup($element)) {
            $this->createCategory($element, ResourceGroupElement::class, $parent);
        }

        if ($this->isDataStructureCategory($element)) {
            $this->createCategory($element, DataStructureCategoryElement::class, $parent);
        }
    }

    /**
     * Detect master category
     *
     * @param array $element
     * @return bool
     */
    private function isMasterCategory(array $element)
    {
        return $this->hasClass('api', $element);
    }

    /**
     * Class query helper
     *
     * @param string $className
     * @param array $element
     * @return bool
     */
    private function hasClass($className, array $element)
    {
        return in_array($className, $element['meta']['classes']);
    }

    /**
     * Sub-helper for `processCategory`
     *
     * @param array $element
     * @param string $className
     * @param BaseElement $parent
     */
    private function createCategory(array $element, $className, BaseElement $parent)
    {
        $newElement = new $className($element);
        $parent->addContentElement($newElement);
        $this->iterate($element['content'], $newElement);
    }

    /**
     * Detect resource group
     *
     * @param array $element
     * @return bool
     */
    private function isResourceGroup(array $element)
    {
        return $this->hasClass('resourceGroup', $element);
    }

    /**
     * Detect data structures group
     *
     * @param array $element
     * @return bool
     */
    private function isDataStructureCategory(array $element)
    {
        return $this->hasClass('dataStructures', $element);
    }
}