- Merged multiple sub-libraries
This commit is contained in:
parent
767152e0c3
commit
cee97fca25
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Breadcrumb\Attribute\Method;
|
||||
|
||||
use Notes\Route\Attribute\Method\Route;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)]
|
||||
class Breadcrumb implements \Notes\Attribute {
|
||||
|
||||
public Route $routeAnnotation;
|
||||
|
||||
public bool $current = false;
|
||||
|
||||
public function __construct(
|
||||
public string $parent = "",
|
||||
public string $icon = "",
|
||||
public string $lang = "",
|
||||
public string $route = "",
|
||||
public array $methods = [],
|
||||
public string $class = "",
|
||||
public string $classMethod = "",
|
||||
) {}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php namespace Notes\Breadcrumb;
|
||||
|
||||
use Notes\ObjectReflection,
|
||||
Notes\ObjectResolver;
|
||||
|
||||
use Notes\Route\{ RouteFetcher, Attribute as RouteAttribute };
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use RuntimeException, DirectoryIterator, Generator;
|
||||
|
||||
class Breadcrumb extends RouteFetcher {
|
||||
|
||||
public function getRouteTree(\Notes\Route\Attribute\Method\Route $route) : array
|
||||
{
|
||||
$tree = [];
|
||||
|
||||
$routes = $this->compile([
|
||||
'object' => [ RouteAttribute\Object\Route::class ],
|
||||
'method' => [ RouteAttribute\Method\Route::class ],
|
||||
]);
|
||||
|
||||
$crumbs = $this->compile([
|
||||
'method' => [ Attribute\Method\Breadcrumb::class ],
|
||||
]);
|
||||
|
||||
while( $route ) {
|
||||
$crumb = $this->getBreadcrumbAttribute($crumbs, $route);
|
||||
|
||||
if ($crumb) {
|
||||
empty($tree) && $crumb->current = true;
|
||||
|
||||
$tree[$route->name] = $crumb;
|
||||
|
||||
$route = $this->getRouteAttribute($routes, $crumb);
|
||||
}
|
||||
else {
|
||||
$route = null;
|
||||
}
|
||||
};
|
||||
|
||||
return array_reverse($tree);
|
||||
}
|
||||
|
||||
protected function getRouteAttribute(array $list, Attribute\Method\Breadcrumb $crumb) : null|RouteAttribute\Method\Route
|
||||
{
|
||||
if ($crumb->parent ?? null) {
|
||||
foreach ($list as $route) {
|
||||
if ($crumb->parent === $route->name) {
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getBreadcrumbAttribute(array $list, RouteAttribute\Method\Route $route) : null|Attribute\Method\Breadcrumb
|
||||
{
|
||||
foreach($list as $crumb) {
|
||||
if ($crumb->class === $route->class && $crumb->classMethod === $route->classMethod) {
|
||||
$crumb->routeAnnotation = $route;
|
||||
|
||||
return $crumb;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\CLI\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
||||
class Command implements \Notes\Attribute {
|
||||
|
||||
public function __construct(
|
||||
public string $description,
|
||||
public null|string $command = null,
|
||||
public string|\Closure|null $default = null,
|
||||
public null|string $version = null,
|
||||
) {}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\CLI\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
|
||||
class Option extends \Mcnd\CLI\Option implements \Notes\Attribute {
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
<?php namespace Notes\CLI;
|
||||
|
||||
use Kash\HandleCacheTrait;
|
||||
use Mcnd\CLI\Command;
|
||||
use Mcnd\CLI\CommandStack;
|
||||
use Mcnd\CLI\Option;
|
||||
use Notes\ObjectResolver;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use RuntimeException, DirectoryIterator, Generator, Closure;
|
||||
|
||||
class CommandFetcher {
|
||||
use HandleCacheTrait;
|
||||
|
||||
protected array $folderList;
|
||||
|
||||
protected Closure $callback;
|
||||
|
||||
public array $defaultMethods = [ 'GET', 'POST' ];
|
||||
|
||||
protected array $annotations;
|
||||
|
||||
public function __construct( ?array $folderList = null, ?array $annotations = null, ? CacheInterface $cache = null)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
|
||||
if ($folderList !== null) {
|
||||
$this->folderList = $folderList;
|
||||
}
|
||||
|
||||
if ($annotations !== null) {
|
||||
$this->annotations = $annotations;
|
||||
}
|
||||
else {
|
||||
$this->annotations = [
|
||||
'object' => [ Attribute\Command::class, Attribute\Option::class ],
|
||||
'method' => [ Attribute\Command::class, Attribute\Option::class ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function addFolder($folder) : void
|
||||
{
|
||||
$this->folderList[] = $folder;
|
||||
}
|
||||
|
||||
public function setFolderList(array $list) : void
|
||||
{
|
||||
$this->folderList = $list;
|
||||
}
|
||||
|
||||
public function scan(? array $folders = null) : Generator
|
||||
{
|
||||
foreach($folders ?: $this->folderList as $namespace => $folder) {
|
||||
if ( ! file_exists($folder) ) {
|
||||
throw new RuntimeException(sprintf("Folder `%s` can not be found or scanned", $folder));
|
||||
}
|
||||
|
||||
foreach (new DirectoryIterator($folder) as $fileinfo) {
|
||||
if ( ! $fileinfo->isDot() ) {
|
||||
if ( $fileinfo->isDir() ) {
|
||||
foreach($this->scan([ "{$namespace}\\" . $fileinfo->getBasename() => $fileinfo->getPathname() ]) as $ns2 => $fi2) {
|
||||
yield $ns2 => $fi2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield $namespace => $fileinfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function compile() : CommandStack
|
||||
{
|
||||
return $this->handleCaching(substr(md5(serialize($this->annotations)), 0, 7), function() : CommandStack {
|
||||
$stack = new CommandStack();
|
||||
|
||||
$classCmd = [];
|
||||
|
||||
foreach($this->scan() as $namespace => $file) {
|
||||
if ( $file->getExtension() !== "php" ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$basename = $file->getBasename(".php");
|
||||
|
||||
$class = $this->generateClassname($basename, $namespace);
|
||||
|
||||
$objectResolver = new ObjectResolver($class, false, true, false, false);
|
||||
|
||||
$attributes = $objectResolver->getAttributeListFromClassname( $this->annotations['object'], false ) ;
|
||||
|
||||
$commandList = array_filter($attributes, fn($e) => $e instanceof Attribute\Command);
|
||||
|
||||
ksort($commandList);
|
||||
|
||||
if ($commandList) {
|
||||
$fullCommand = [];
|
||||
|
||||
# Build full command token
|
||||
foreach($commandList as $cmd) {
|
||||
if ($cmd->command) {
|
||||
$fullCommand[] = $cmd->command;
|
||||
}
|
||||
}
|
||||
|
||||
$fullCommand = implode(' ', $fullCommand);
|
||||
|
||||
$commandHeader = new Command(name: $fullCommand, description: end($commandList)->description);
|
||||
$commandHeader->pushOption(array_filter($attributes, fn($e) => $e instanceof Attribute\Option));
|
||||
|
||||
$stack->append($commandHeader);
|
||||
|
||||
foreach($objectResolver->getAttributeListFromClassname( $this->annotations['method'], false ) as $method => $commands) {
|
||||
if (is_array($commands) ) {
|
||||
$commandAttribute = array_filter($commands, fn($e) => $e instanceof Attribute\Command)[0] ?? null;
|
||||
$name = implode(' ', array_filter( [ $fullCommand, $commandAttribute->command ?? $method ]));
|
||||
$commandFunction = new Command(name: $name, description: $commandAttribute->description, parent: $commandHeader, callback: "$class::$method");
|
||||
$stack->append($commandFunction);
|
||||
|
||||
$commandFunction->pushOption(array_filter($commands, fn($e) => $e instanceof Attribute\Option));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $stack;
|
||||
});
|
||||
}
|
||||
|
||||
protected function generateClassname($file, $namespace)
|
||||
{
|
||||
return "\\$namespace\\$file";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Cronard\Attribute\Method;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)]
|
||||
class Cronard implements \Notes\Attribute {
|
||||
public function __construct(
|
||||
public string $cron,
|
||||
public null|string $method = null,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Cronard\Attribute\Object;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
|
||||
class Cronard implements \Notes\Attribute {
|
||||
public function __construct(
|
||||
public string $cron,
|
||||
public string $method = "__invoke",
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php namespace Notes\Cronard;
|
||||
|
||||
use Kash\HandleCacheTrait;
|
||||
use Notes\ObjectResolver;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use RuntimeException, DirectoryIterator, Generator, Closure;
|
||||
|
||||
class TaskFetcher {
|
||||
use HandleCacheTrait;
|
||||
|
||||
protected array $folderList;
|
||||
|
||||
protected Closure $callback;
|
||||
|
||||
public array $defaultMethods = [ 'GET', 'POST' ];
|
||||
|
||||
protected array $annotations;
|
||||
|
||||
public function __construct( ?array $folderList = null, ?array $annotations = null, ? CacheInterface $cache = null)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
|
||||
if ($folderList !== null) {
|
||||
$this->folderList = $folderList;
|
||||
}
|
||||
|
||||
if ($annotations !== null) {
|
||||
$this->annotations = $annotations;
|
||||
}
|
||||
else {
|
||||
$this->annotations = [
|
||||
'method' => [ Attribute\Method\Cronard::class ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function addFolder($folder) : void
|
||||
{
|
||||
$this->folderList[] = $folder;
|
||||
}
|
||||
|
||||
public function setFolderList(array $list) : void
|
||||
{
|
||||
$this->folderList = $list;
|
||||
}
|
||||
|
||||
public function scan(? array $folders = null) : Generator
|
||||
{
|
||||
foreach($folders ?: $this->folderList as $namespace => $folder) {
|
||||
if ( ! file_exists($folder) ) {
|
||||
throw new RuntimeException(sprintf("Folder `%s` can not be found or scanned", $folder));
|
||||
}
|
||||
|
||||
foreach (new DirectoryIterator($folder) as $fileinfo) {
|
||||
if ( ! $fileinfo->isDot() ) {
|
||||
if ( $fileinfo->isDir() ) {
|
||||
foreach($this->scan([ "{$namespace}\\" . $fileinfo->getBasename() => $fileinfo->getPathname() ]) as $ns2 => $fi2) {
|
||||
yield $ns2 => $fi2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield $namespace => $fileinfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function compile() : array
|
||||
{
|
||||
return $this->handleCaching(substr(md5(serialize($this->annotations)), 0, 7), function() : array {
|
||||
foreach($this->scan() as $namespace => $file) {
|
||||
if ( $file->getExtension() !== "php" ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $this->generateClassname($file->getBasename(".php"), $namespace);
|
||||
|
||||
# Should generate an equivalent of Ulmus's object reflection here !
|
||||
$objectResolver = new ObjectResolver($class, true, true, false, true);
|
||||
|
||||
foreach($objectResolver->getAttributeListFromClassname( $this->annotations['method'], false ) as $func => $cronard) {
|
||||
foreach($cronard as $task) {
|
||||
$list[] = [ 'class' => $class, 'method' => $func, 'annotation' => $task ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $list ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
protected function generateClassname($file, $namespace)
|
||||
{
|
||||
return "\\$namespace\\$file";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Event\Attribute\Method;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)]
|
||||
class Event implements \Notes\Attribute {
|
||||
public function __construct(
|
||||
public string $name,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Event\Attribute\Object;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
|
||||
class Event implements \Notes\Attribute {
|
||||
public function __construct(
|
||||
public string $name,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php namespace Notes\Event;
|
||||
|
||||
use Kash\HandleCacheTrait;
|
||||
use Notes\ObjectResolver;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use RuntimeException, DirectoryIterator, Generator, Closure;
|
||||
|
||||
class EventFetcher {
|
||||
use HandleCacheTrait;
|
||||
|
||||
protected array $folderList;
|
||||
|
||||
protected Closure $callback;
|
||||
|
||||
public array $defaultMethods = [ 'GET', 'POST' ];
|
||||
|
||||
protected array $annotations;
|
||||
|
||||
public function __construct( ?array $folderList = null, ?array $annotations = null, ? CacheInterface $cache = null)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
|
||||
if ($folderList !== null) {
|
||||
$this->folderList = $folderList;
|
||||
}
|
||||
|
||||
if ($annotations !== null) {
|
||||
$this->annotations = $annotations;
|
||||
}
|
||||
else {
|
||||
$this->annotations = [
|
||||
'method' => [ Attribute\Method\Event::class ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function addFolder($folder) : void
|
||||
{
|
||||
$this->folderList[] = $folder;
|
||||
}
|
||||
|
||||
public function setFolderList(array $list) : void
|
||||
{
|
||||
$this->folderList = $list;
|
||||
}
|
||||
|
||||
public function scan(? array $folders = null) : Generator
|
||||
{
|
||||
foreach($folders ?: $this->folderList as $namespace => $folder) {
|
||||
if ( ! file_exists($folder) ) {
|
||||
throw new RuntimeException(sprintf("Folder `%s` can not be found or scanned", $folder));
|
||||
}
|
||||
|
||||
foreach (new DirectoryIterator($folder) as $fileinfo) {
|
||||
if ( ! $fileinfo->isDot() ) {
|
||||
if ( $fileinfo->isDir() ) {
|
||||
foreach($this->scan([ "{$namespace}\\" . $fileinfo->getBasename() => $fileinfo->getPathname() ]) as $ns2 => $fi2) {
|
||||
yield $ns2 => $fi2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield $namespace => $fileinfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function compile() : array
|
||||
{
|
||||
return $this->handleCaching(substr(md5(serialize($this->annotations)), 0, 7), function() : array {
|
||||
foreach($this->scan() as $namespace => $file) {
|
||||
if ( $file->getExtension() !== "php" ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $this->generateClassname($file->getBasename(".php"), $namespace);
|
||||
|
||||
# Should generate an equivalent of Ulmus's object reflection here !
|
||||
$objectResolver = new ObjectResolver($class, true, true, false, true);
|
||||
|
||||
foreach($objectResolver->getAttributeListFromClassname( $this->annotations['method'], false ) as $func => $events) {
|
||||
foreach($events as $ev) {
|
||||
$list[] = new \Mcnd\Event\Event(name: $ev->name, class: $class, method: $func, attribute: $ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $list ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
protected function generateClassname($file, $namespace)
|
||||
{
|
||||
return "\\$namespace\\$file";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Route\Attribute\Method;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)]
|
||||
class Route implements \Notes\Attribute {
|
||||
public function __construct(
|
||||
public string $route,
|
||||
public string $name,
|
||||
public string|array $method = [ "GET", "POST" ],
|
||||
public string $base = "",
|
||||
public ? string $class = null,
|
||||
public ? string $classMethod = null,
|
||||
public bool $currentRoute = false,
|
||||
) {}
|
||||
|
||||
public function getRoute() : string
|
||||
{
|
||||
return rtrim("/" . trim(isset($this->base) ?
|
||||
"/" . trim($this->base, "/") . "/" . ltrim($this->route, "/")
|
||||
:
|
||||
"/" . ltrim($this->route, "/"), "/"), '/');
|
||||
}
|
||||
|
||||
public function matchRouteName(string $name) : bool
|
||||
{
|
||||
return strtolower($this->name) === strtolower($name);
|
||||
}
|
||||
|
||||
public function isRoute(string $name) {
|
||||
return $this->name === $name;
|
||||
}
|
||||
|
||||
public function currentRoute(bool|null $value = null ) {
|
||||
return is_null($value) ? $value : $this->currentRoute = $value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Route\Attribute\Object;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Route implements \Notes\Attribute {
|
||||
public function __construct(
|
||||
public string|array $method = [ "GET", "POST" ],
|
||||
public string $base = "",
|
||||
){}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
<?php namespace Notes\Route;
|
||||
|
||||
use Kash\HandleCacheTrait;
|
||||
use Notes\ObjectReflection,
|
||||
Notes\ObjectResolver;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use RuntimeException, DirectoryIterator, Generator, Closure;
|
||||
|
||||
class RouteFetcher {
|
||||
use HandleCacheTrait;
|
||||
|
||||
public array $list = [];
|
||||
|
||||
protected bool $debug;
|
||||
|
||||
protected array $folderList;
|
||||
|
||||
protected Closure $callback;
|
||||
|
||||
public array $defaultMethods = [ 'GET', 'POST' ];
|
||||
|
||||
protected array $annotations;
|
||||
|
||||
public function __construct(?Closure $callback = null, ? array $folderList = null, ? array $annotations = null, ? CacheInterface $cache = null)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
|
||||
if ($callback !== null) {
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
if ($folderList !== null) {
|
||||
$this->folderList = $folderList;
|
||||
}
|
||||
|
||||
if ($annotations !== null) {
|
||||
$this->annotations = $annotations;
|
||||
}
|
||||
else {
|
||||
$this->annotations = [
|
||||
'object' => [ Attribute\Object\Route::class ],
|
||||
'method' => [ Attribute\Method\Route::class ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function addFolder($folder) : void
|
||||
{
|
||||
$this->folderList[] = $folder;
|
||||
}
|
||||
|
||||
public function setFolderList(array $list) : void
|
||||
{
|
||||
$this->folderList = $list;
|
||||
}
|
||||
|
||||
public function scan(? array $folders = null) : Generator
|
||||
{
|
||||
foreach($folders ?: $this->folderList as $namespace => $folder) {
|
||||
if ( ! file_exists($folder) ) {
|
||||
throw new RuntimeException(sprintf("Folder `%s` can not be found or scanned", $folder));
|
||||
}
|
||||
|
||||
foreach (new DirectoryIterator($folder) as $fileinfo) {
|
||||
if ( ! $fileinfo->isDot() ) {
|
||||
if ( $fileinfo->isDir() ) {
|
||||
foreach($this->scan([ "{$namespace}\\" . $fileinfo->getBasename() => $fileinfo->getPathname() ]) as $ns2 => $fi2) {
|
||||
yield $ns2 => $fi2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield $namespace => $fileinfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function compile(null|array $annotations = null) : array
|
||||
{
|
||||
$annotations ??= $this->annotations;
|
||||
|
||||
return $this->handleCaching(substr(md5(serialize($annotations)), 0, 7), function() use ($annotations) : array {
|
||||
$list = [];
|
||||
|
||||
foreach($this->scan() as $namespace => $file) {
|
||||
if ( $file->getExtension() !== "php" ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$base = "";
|
||||
$class = $this->generateClassname($file->getBasename(".php"), $namespace);
|
||||
$methods = $this->defaultMethods;
|
||||
|
||||
$objectResolver = new ObjectResolver($class, true, true, false, true, $this->cache);
|
||||
|
||||
if ( isset($annotations['object']) ) {
|
||||
$object = $objectResolver->getAttributeFromClassname($annotations['object'], false) ?: $objectResolver->getAnnotationFromClassname($annotations['object'], false);
|
||||
|
||||
if ($object) {
|
||||
if ( $object->methods ?? false ) {
|
||||
$methods = $object->methods;
|
||||
}
|
||||
elseif ($object->method ?? false ) {
|
||||
$methods = (array) $object->method;
|
||||
}
|
||||
|
||||
$base = $object->base ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset($annotations['method']) ) {
|
||||
$routeList = $objectResolver->getAttributeListFromClassname( $annotations['method'], false );
|
||||
|
||||
foreach($routeList as $func => $routes) {
|
||||
if (is_array($routes)) {
|
||||
foreach ($routes as $route) {
|
||||
$route->base = $base;
|
||||
$route->class = $class;
|
||||
$route->classMethod = $func;
|
||||
|
||||
if (false === ($route->methods ?? false)) {
|
||||
$route->methods = $methods;
|
||||
}
|
||||
|
||||
if (false !== ($this->callback ?? false)) {
|
||||
call_user_func_array($this->callback, [$route]);
|
||||
}
|
||||
|
||||
$list[] = $route;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
});
|
||||
}
|
||||
|
||||
protected function generateClassname($file, $namespace)
|
||||
{
|
||||
return "\\$namespace\\$file";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Security\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
|
||||
class Security implements \Notes\Attribute {
|
||||
public function __construct(
|
||||
public null|bool $locked = null,
|
||||
public null|string $realm = null,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace Notes\Security\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
|
||||
class Taxus implements \Notes\Attribute {
|
||||
public function __construct(
|
||||
public null|string|\BackedEnum $privilege = null,
|
||||
public null|string $module = null,
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace Notes\Security;
|
||||
|
||||
use Taxus\Taxus;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
use Notes\ObjectResolver;
|
||||
|
||||
class SecurityHandler {
|
||||
|
||||
protected ResponseInterface $redirectResponse;
|
||||
|
||||
protected ? \Closure $unauthorizeResponse;
|
||||
|
||||
protected ? Taxus $taxus;
|
||||
|
||||
public function __construct(ResponseInterface $redirectResponse, ? \Closure $unauthorizeResponse = null, ? Taxus $taxus = null) {
|
||||
$this->redirectResponse = $redirectResponse;
|
||||
$this->unauthorizeResponse = $unauthorizeResponse;
|
||||
$this->taxus = $taxus;
|
||||
}
|
||||
|
||||
public function verify(string $className, string $methodName) : ? ResponseInterface
|
||||
{
|
||||
return $this->isLocked($className, $methodName) ? $this->redirectResponse : null;
|
||||
}
|
||||
|
||||
public function isLocked(string $className, string $methodName) : bool
|
||||
{
|
||||
if ( $security = $this->getClassAttributes(Attribute\Security::class, $className, $methodName) ) {
|
||||
return array_pop($security)->locked;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function taxus(string $className, string $methodName, object $user = null) : ? ResponseInterface
|
||||
{
|
||||
if ($taxus = $this->getClassAttributes(Attribute\Taxus::class, $className, $methodName)) {
|
||||
if ($this->unauthorizeResponse) {
|
||||
foreach ($taxus as $item) {
|
||||
if (!isset($item->privilege) || $this->taxus->granted($item->privilege, $user, $item)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return call_user_func_array($this->unauthorizeResponse, [ $user, $taxus, $className, $methodName ]);
|
||||
}
|
||||
else {
|
||||
throw new \ErrorException("Unauthorized response given.");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getClassAttributes(string $annotationClass, string $className, string $methodName)/* : \Notes\Attribute|array */
|
||||
{
|
||||
$objectResolver = new ObjectResolver($className, true, true, false, true);
|
||||
|
||||
try {
|
||||
$method = $objectResolver->getAttributeListFromClassname( $annotationClass, false );
|
||||
}
|
||||
catch(\Exception $e) { }
|
||||
|
||||
if ( $method[$methodName] ?? false ) {
|
||||
return $method[$methodName];
|
||||
}
|
||||
else {
|
||||
return array_filter($method, fn($e) => is_object($e));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue