123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- <?php
- namespace ForkBB\Core;
- class Router
- {
- const OK = 200;
- const NOT_FOUND = 404;
- const METHOD_NOT_ALLOWED = 405;
- const NOT_IMPLEMENTED = 501;
- /**
- * Массив постоянных маршрутов
- * @var array
- */
- protected $statical = [];
- /**
- * Массив динамических маршрутов
- * @var array
- */
- protected $dynamic = [];
- /**
- * Список методов доступа
- * @var array
- */
- protected $methods = [];
- /**
- * Массив для построения ссылок
- * @var array
- */
- protected $links = [];
- /**
- * Базовый url сайта
- * @var string
- */
- protected $baseUrl;
- /**
- * Host сайта
- * @var string
- */
- protected $host;
- /**
- * Префикс uri
- * @var string
- */
- protected $prefix;
- /**
- * Длина префикса в байтах
- * @var int
- */
- protected $length;
- /**
- * Конструктор
- * @param string $base
- */
- public function __construct($base)
- {
- $this->baseUrl = $base;
- $this->host = parse_url($base, PHP_URL_HOST);
- $this->prefix = parse_url($base, PHP_URL_PATH);
- $this->length = strlen($this->prefix);
- }
- /**
- * Проверка url на принадлежность форуму
- * @param mixed $url
- * @param string $defMarker
- * @param array $defArgs
- * @return string
- */
- public function validate($url, $defMarker, array $defArgs = [])
- {
- if (is_string($url)
- && parse_url($url, PHP_URL_HOST) === $this->host
- && ($route = $this->route('GET', rawurldecode(parse_url($url, PHP_URL_PATH))))
- && $route[0] === self::OK
- ) {
- return $url;
- } else {
- return $this->link($defMarker, $defArgs);
- }
- }
- /**
- * Возвращает ссылку на основании маркера
- * @param string $marker
- * @param array $args
- * @return string
- */
- public function link($marker = null, array $args = [])
- {
- $result = $this->baseUrl; //???? http и https
- if (is_string($marker) && isset($this->links[$marker])) {
- $s = $this->links[$marker];
- foreach ($args as $key => $val) {
- if ($key == '#') {
- $s .= '#' . rawurlencode($val); //????
- continue;
- }
- $s = preg_replace(
- '%\{' . preg_quote($key, '%') . '(?::[^{}]+)?\}%',
- rawurlencode($val),
- $s
- );
- }
- $s = preg_replace('%\[[^{}\[\]]*\{[^}]+\}[^{}\[\]]*\]%', '', $s);
- if (strpos($s, '{') === false) {
- $result .= str_replace(['[', ']'], '', $s);
- } else {
- $result .= '/';
- }
- } else {
- $result .= '/';
- }
- return $result;
- }
- /**
- * Метод определяет маршрут
- * @param string $method
- * @param string $uri
- * @return array
- */
- public function route($method, $uri)
- {
- $head = $method == 'HEAD';
- if (empty($this->methods[$method]) && (! $head || empty($this->methods['GET']))) {
- return [self::NOT_IMPLEMENTED];
- }
- if ($this->length) {
- if (0 === strpos($uri, $this->prefix)) {
- $uri = substr($uri, $this->length);
- } else {
- return [self::NOT_FOUND];
- }
- }
- $allowed = [];
- if (isset($this->statical[$uri])) {
- if (isset($this->statical[$uri][$method])) {
- return [self::OK, $this->statical[$uri][$method], []];
- } elseif ($head && isset($this->statical[$uri]['GET'])) {
- return [self::OK, $this->statical[$uri]['GET'], []];
- } else {
- $allowed = array_keys($this->statical[$uri]);
- }
- }
- $pos = strpos(substr($uri, 1), '/');
- $base = false === $pos ? $uri : substr($uri, 0, ++$pos);
- if (isset($this->dynamic[$base])) {
- foreach ($this->dynamic[$base] as $pattern => $data) {
- if (! preg_match($pattern, $uri, $matches)) {
- continue;
- }
- if (isset($data[$method])) {
- $data = $data[$method];
- } elseif ($head && isset($data['GET'])) {
- $data = $data['GET'];
- } else {
- $allowed += array_keys($data);
- continue;
- }
- $args = [];
- foreach ($data[1] as $key) {
- if (isset($matches[$key])) {
- $args[$key] = $matches[$key];
- }
- }
- return [self::OK, $data[0], $args];
- }
- }
- if (empty($allowed)) {
- return [self::NOT_FOUND];
- } else {
- return [self::METHOD_NOT_ALLOWED, $allowed];
- }
- }
- /**
- * Метод добавдяет маршрут
- * @param string|array $method
- * @param string $route
- * @param string $handler
- * @param string $marker
- */
- public function add($method, $route, $handler, $marker = null)
- {
- if (is_array($method)) {
- foreach ($method as $m) {
- $this->methods[$m] = 1;
- }
- } else {
- $this->methods[$method] = 1;
- }
- $link = $route;
- if (($pos = strpos($route, '#')) !== false) {
- $route = substr($route, 0, $pos);
- }
- if (false === strpbrk($route, '{}[]')) {
- if (is_array($method)) {
- foreach ($method as $m) {
- $this->statical[$route][$m] = $handler;
- }
- } else {
- $this->statical[$route][$method] = $handler;
- }
- } else {
- $data = $this->parse($route);
- if (false === $data) {
- throw new \Exception('Route is incorrect');
- }
- if (is_array($method)) {
- foreach ($method as $m) {
- $this->dynamic[$data[0]][$data[1]][$m] = [$handler, $data[2]];
- }
- } else {
- $this->dynamic[$data[0]][$data[1]][$method] = [$handler, $data[2]];
- }
- }
- if ($marker) {
- $this->links[$marker] = $link;
- }
- }
- /**
- * Метод разбирает динамический маршрут
- * @param string $route
- * @return array|false
- */
- protected function parse($route)
- {
- $parts = preg_split('%([\[\]{}/])%', $route, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
- $s = 1;
- $base = $parts[0];
- if ($parts[0] === '/') {
- $s = 2;
- $base .= $parts[1];
- }
- if (isset($parts[$s]) && $parts[$s] !== '/' && $parts[$s] !== '[') {
- $base = '/';
- }
- $pattern = '%^';
- $var = false;
- $first = false;
- $buffer = '';
- $args = [];
- $s = 0;
- foreach ($parts as $part) {
- if ($var) {
- switch ($part) {
- case '{':
- return false;
- case '}':
- $data = explode(':', $buffer, 2);
- if (! isset($data[1])) {
- $data[1] = '[^/\x00-\x1f]+';
- }
- if ($data[0] === '' || $data[1] === '' || is_numeric($data[0]{0})) {
- return false;
- }
- $pattern .= '(?P<' . $data[0] . '>' . $data[1] . ')';
- $args[] = $data[0];
- $var = false;
- $buffer = '';
- break;
- default:
- $buffer .= $part;
- }
- } elseif ($first) {
- switch ($part) {
- case '/':
- $first = false;
- $pattern .= preg_quote($part, '%');
- break;
- default:
- return false;
- }
- } else {
- switch ($part) {
- case '[':
- ++$s;
- $pattern .= '(?:';
- $first = true;
- break;
- case ']':
- --$s;
- if ($s < 0) {
- return false;
- }
- $pattern .= ')?';
- break;
- case '{':
- $var = true;
- break;
- case '}':
- return false;
- default:
- $pattern .= preg_quote($part, '%');
- }
- }
- }
- if ($var || $s) {
- return false;
- }
- $pattern .= '$%D';
- return [$base, $pattern, $args];
- }
- }
|