daikon-cqrs/entity

View on GitHub
src/Path/ValuePathParser.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php declare(strict_types=1);
/**
 * This file is part of the daikon-cqrs/entity project.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Daikon\Entity\Path;

use Daikon\Interop\Assertion;
use JMS\Parser\AbstractParser;
use JMS\Parser\SimpleLexer;

final class ValuePathParser extends AbstractParser
{
    private const T_ATTRIBUTE = 1;

    private const T_POSITION = 2;

    private const T_COMPONENT_SEP = 3;

    private const T_PART_SEP = 4;

    private const TOKEN_REGEX = <<<REGEX
/
    # type identifier which refers to an attribute
    ([a-zA-Z_]+)

    # value position
    |(\d+)

    # value-path-component separator. the two components of a value-path-part being attribute and position.
    |(\.)

    # value-path separator
    |(\-)
/x
REGEX;

    private const TOKEN_MAP = [
        0 => 'T_UNKNOWN',
        1 => 'T_ATTRIBUTE',
        2 => 'T_POSITION',
        3 => 'T_PART_SEP'
    ];

    public static function create(): ValuePathParser
    {
        $mapToken = function (string $token): array {
            switch ($token) {
                case '.':
                    return [ self::T_COMPONENT_SEP, $token ];
                case '-':
                    return [ self::T_PART_SEP, $token ];
                default:
                    return is_numeric($token)
                        ? [ self::T_POSITION, (int)$token ]
                        : [ self::T_ATTRIBUTE, $token ];
            }
        };
        $lexer = new SimpleLexer(self::TOKEN_REGEX, self::TOKEN_MAP, $mapToken);
        return new ValuePathParser($lexer);
    }

    /**
     * @param string $path
     * @param string $context
     */
    public function parse($path, $context = null): ValuePath
    {
        return parent::parse($path, $context);
    }

    public function parseInternal(): ValuePath
    {
        $valuePathParts = [];
        while ($valuePathPart = $this->consume()) {
            $valuePathParts[] = $valuePathPart;
        }
        return new ValuePath($valuePathParts);
    }

    private function consume(): ?ValuePathPart
    {
        $this->eatSeparator();
        $attribute = $this->parseAttribute();
        return is_null($attribute) ? null : new ValuePathPart($attribute, $this->parsePosition());
    }

    private function eatSeparator(): void
    {
        if ($this->lexer->isNext(self::T_PART_SEP)) {
            $this->match(self::T_PART_SEP);
        }
    }

    private function parseAttribute(): ?string
    {
        if (!$this->lexer->isNext(self::T_ATTRIBUTE)) {
            Assertion::null($this->lexer->next, 'Expecting T_TYPE at the beginning of a new path-part.');
            return null;
        }
        return $this->match(self::T_ATTRIBUTE);
    }

    private function parsePosition(): int
    {
        if ($this->lexer->isNext(self::T_COMPONENT_SEP)) {
            $this->match(self::T_COMPONENT_SEP);
            return $this->match(self::T_POSITION);
        }
        return -1;
    }
}