phossa2/route

View on GitHub
src/Route/Parser/ParserGcb.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php
/**
 * Phossa Project
 *
 * PHP version 5.4
 *
 * @category  Library
 * @package   Phossa2\Route
 * @copyright Copyright (c) 2016 phossa.com
 * @license   http://mit-license.org/ MIT License
 * @link      http://www.phossa.com/
 */
/*# declare(strict_types=1); */

namespace Phossa2\Route\Parser;

use Phossa2\Route\Message\Message;

/**
 * ParserGcb
 *
 * FastRoute algorithm
 *
 * @package Phossa2\Route
 * @author  Hong Zhang <phossa@126.com>
 * @see     ParserAbstract
 * @version 2.0.0
 * @since   2.0.0 added
 */
class ParserGcb extends ParserAbstract
{
    /**
     * group position map
     *
     * @var    array
     * @access protected
     */
    protected $maps = [];

    /**
     * chunk size 4 - 12 for merging regex
     *
     * @var    int
     * @access protected
     */
    protected $chunk = 8;

    /**
     * combined regex (cache)
     *
     * @var    string[]
     * @access protected
     */
    protected $data = [];

    /**
     * another cache
     *
     * @var    string[]
     * @access protected
     */
    protected $xmap = [];

    /**
     * {@inheritDoc}
     */
    public function processRoute(
        /*# string */ $routeName,
        /*# string */ $routePattern
    )/*# : string */ {
        list($regex, $map) = $this->convert($routePattern);
        $this->maps[$routeName] = $map;
        $this->doneProcess($routeName, $routePattern, $regex);
        return $regex;
    }

    /**
     * {@inheritDoc}
     */
    public function matchPath(/*# string */ $uriPath)
    {
        $matches = [];
        foreach ($this->getRegexData() as $i => $regex) {
            if (preg_match($regex, $uriPath, $matches)) {
                $map = array_flip($this->xmap[$i]);
                $key = $map[count($matches) - 1];
                return $this->fixMatches($key, $matches);
            }
        }
        return false;
    }

    /**
     * Convert to regex
     *
     * @param  string $pattern pattern to parse
     * @return array
     * @access protected
     */
    protected function convert(/*# string */ $pattern)/*# : array */
    {
        $ph = sprintf("\{%s(?:%s)?\}", self::MATCH_GROUP_NAME, self::MATCH_GROUP_TYPE);

        // count placeholders
        $map = $m = [];
        if (preg_match_all('~'. $ph .'~x', $pattern, $m)) {
            $map = $m[1];
        }

        $result = preg_replace(
            [
            '~' . $ph . '(*SKIP)(*FAIL) | \[~x', '~' . $ph . '(*SKIP)(*FAIL) | \]~x',
            '~\{' . self::MATCH_GROUP_NAME . '\}~x', '~' . $ph . '~x',
            ],
            ['(?:', ')?', '{\\1:' . self::MATCH_SEGMENT . '}', '(\\2)'],
            strtr('/' . trim($pattern, '/'), $this->shortcuts)
        );

        return [$result, $map];
    }

    /**
     * Merge several (chunk size) regex into one
     *
     * @return array
     * @access protected
     */
    protected function getRegexData()/*# : array */
    {
        // load from cache
        if (!$this->modified) {
            return $this->data;
        }

        // merge
        $this->data = array_chunk($this->regex, $this->chunk, true);
        foreach ($this->data as $i => $arr) {
            $map = $this->getMapData($arr, $this->maps);
            $str = '~^(?|';
            foreach ($arr as $k => $reg) {
                $str .= $reg . str_repeat('()', $map[$k] - count($this->maps[$k])) . '|';
            }
            $this->data[$i] = substr($str, 0, -1) . ')$~x';
            $this->xmap[$i] = $map;
        }
        $this->modified = false;
        return $this->data;
    }

    /**
     * @param  array $arr
     * @param  array $maps
     * @return array
     * @access protected
     */
    protected function getMapData(array $arr, array $maps)/*#: array */
    {
        $new1 = [];
        $keys = array_keys($arr);
        foreach ($keys as $k) {
            $new1[$k] = count($maps[$k]) + 1; // # of PH for route $k
        }
        $new2 = array_flip($new1);
        $new3 = array_flip($new2);

        foreach ($keys as $k) {
            if (!isset($new3[$k])) {
                foreach (range(1, 200) as $i) {
                    $cnt = $new1[$k] + $i;
                    if (!isset($new2[$cnt])) {
                        $new2[$cnt] = $k;
                        $new3[$k] = $cnt;
                        break;
                    }
                }
            }
        }
        return $new3;
    }

    /**
     * Fix matched placeholders, return with unique route key
     *
     * @param  string $name the route key/name
     * @param  array $matches desc
     * @return array [ $name, $matches ]
     * @access protected
     */
    protected function fixMatches(
        /*# string */ $name,
        array $matches
    )/*# : array */ {
        $res = [];
        $map = $this->maps[$name];
        foreach ($matches as $idx => $val) {
            if ($idx > 0 && '' !== $val) {
                $res[$map[$idx - 1]] = $val;
            }
        }

        // debug
        $this->debug(Message::get(
            Message::RTE_PARSER_MATCH,
            $name,
            $this->regex[$name]
        ));
        return [$name, $res];
    }
}