NeroReflex/Gishiki

View on GitHub
src/Core/Router/MatchableRouteTrait.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php
/**************************************************************************
Copyright 2017 Benato Denis

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 *****************************************************************************/

namespace Gishiki\Core\Router;

use Gishiki\Algorithms\Strings\SimpleLexer;

/**
 * Working implementation of RouteInterface ready to be implemented by a route.
 *
 * Written to ease the process of extending the Router component
 * with routes written by third-parties and reducing my own codebase.
 *
 * @see RouteInterface Documentation
 *
 * @author Benato Denis <benato.denis96@gmail.com>
 */
trait MatchableRouteTrait
{
    /**
     * uri => string (begins with / example: "/do_something")
     * status => integer (the HTTP return status code, used for error handling)
     * verbs => string[] (the array of string of allowed/matchable HTTP methods)
     *
     * @var array the route definition
     */
    protected $route = [];

    public function getURI() : string
    {
        return $this->route["uri"];
    }

    public function getStatus() : int
    {
        return $this->route["status"];
    }

    public function getMethods() : array
    {
        return $this->route["verbs"];
    }

    /**
     * Check if the given URL matches the route URI.
     * Also fill GET and expressions parameters.
     *
     * @param  string $method      the HTTP method used on the Request
     * @param  string $url         the URL invoked
     * @param  array  $matchedExpr will be filled with an associative array of paramName => urlValue
     * @param  array  $matchedGet  will be filled with an associative array of paramName => urlValue (reserved for get parameters)
     * @return bool true if the given method and url matches this route
     */
    public function matches($method, $url, array &$matchedExpr, array &$matchedGet) : bool
    {
        return ((in_array($method, $this->getMethods())) && (static::matchURI($this->getURI(), $url, $matchedExpr, $matchedGet)));
    }

    /**
     * Check if a piece of URL matches a parameter of the given type.
     * List of types:
     *  - 0 unsigned integer
     *  - 1 signed integer
     *  - 2 float
     *  - 3 string
     *  - 4 email
     *
     * @param $urlSplit  string the piece of URL to be checked
     * @param $type      int    the type of accepted parameter
     * @throws RouterException  the given type is invalid
     * @return bool true on success, false otherwise
     */
    protected static function paramCheck($urlSplit, $type) : bool
    {
        if ((!is_int($type)) || ($type < 0) || ($type > 4)) {
            throw new RouterException("Invalid parameter type", 100);
        }

        $result = false;

        switch ($type) {
            case 0:
                $result = SimpleLexer::isUnsignedInteger($urlSplit);
                break;

            case 1:
                $result = SimpleLexer::isSignedInteger($urlSplit);
                break;

            case 2:
                $result = SimpleLexer::isFloat($urlSplit);
                break;

            case 3:
                $result = SimpleLexer::isString($urlSplit);
                break;

            case 4:
                $result = SimpleLexer::isEmail($urlSplit);
                break;
        }

        return $result;
    }

    /**
     * Check weather a piece of an URL matches the corresponding piece of URI
     *
     * @param  string $uriSplit the slice of URI to be checked
     * @param  string $urlSplit the slice of URL to be checked
     * @param  mixed  $params   used to register the correspondence (if any)
     * @return bool  true if the URL slice matches the URI slice, false otherwise
     */
    protected static function matchCheck($uriSplit, $urlSplit, array &$params) : bool
    {
        $result = false;

        if ((strlen($uriSplit) >= 7) && ($uriSplit[0] == '{') && ($uriSplit[strlen($uriSplit) - 1] == '}')) {
            $uriSplitRev = substr($uriSplit, 1, strlen($uriSplit) - 2);
            $uriSplitExploded = explode(':', $uriSplitRev);
            $uriParamType = strtolower($uriSplitExploded[1]);

            $type = null;

            if (strcmp($uriParamType, 'uint') == 0) {
                $type = 0;
            } elseif (strcmp($uriParamType, 'int') == 0) {
                $type = 1;
            } elseif ((strcmp($uriParamType, 'str') == 0) || (strcmp($uriParamType, 'string') == 0)) {
                $type = 3;
            } elseif (strcmp($uriParamType, 'float') == 0) {
                $type = 2;
            } elseif ((strcmp($uriParamType, 'email') == 0) || (strcmp($uriParamType, 'mail') == 0)) {
                $type = 4;
            }

            //check the url piece against one of the given model
            if (self::paramCheck($urlSplit, $type)) {
                //matched url piece with the correct type: "1" checked against a string has to become 1
                $urlSplitCType = $urlSplit;
                $urlSplitCType = (($type == 0) || ($type == 1)) ? intval($urlSplit) : $urlSplitCType;
                $urlSplitCType = ($type == 2) ? floatval($urlSplit) : $urlSplitCType;

                $result = true;
                $params[$uriSplitExploded[0]] = $urlSplitCType;
            }
        } elseif (strcmp($uriSplit, $urlSplit) == 0) {
            $result = true;
        }

        return  $result;
    }

    /**
     * Check if the given URL matches the given URI.
     * $matchedExpr is given as an associative array: name => value
     *
     * @param string $uri         the URI to be matched against the given URL
     * @param string $url         the URL to be matched
     * @param array  $matchedExpr the variable to be filled with matched parameters
     * @param array  $matchedGet  the variable to be filled with GET options
     * @return bool true if the URL matches the URI, false otherwise
     */
    public static function matchURI($uri, $url, array &$matchedExpr, array &$matchedGet) : bool
    {
        if (!is_string($url)) {
            throw new \InvalidArgumentException("The URL must be given as a valid string");
        }

        if (!is_string($uri)) {
            throw new \InvalidArgumentException("The URI must be given as a valid string");
        }

        $matchedExpr = [];
        $result = true;

        $urlSlices = explode('/', $url);
        $uriSlices = explode('/', $uri);

        $uriSlicesCount = count($uriSlices);
        $urlSlicesCount = count($urlSlices);
        if (($uriSlicesCount != $urlSlicesCount) && ($uriSlicesCount != ($urlSlicesCount - 1))) {
            return false;
        }

        for ($i = 0; ($i < $uriSlicesCount) && ($result); $i++) {
            //try matching the current URL slice with the current URI slice
            $result = self::matchCheck($uriSlices[$i], $urlSlices[$i], $matchedExpr);
        }

        $result = ($result) &&
            ((($uriSlicesCount == ($urlSlicesCount - 1)) && ($urlSlices[$urlSlicesCount - 1][0] == '?'))
                || ($uriSlicesCount == $urlSlicesCount));

        $matchedGet = [];
        if (($result) && ($uriSlicesCount == ($urlSlicesCount - 1))) {
            //parse the string containing GET parameters, without the beginning '?' character
            parse_str(substr($urlSlices[$urlSlicesCount - 1], 1), $matchedGet);
        }

        return $result;
    }
}