2020-06-12 13:57:36 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Benzine\Router;
|
|
|
|
|
|
2020-07-27 01:31:04 +00:00
|
|
|
use Cache\Adapter\Chain\CachePoolChain;
|
2020-09-07 09:47:25 +00:00
|
|
|
use Doctrine\Common\Annotations\AnnotationReader;
|
|
|
|
|
use Doctrine\Common\Annotations\AnnotationRegistry;
|
2020-06-12 13:57:36 +00:00
|
|
|
use Monolog\Logger;
|
2020-06-18 17:24:31 +00:00
|
|
|
use Slim\App;
|
2020-06-12 13:57:36 +00:00
|
|
|
|
|
|
|
|
class Router
|
|
|
|
|
{
|
|
|
|
|
/** @var Route[] */
|
2020-07-31 21:10:39 +00:00
|
|
|
private array $routes = [];
|
2020-07-27 01:31:04 +00:00
|
|
|
private Logger $logger;
|
|
|
|
|
private CachePoolChain $cachePoolChain;
|
|
|
|
|
private int $cacheTTL = 60;
|
2020-06-12 13:57:36 +00:00
|
|
|
|
2020-08-06 17:48:55 +00:00
|
|
|
private bool $routesArePopulated = false;
|
|
|
|
|
|
2020-09-01 03:15:02 +00:00
|
|
|
public function __construct(Logger $logger, CachePoolChain $cachePoolChain)
|
2020-06-12 13:57:36 +00:00
|
|
|
{
|
2020-07-27 01:31:04 +00:00
|
|
|
$this->logger = $logger;
|
|
|
|
|
$this->cachePoolChain = $cachePoolChain;
|
2020-06-12 13:57:36 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-07 09:47:25 +00:00
|
|
|
public function loadRoutesFromAnnotations(
|
|
|
|
|
array $controllerPaths,
|
|
|
|
|
string $baseNamespace = null
|
|
|
|
|
): void {
|
|
|
|
|
AnnotationRegistry::registerLoader('class_exists');
|
|
|
|
|
|
|
|
|
|
$reader = new AnnotationReader();
|
|
|
|
|
|
|
|
|
|
foreach ($controllerPaths as $controllerPath) {
|
|
|
|
|
foreach (new \RecursiveDirectoryIterator($controllerPath) as $controllerFile) {
|
|
|
|
|
if ($controllerFile->isDot() || !$controllerFile->isFile() || !$controllerFile->isReadable()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$fileClassName = str_replace('.php', '', $controllerFile->getFilename());
|
|
|
|
|
$expectedClasses = [
|
|
|
|
|
$baseNamespace . '\\Controllers\\' . $fileClassName,
|
|
|
|
|
'Benzine\\Controllers\\' . $fileClassName,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($expectedClasses as $expectedClass) {
|
|
|
|
|
if (!class_exists($expectedClass)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rc = new \ReflectionClass($expectedClass);
|
|
|
|
|
if ($rc->isAbstract()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($rc->getMethods() as $method) {
|
|
|
|
|
if (!$method->isPublic()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$routeAnnotation = $reader->getMethodAnnotation($method, \Benzine\Annotations\Route::class);
|
|
|
|
|
if (!($routeAnnotation instanceof \Benzine\Annotations\Route)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach($routeAnnotation->methods as $httpMethod) {
|
|
|
|
|
$newRoute = new Route($this->logger);
|
|
|
|
|
|
|
|
|
|
$newRoute
|
|
|
|
|
->setHttpMethod($httpMethod)
|
|
|
|
|
->setRouterPattern('/' . ltrim($routeAnnotation->path, '/'))
|
|
|
|
|
->setCallback($method->class . ':' . $method->name)
|
|
|
|
|
->setWeight($routeAnnotation->weight);
|
|
|
|
|
|
|
|
|
|
foreach ($routeAnnotation->domains as $domain) {
|
|
|
|
|
$newRoute->addValidDomain($domain);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->addRoute($newRoute);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-12 13:57:36 +00:00
|
|
|
public function weighRoutes(): Router
|
|
|
|
|
{
|
|
|
|
|
$allocatedRoutes = [];
|
|
|
|
|
if (is_array($this->routes) && count($this->routes) > 0) {
|
|
|
|
|
uasort($this->routes, function (Route $a, Route $b) {
|
|
|
|
|
return $a->getWeight() > $b->getWeight();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
foreach ($this->routes as $index => $route) {
|
2020-07-31 21:10:39 +00:00
|
|
|
if (($route->isInContainedInValidDomains() || !$route->hasValidDomains())
|
|
|
|
|
&& !isset($allocatedRoutes[$route->getHttpMethod().$route->getRouterPattern()])) {
|
2020-06-12 13:57:36 +00:00
|
|
|
$allocatedRoutes[$route->getHttpMethod().$route->getRouterPattern()] = true;
|
|
|
|
|
} else {
|
|
|
|
|
unset($this->routes[$index]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function populateRoutes(App $app)
|
|
|
|
|
{
|
2020-08-06 17:48:55 +00:00
|
|
|
if ($this->routesArePopulated) {
|
|
|
|
|
return $app;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-12 13:57:36 +00:00
|
|
|
$this->weighRoutes();
|
|
|
|
|
if (count($this->routes) > 0) {
|
2020-07-31 21:10:39 +00:00
|
|
|
foreach ($this->getRoutes() as $route) {
|
2020-06-12 13:57:36 +00:00
|
|
|
$app = $route->populateRoute($app);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-06 17:48:55 +00:00
|
|
|
$this->routesArePopulated = true;
|
|
|
|
|
|
2020-06-12 13:57:36 +00:00
|
|
|
return $app;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function addRoute(Route $route)
|
|
|
|
|
{
|
|
|
|
|
$this->routes[$route->getUniqueIdentifier()] = $route;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return Route[]
|
|
|
|
|
*/
|
|
|
|
|
public function getRoutes()
|
|
|
|
|
{
|
|
|
|
|
return $this->routes;
|
|
|
|
|
}
|
2020-07-27 01:31:04 +00:00
|
|
|
|
|
|
|
|
public function loadCache(): bool
|
|
|
|
|
{
|
|
|
|
|
$time = microtime(true);
|
|
|
|
|
$cacheItem = $this->cachePoolChain->getItem('routes');
|
|
|
|
|
if (!$cacheItem || null === $cacheItem->get()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$this->routes = $cacheItem->get();
|
|
|
|
|
$this->logger->debug(sprintf('Loaded routes from Cache in %sms', number_format((microtime(true) - $time) * 1000, 2)));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function cache(): Router
|
|
|
|
|
{
|
|
|
|
|
$routeItem = $this->cachePoolChain
|
|
|
|
|
->getItem('routes')
|
|
|
|
|
->set($this->getRoutes())
|
|
|
|
|
->expiresAfter($this->cacheTTL)
|
|
|
|
|
;
|
|
|
|
|
$this->cachePoolChain->save($routeItem);
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
2020-06-12 13:57:36 +00:00
|
|
|
}
|