canax/router

View on GitHub
src/Route/RouteMatcher.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

namespace Anax\Route;

use Anax\Commons\ContainerInjectableInterface;
use Anax\Route\Exception\ConfigurationException;

/**
 * Matching a incoming path to see it it matches a route.
 */
class RouteMatcher
{
    /**
     * @var null|array   $arguments     arguments for the callback, extracted
     *                                  from path
     * @var string       $methodMatched the matched method.
     * @var string       $pathMatched   the matched path.
     */
    public $arguments = [];
    public $methodMatched;
    public $pathMatched;



    /**
     * Check if part of route is a argument and optionally match type
     * as a requirement {argument:type}.
     *
     * @param string $rulePart   the rule part to check.
     * @param string $queryPart  the query part to check.
     * @param array  &$args      add argument to args array if matched
     *
     * @return boolean
     */
    private function checkPartAsArgument($rulePart, $queryPart, &$args)
    {
        if (substr($rulePart, -1) == "}"
            && !is_null($queryPart)
        ) {
            $part = substr($rulePart, 1, -1);
            $pos = strpos($part, ":");
            $type = null;
            if ($pos !== false) {
                $type = substr($part, $pos + 1);
                if (! $this->checkPartMatchingType($queryPart, $type)) {
                    return false;
                }
            }
            $args[] = $this->typeConvertArgument($queryPart, $type);
            return true;
        }
        return false;
    }



    /**
     * Check if value is matching a certain type of values.
     *
     * @param string $value   the value to check.
     * @param array  $type    the expected type to check against.
     *
     * @return boolean
     */
    private function checkPartMatchingType($value, $type)
    {
        switch ($type) {
            case "digit":
                return ctype_digit($value);
                break;

            case "hex":
                return ctype_xdigit($value);
                break;

            case "alpha":
                return ctype_alpha($value);
                break;

            case "alphanum":
                return ctype_alnum($value);
                break;

            default:
                return false;
        }
    }



    /**
     * Check if value is matching a certain type and do type
     * conversion accordingly.
     *
     * @param string $value   the value to check.
     * @param array  $type    the expected type to check against.
     *
     * @return boolean
     */
    private function typeConvertArgument($value, $type)
    {
        switch ($type) {
            case "digit":
                return (int) $value;
                break;

            default:
                return $value;
        }
    }



    /**
     * Match part of rule and query.
     *
     * @param string $rulePart   the rule part to check.
     * @param string $queryPart  the query part to check.
     * @param array  &$args      add argument to args array if matched
     *
     * @return boolean
     */
    private function matchPart($rulePart, $queryPart, &$args)
    {
        $match = false;
        $first = isset($rulePart[0]) ? $rulePart[0] : '';
        switch ($first) {
            case '*':
                $match = true;
                break;

            case '{':
                $match = $this->checkPartAsArgument($rulePart, $queryPart, $args);
                break;

            default:
                $match = ($rulePart == $queryPart);
                break;
        }
        return $match;
    }



    /**
     * Check if the request method matches.
     *
     * @param string $method    as request method.
     * @param string $supported as request methods that are valid.
     *
     * @return boolean true if request method matches
     */
    private function matchRequestMethod(
        string $method = null,
        array $supported = null
    ) {
        if ($supported && !in_array($method, $supported)) {
            return false;
        }

        return true;
    }



    /**
     * Check if the route matches a query and request method.
     *
     * @param string $mount           of the current route being matched.
     * @param string $relativePath    of the current route being matched.
     * @param string $absolutePath    of the current route being matched.
     * @param string $query           to match against
     * @param array  $methodSupported as supported request method
     * @param string $method          as request method
     *
     * @return boolean true if query matches the route
     */
    public function match(
        ?string $mount,
        ?string $relativePath,
        ?string $absolutePath,
        ?string $query,
        ?array $methodSupported,
        ?string $method
    ) {
        $this->arguments = [];
        $this->methodMatched = null;
        $this->pathMatched = null;

        if (!$this->matchRequestMethod($method, $methodSupported)) {
            return false;
        }

        // Is a null path  - mounted on empty, or mount path matches
        // initial query.
        $charsToMatch = strlen($mount);
        $matchedQueryToMountPath = strncmp($query, $mount, $charsToMatch) === 0 ? true : false;
        $nextChar = substr($query, $charsToMatch, 1);
        if (is_null($relativePath)
            && (
                empty($mount)
                || (
                    $matchedQueryToMountPath && ($nextChar === "/" || $nextChar === "")
                )
            )
        ) {
            $matchedChars = strlen($mount);
            $nextChar = substr($query, $matchedChars, 1);
            //var_dump($nextChar);
            // echo "TRUE";
            //echo "\nMATCHED: $query '$nextChar' == $mount (" . strlen($mount) . ")";

            $this->methodMatched = $method;
            $this->pathMatched = $query;
            return true;
        }

        // Check all parts to see if they matches
        $ruleParts  = explode('/', $absolutePath);
        $queryParts = explode('/', $query);
        $ruleCount = max(count($ruleParts), count($queryParts));
        $args = [];

        for ($i = 0; $i < $ruleCount; $i++) {
            $rulePart  = isset($ruleParts[$i])  ? $ruleParts[$i]  : null;
            $queryPart = isset($queryParts[$i]) ? $queryParts[$i] : null;

            if ($rulePart === "**") {
                break;
            }

            if (!$this->matchPart($rulePart, $queryPart, $args)) {
                return false;
            }
        }

        $this->arguments = $args;
        $this->methodMatched = $method;
        $this->pathMatched = $query;
        return true;
    }
}