Files
2026-07-01 16:44:17 +03:00

413 lines
10 KiB
PHP

<?php namespace ZN\Routing;
/**
* ZN PHP Web Framework
*
* "Simplicity is the ultimate sophistication." ~ Da Vinci
*
* @package ZN
* @license MIT [http://opensource.org/licenses/MIT]
* @author Ozan UYKUN [ozan@znframework.com]
*/
use ZN\Base;
use ZN\Lang;
use ZN\Helper;
use ZN\Kernel;
use ZN\Config;
use ZN\Request;
use ZN\Response;
use ZN\Datatype;
use ZN\Singleton;
use ZN\Request\URI;
use ZN\ErrorHandling\Errors;
class Route extends FilterProperties implements RouteInterface
{
use PropertyCreatorTrait;
/**
* Run direct show 404 page
*
* @var bool
*/
protected $direct = false;
/**
* Keeps Container Data
*
* @var bool
*/
protected $container, $useRunMethod = false;
/**
* Keeps Array Data
*
* @var array
*/
protected $route = [], $routes = [], $status = [], $setFilters = [], $recursion = [], $recursionFilters = [], $allFilterKeys = [], $lines;
/**
* Keeps routing config
*
* @var array
*/
protected $getConfig;
/**
* Magic Constructor
*
* Get route configuration.
*
* @codeCoverageIgnore
*/
public function __construct()
{
$this->getConfig = Config::get('Routing');
}
/**
* Magic Destructor
*
* @codeCoverageIgnore
*/
public function __destruct()
{
if( $this->useRunMethod === true && empty($this->status) )
{
$this->redirectShow404(CURRENT_CFUNCTION);
}
}
/**
* Run direct show 404 page
*
* @return self
*/
public function direct()
{
$this->direct = true;
return $this;
}
/**
* Route Show404
*
* @param string $controllerAndMethod
*/
public function show404(string $controllerAndMethod)
{
if( $this->direct === true )
{
Config::set('Routing', 'runWithoutRedirect', $controllerAndMethod);
return;
}
if( empty( $this->route ) )
{
$s404 = '404';
$this->change('{start}404{end}');
}
Config::set('Routing', 'show404', $s404 ?? $this->route);
$this->uri($controllerAndMethod, false);
}
/**
* Container
*
* @param callable $callback
*/
public function container(callable $callback)
{
$current = count(debug_backtrace(2));
# 6.55.4.32[added] recursion container
$recursion = $this->recursion;
$recursionFilters = $this->recursionFilters;
# Calculates the number of inclusive recursion.
# Rearranges the filters according to this calculation.
if( count($recursion) > 1 ) for( $i = count($recursion) - 1; $i >= 0; $i-- )
{
if( $recursion[$i] < $current )
{
$this->filters = array_merge($recursionFilters[$i], $this->filters);
break;
}
}
$this->recursion[] = $current;
$this->recursionFilters[] = $this->filters;
$callback();
}
/**
* Apply Filters
*/
public function filter()
{
foreach( array_unique($this->allFilterKeys) as $filter )
{
new Filter($filter, $this->setFilters, $this->getConfig);
}
}
/**
* Sets old URI
*
* @param string $path = NULL
*/
public function uri(?string $path = NULL)
{
$path = $this->setPath($path, $routeConfig);
$container = debug_backtrace(2)[2];
$this->setContainerFilters($container);
$this->setFilters($path);
$this->changeRouteURI($path, $routeConfig);
}
/**
* Sets all route
*/
public function all()
{
if( ! empty($this->routes) )
{
$config = $this->getConfig;
Config::set('Routing', 'changeUri', array_merge($this->routes['changeUri'], $config['changeUri']));
}
}
/**
* Change URI
*
* @param string $route
*
* @return Route
*/
public function change(string $route) : Route
{
$route = trim($route, '/');
$return = true;
$routeSegment = explode('/', $route);
// Database Routing
$route = $this->database($route, $routeSegment, $return);
if( empty($return) )
{
$this->route = NULL;
}
else
{
$this->route = $route;
}
return $this;
}
/**
* Redirect Show 404
*
* @param string $function
* @param string $lang
* @param report
*
* @codeCoverageIgnore
*/
public function redirectShow404(string $function, string $lang = 'callUserFuncArrayError', string $report = 'SystemCallUserFuncArrayError')
{
if( ! $routeShow404 = $this->getConfig['show404'] )
{
Helper::report('Error', Lang::default('ZN\CoreDefaultLanguage')::select('Error', $lang, $function), $report);
exit(Errors::message('Error', $lang, $function));
}
else
{
Response::redirect($routeShow404);
}
}
/**
* Protected set container filters
*/
protected function setContainerFilters($container)
{
$line = $container['line'];
$func = $container['function'];
if( $func === 'container' )
{
if( isset($this->lines[$line]) )
{
$this->filters = array_merge($this->lines[$line], $this->filters); // @codeCoverageIgnore
}
else
{
$this->lines[$line] = $this->filters;
}
}
}
/**
* Protected set path
*/
protected function setPath($path, &$routeConfig)
{
$path = rtrim($path ?? '', '/');
$routeConfig = $this->getConfig;
if( ! strstr($path, '/') )
{
$path = Base::suffix($path) . $routeConfig['openFunction'];
}
return $path;
}
/**
* Protected change route uri
*/
protected function changeRouteURI($path, $routeConfig)
{
if( empty($this->route) )
{
return false;
}
$configPatternType = $routeConfig['patternType'];
if( $configPatternType === 'classic' )
{
$routeString = Singleton::class('ZN\Regex')->special2classic($this->route);
}
// @codeCoverageIgnoreStart
elseif( $configPatternType === 'special' )
{
$routeString = $this->route;
}
// @codeCoverageIgnoreEnd
# 5.3.21[edited] is empty
if( trim($routeString ?? '', '/') )
{
$this->routes['changeUri'][$routeString] = $this->getStringRoute($path, $this->route)[$this->route];
}
$this->route = NULL;
}
/**
* Protected routing database
*/
protected function database($route, $routeSegment, &$return)
{
return preg_replace_callback
(
'/\[(?<table>\w+|\.)\:(?<column>\w+|\.)(\s*\,\s*(?<separator>json|serial|separator)(\:(?<key>.*?))*)*\]/i',
function($match) use (&$count, &$return, &$routeSegment)
{
$count = key(preg_grep('/' . preg_quote($match[0], '/') . '/', $routeSegment));
unset($routeSegment[$count]);
$decoder = $match['separator'] ?? NULL;
$value = $val = URI::segment($count + 1);
$column = $select = $match['column'];
$dbClass = Singleton::class('ZN\Database\DB');
// @codeCoverageIgnoreStart
# Json, Serial or Separator
if( $decoder !== NULL )
{
$column .= ' like';
$value = $dbClass->like($value, 'inside');
}
// @codeCoverageIgnoreEnd
$return = $dbClass->select($select)->where($column, $value)->get($match['table'])->value();
// @codeCoverageIgnoreStart
# Json, Serial or Separator
if( $decoder !== NULL )
{
$row = $match['key'] ?? Lang::get();
$rows = $decoder::decode($return);
$rowsArray = $decoder::decodeArray($return);
$return = $rows->$row ?? NULL;
# Current Lang Manipulation
if( $return !== $value && in_array($val, $rowsArray) )
{
$arrayTransform = array_flip($rowsArray);
$newRow = $arrayTransform[$val];
$return = $rows->$newRow;
Lang::set($newRow);
}
}
// @codeCoverageIgnoreEnd
return $return;
},
$route
);
}
/**
* Protected Filter
*/
protected function setFilters($path)
{
$lowerPath = strtolower($path ?? '');
$filterKeys = array_keys($this->filters);
$this->allFilterKeys = array_merge($this->allFilterKeys, $filterKeys);
foreach( $filterKeys as $type ) if( isset($this->filters[$type]) )
{
$this->setFilters[$type . 's'][$lowerPath][$type] = $this->filters[$type];
}
$this->filters = [];
}
/**
* Protected String Route
*/
protected function getStringRoute($functionName, $route)
{
preg_match_all('/\:\w+/', $route ?? '', $match);
$newMatch = [];
$matchAll = $match[0] ?? [];
foreach( $matchAll as $key => $val )
{
$key++; // @codeCoverageIgnore
$newMatch[] = "$$key"; // @codeCoverageIgnore
}
$changeRoute = str_replace($matchAll, $newMatch, $route ?? '');
$changeRoute = str_replace(Datatype::divide($route, '/'), $functionName, $changeRoute);
$route = [$route => $changeRoute];
return $route;
}
}