Route.php

<?php

namespace Lia\Http;

/**
 * A value-object for holding route information, with some convenience methods.
 *
 * Some behavior is documented in this class, but it's really all up to the calling code.
 */
class Route {

    /**
     * The router that returned this route. 
     */
    public ?Router $router = null;

    /**
     * Paramaters extracted from the url, based upon the route.
     *
     * @example `/blog/{slug}/` requested by `/blog/happy-cats/` would yield `['slug'=>'happy-cats']`
     */
    public array $paramaters;
    
    /**
     * Requested URL Path
     */
    public string $url;

    /**
     * Request method, like "GET" or "POST"
     */
    public string $method;

    /**
     * Original route pattern, like `/blog/{slug}/`
     */
    public string $originalPattern;

    /**
     * Full Regex used to check if the request url is valid, AFTER the testPattern has reduced the number of possibilities.
     */
    public string $regexPattern;

    /**
     * A simplified pattern for the route-selection algorithm. After this filter-step, $regexPattern filters to the final set of pattern matches.
     *
     * @example For `/blog/{slug}/`, `$testPattern = `blog/?/`
     */
    public string $testPattern;

    /**
     * Array of methods that are valid for this route. Such as GET, PUT, POST, DELETE, etc
     */
    public array $allowedMethods;

    /**
     * Callable or absolute file path.
     * The calling code is responsible for passing args.
     */
    public mixed $target;
    /**
     * Array of arguments to pass to $target
     * The calling code is responsible for passing args.
     */
    public array $args;

    //static public function new_route(string $pattern, string $url, string $method="GET"){
        //$route = new static();
        //$route->originalPattern = $pattern;
        //$route->url = $url;
        //$route->method = $method;
        //$route->allowedMethods = [$method];

    //}


    /**
     * All desired properties must be set explicitly one-by-one.
     */
    public function __construct(){}

    /**
     * Get a portion of the url.
     * @example for `/blog/cute-cats/`, part(1) returns `cute-cats`.
     *
     * @param int $index index of the url portion to return;
     * @return string `$index` portion of url, or null if `$index` iss too big
     */
    public function part(int $index): ?string {
        return explode('/',$this->url)[1+$index] ?? null;
    }
    /**
     * Get a named url paramater.
     *
     * @example for pattern `/blog/{slug}/`, requested by `/blog/cute-cats/`, `param('slug')` returns the 'cute-cats'
     *
     * @param $name string name of url paramater.
     * @return string named paramater from URL, or null if paramater doesn't exist.
     */
    public function param(string $name): ?string {
        return $this->paramaters[$name] ?? null;
    }
    /**
     * Get a named url paramater by zero-based index (instead of by name)
     * @example for pattern `/post/{category}/{slug}/`, requested by `/post/blog/cute-cats/`, `var(0)` returns 'blog' and `var(1)` returns 'cute-cats'
     *
     * @return string `$index` of a named paramater from the url, or null if `$index` is too big
     */
    public function var(int $index): ?string {
        return array_values($this->paramaters)[$index] ?? null;
    }

    /**
     * Check if target is callable, a file, or null
     *
     * @return string 'file', 'callable', or null if neither. Returns null if file does not exist.
     */
    public function type(): ?string {
        $t = $this->target;
        if ($this->isFile())return 'file';
        else if ($this->isCallable())return 'callable';
        else return null;
    }
    /**
     * Check if target is callable
     *
     * @return bool true if `is_callable($target)` is true. false otherwise.
     */
    public function isCallable(): bool{
        $t = $this->target;
        return is_callable($t);
    }
    /**
     * Check if target is a file
     *
     * @return bool true if target `is_string` AND `is_file()`. false otherwise.
     */
     public function isFile(): bool{
        $t = $this->target;
        return is_string($t) && is_file($t);
    }

    /**
     * Check if target is an executable file. 
     *
     * @return bool true if target `is_string()` AND `is_file()` AND `is_executable()`. false otherwise.
     */
     public function isExecutableFile(): bool{
        $t = $this->target;
        return is_string($t) && is_file($t) && is_executable($t);
    }

    /** 
     * Get file extension, if target is a file. null otherwise.
     *
     * @return string file extension of target, like 'php' or 'js' or 'jpeg', or null if target is not a file, or empty string if no extension.
     */
    public function fileExt(): ?string {
        if (!$this->isFile())return null;
        return pathinfo($this->target, PATHINFO_EXTENSION);
    }

    /**
     * Check if the route's originalPattern is static.
     *
     * @return bool true if the originalPattern has no named paramaters. false otherwise.
     */
    public function isStatic(): bool {
        return (count($this->paramaters)==0);
    }

    /**
     * Check if the route's originalPattern contains named parmaters.
     *
     * @return bool true if the originalPattern contains named paramaters. false otherwise.
     */
    public function isDynamic(): bool {
        return !$this->isStatic();
    }


}