ParserGcb::fixMatches()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 3
nop 2
1
<?php
2
/**
3
 * Phossa Project
4
 *
5
 * PHP version 5.4
6
 *
7
 * @category  Library
8
 * @package   Phossa2\Route
9
 * @copyright Copyright (c) 2016 phossa.com
10
 * @license   http://mit-license.org/ MIT License
11
 * @link      http://www.phossa.com/
12
 */
13
/*# declare(strict_types=1); */
14
15
namespace Phossa2\Route\Parser;
16
17
use Phossa2\Route\Message\Message;
18
19
/**
20
 * ParserGcb
21
 *
22
 * FastRoute algorithm
23
 *
24
 * @package Phossa2\Route
25
 * @author  Hong Zhang <[email protected]>
26
 * @see     ParserAbstract
27
 * @version 2.0.0
28
 * @since   2.0.0 added
29
 */
30
class ParserGcb extends ParserAbstract
31
{
32
    /**
33
     * group position map
34
     *
35
     * @var    array
36
     * @access protected
37
     */
38
    protected $maps = [];
39
40
    /**
41
     * chunk size 4 - 12 for merging regex
42
     *
43
     * @var    int
44
     * @access protected
45
     */
46
    protected $chunk = 8;
47
48
    /**
49
     * combined regex (cache)
50
     *
51
     * @var    string[]
52
     * @access protected
53
     */
54
    protected $data = [];
55
56
    /**
57
     * another cache
58
     *
59
     * @var    string[]
60
     * @access protected
61
     */
62
    protected $xmap = [];
63
64
    /**
65
     * {@inheritDoc}
66
     */
67
    public function processRoute(
68
        /*# string */ $routeName,
69
        /*# string */ $routePattern
70
    )/*# : string */ {
71
        list($regex, $map) = $this->convert($routePattern);
72
        $this->maps[$routeName] = $map;
73
        $this->doneProcess($routeName, $routePattern, $regex);
74
        return $regex;
75
    }
76
77
    /**
78
     * {@inheritDoc}
79
     */
80
    public function matchPath(/*# string */ $uriPath)
81
    {
82
        $matches = [];
83
        foreach ($this->getRegexData() as $i => $regex) {
84
            if (preg_match($regex, $uriPath, $matches)) {
85
                $map = array_flip($this->xmap[$i]);
86
                $key = $map[count($matches) - 1];
87
                return $this->fixMatches($key, $matches);
88
            }
89
        }
90
        return false;
91
    }
92
93
    /**
94
     * Convert to regex
95
     *
96
     * @param  string $pattern pattern to parse
97
     * @return array
98
     * @access protected
99
     */
100
    protected function convert(/*# string */ $pattern)/*# : array */
101
    {
102
        $ph = sprintf("\{%s(?:%s)?\}", self::MATCH_GROUP_NAME, self::MATCH_GROUP_TYPE);
103
104
        // count placeholders
105
        $map = $m = [];
106
        if (preg_match_all('~'. $ph .'~x', $pattern, $m)) {
107
            $map = $m[1];
108
        }
109
110
        $result = preg_replace(
111
            [
112
            '~' . $ph . '(*SKIP)(*FAIL) | \[~x', '~' . $ph . '(*SKIP)(*FAIL) | \]~x',
113
            '~\{' . self::MATCH_GROUP_NAME . '\}~x', '~' . $ph . '~x',
114
            ],
115
            ['(?:', ')?', '{\\1:' . self::MATCH_SEGMENT . '}', '(\\2)'],
116
            strtr('/' . trim($pattern, '/'), $this->shortcuts)
117
        );
118
119
        return [$result, $map];
120
    }
121
122
    /**
123
     * Merge several (chunk size) regex into one
124
     *
125
     * @return array
126
     * @access protected
127
     */
128
    protected function getRegexData()/*# : array */
129
    {
130
        // load from cache
131
        if (!$this->modified) {
132
            return $this->data;
133
        }
134
135
        // merge
136
        $this->data = array_chunk($this->regex, $this->chunk, true);
137
        foreach ($this->data as $i => $arr) {
138
            $map = $this->getMapData($arr, $this->maps);
139
            $str = '~^(?|';
140
            foreach ($arr as $k => $reg) {
141
                $str .= $reg . str_repeat('()', $map[$k] - count($this->maps[$k])) . '|';
142
            }
143
            $this->data[$i] = substr($str, 0, -1) . ')$~x';
144
            $this->xmap[$i] = $map;
145
        }
146
        $this->modified = false;
147
        return $this->data;
148
    }
149
150
    /**
151
     * @param  array $arr
152
     * @param  array $maps
153
     * @return array
154
     * @access protected
155
     */
156
    protected function getMapData(array $arr, array $maps)/*#: array */
157
    {
158
        $new1 = [];
159
        $keys = array_keys($arr);
160
        foreach ($keys as $k) {
161
            $new1[$k] = count($maps[$k]) + 1; // # of PH for route $k
162
        }
163
        $new2 = array_flip($new1);
164
        $new3 = array_flip($new2);
165
166
        foreach ($keys as $k) {
167
            if (!isset($new3[$k])) {
168
                foreach (range(1, 200) as $i) {
169
                    $cnt = $new1[$k] + $i;
170
                    if (!isset($new2[$cnt])) {
171
                        $new2[$cnt] = $k;
172
                        $new3[$k] = $cnt;
173
                        break;
174
                    }
175
                }
176
            }
177
        }
178
        return $new3;
179
    }
180
181
    /**
182
     * Fix matched placeholders, return with unique route key
183
     *
184
     * @param  string $name the route key/name
185
     * @param  array $matches desc
186
     * @return array [ $name, $matches ]
187
     * @access protected
188
     */
189
    protected function fixMatches(
190
        /*# string */ $name,
191
        array $matches
192
    )/*# : array */ {
193
        $res = [];
194
        $map = $this->maps[$name];
195
        foreach ($matches as $idx => $val) {
196
            if ($idx > 0 && '' !== $val) {
197
                $res[$map[$idx - 1]] = $val;
198
            }
199
        }
200
201
        // debug
202
        $this->debug(Message::get(
203
            Message::RTE_PARSER_MATCH,
204
            $name,
205
            $this->regex[$name]
206
        ));
207
        return [$name, $res];
208
    }
209
}
210