25 Commits
Author SHA1 Message Date
mcndave 13ccb13a0b - Removed an unused 'methods' property 2026-07-14 14:54:06 +00:00
Dev 67aa7e513d - Parent routes can now be loaded on-demand 2026-06-09 19:25:23 +00:00
Dave Mc Nicoll eac8269c48 - Work done on base from routes 2026-05-21 14:59:39 +00:00
Dave Mc Nicoll fa0430a3fc - Added some reflected properties 2026-04-23 18:10:19 +00:00
Dave Mc Nicoll 237f0a30d2 - Restricted source of permission 2026-02-25 19:13:42 +00:00
mcndave b093f53a6b - quick bugfix awaiting for array_last 2025-11-06 17:18:44 +00:00
mcndave dd239c63bc - Added a name property on Cronard's attributes 2025-07-14 16:55:56 +00:00
Dave Mc Nicoll 0db34fe228 - Added old migration script, reworked security attribute also 2025-05-23 19:03:39 +00:00
Dave Mc Nicoll fa4fe32277 - Small fix on reflection property name 2025-05-19 17:31:33 +00:00
Dave Mc Nicoll e20157a162 - Unauthorize response now allows ResponseInterface as type 2025-04-01 19:16:30 +00:00
mcndave 63c7dc23f9 - Reworked url default value removal; preg_replace will remove them without having to slit the string 2024-11-22 20:49:51 -05:00
mcndave a4d99dfe08 - Route now supports default values 2024-11-22 20:46:23 -05:00
Dave Mc Nicoll f199cc7085 - Merged v2.0.0b branch into master; rewrite is officially stable and a drop-in replacement of v1.x 2024-11-13 14:00:50 -05:00
Dave Mc Nicoll 08c0ffacab Merge branch 'v2.0.0b' of https://git.mcnd.ca/mcndave/notes into v2.0.0b 2024-10-29 16:00:36 +00:00
Dave Mc Nicoll 6ba66dcd37 - Adjusted for notes V2.x 2024-10-29 16:00:12 +00:00
Dave Mc Nicoll 81fa3d49c7 - Breadcrumb now shows up only if deep within at least two routes (if ignoreSoleRoute is true) 2024-10-14 12:52:34 +00:00
Dave Mc Nicoll fcb332ae53 Merge branch 'master' of https://git.mcnd.ca/mcndave/notes 2024-10-07 15:44:24 -04:00
Dave Mc Nicoll 133672b347 - WIP on Breadcrumb, AnnotationReader now return #UNKNOWN# if reflection fail 2024-10-07 15:44:00 -04:00
Dave Mc Nicoll 34a3351ed5 - Added classReflection property 2024-06-21 14:17:57 +00:00
Dave Mc Nicoll dd680c8a9a v2.0.0b 2024-06-04 13:33:11 +00:00
Dave Mc Nicoll c92baf6cda - Removed NULL and added a new Enum instead 2024-06-02 23:02:27 +00:00
Dave Mc Nicoll 86afea9b4e - Reworked the SecurityHandler granting part which now supports NULL as a 'skipping' value. This necessity appeared since a new property in the attribute was added. 2024-06-02 19:54:55 +00:00
Dave Mc Nicoll 4568427c66 - Added HTTP methods to Taxus object 2024-05-31 19:00:32 +00:00
Dave Mc Nicoll 19c79d0374 - getAttribute() can now query multiple types 2024-05-31 12:28:00 +00:00
Dave Mc Nicoll 0cdc2dc3d8 - Added some more method to filter properties 2024-05-27 18:06:56 +00:00
18 changed files with 224 additions and 194 deletions

No files matched your search

-129
View File
@@ -1,129 +0,0 @@
#!/usr/bin/php
<?php
array_shift($argv);
$opt = getopt('c::f:v', [ 'folder:', 'confirm', 'verbose', 'clean', 'old' ]);
if ( ! $opt ) {
exit("Bad arguments provided, you must specify a fullpath using the -f or --folder option\n");
}
$iterator = new RecursiveIteratorIterator(new class(new RecursiveDirectoryIterator($opt['folder'] ?? $opt['f'], FilesystemIterator::SKIP_DOTS)) extends RecursiveFilterIterator {
public function accept() : bool {
return true;
}
}, RecursiveIteratorIterator::SELF_FIRST);
$files = array();
foreach ($iterator as $info) {
if ($info->getExtension() !== "php" || $info->isDir() ) {
continue;
}
echo sprintf("\n### MIGRATING FILE %s\n", $info->getFilename());
$opened = false;
$attr = $newContent = [];
$content = file_get_contents($info->getPathname());
if ( isset($opt['clean']) ) {
foreach (explode(PHP_EOL, $content) as $lineIdx => $line) {
if ( $pos = strpos($line, '# migrated from: ') ) {
$attr = true;
$newContent[] = $newLine = substr($line, 0, $pos);
echo sprintf("Changed line '%s' \n", trim($newLine));
}
else {
$newContent[] = $line;
}
}
}
else {
foreach (explode(PHP_EOL, $content) as $lineIdx => $line) {
$tline = trim($line);
if ($tline === "/**") {
$opened = true;
} elseif ($tline === '*/') {
$opened = false;
} elseif (substr($tline, 0, 1) === '*') {
$annotation = trim(substr($tline, 1));
if (strpos($annotation, '@') === 0) {
$argpos = strpos($annotation, '(');
if ($argpos !== false) {
$name = substr($annotation, 1, $argpos - 1);
$args = substr($annotation, $argpos + 1, strrpos($annotation, ')') - $argpos - 1);
try {
$array = eval("return [{$args}];");
$renderedArgs = [];
foreach ($array as $key => $argument) {
if (is_array($argument)) {
$argument = var_export($argument, true);
} elseif (is_bool($argument)) {
$argument = $argument ? 'true' : 'false';
} elseif (!is_numeric($argument)) {
$argument = "\"$argument\"";
}
if (is_numeric($key)) {
$renderedArgs[] = $argument;
} else {
$renderedArgs[] = "$key: $argument";
}
}
$renderedArgs = implode(', ', $renderedArgs);
} catch (\Throwable $e) {
echo sprintf("!!! WARNING : %s in file (%s) line %d ; using previous notation.\n", $e->getMessage(), $info->getPathname(), $lineIdx + 1);
$renderedArgs = str_replace(' => ', ': ', $args);
}
} else {
$name = substr($annotation, 1);
$renderedArgs = "";
}
$attr[] = $attribute = sprintf("%s#[%s%s]%s", str_repeat(" ", strlen($line) - strlen(ltrim($line)) >= 4 ? 4 : 0), $name, $renderedArgs ? "($renderedArgs)" : "", $renderedArgs ? ( isset($opt['old']) ? " # migrated from: $args" : "" ) : "");
$newContent[] = $attribute;
}
} else {
$newContent[] = $line;
}
}
}
$newContent = implode(PHP_EOL, $newContent);
if ($attr) {
if ( isset($opt['verbose']) || isset($opt['v']) ) {
echo $newContent;
}
if ( isset($opt['confirm']) || isset($opt['c']) ) {
file_put_contents($info->getPathname(), $newContent);
echo sprintf("\n### FILE CONVERSION COMPLETED %s\n", $info->getFilename());
}
else {
echo sprintf("\n### FILE CONVERSION ANALYZED, NOTHING WRITTEN UNTIL --confirm or -c FLAG IS PROVIDED %s\n", $info->getFilename());
}
}
else {
echo sprintf("\n### NOTHING TO DO ON FILE %s\n", $info->getFilename());
}
}
+2
View File
@@ -99,6 +99,8 @@ class AnnotationReader
case $reflect instanceof ReflectionClass : case $reflect instanceof ReflectionClass :
return $reflect->name; return $reflect->name;
} }
return "#UNKNOWN#";
} }
protected function getObjectNamespace(Reflector $reflect) : string protected function getObjectNamespace(Reflector $reflect) : string
+2 -1
View File
@@ -33,8 +33,9 @@ class AttributeReader
{ {
switch(true) { switch(true) {
case $reflect instanceof ReflectionMethod : case $reflect instanceof ReflectionMethod :
case $reflect instanceof ReflectionProperty :
return $reflect->class . "::" . $reflect->name; return $reflect->class . "::" . $reflect->name;
case $reflect instanceof ReflectionProperty :
return $reflect->class . "::$" . $reflect->name;
case $reflect instanceof ReflectionClass : case $reflect instanceof ReflectionClass :
return $reflect->name; return $reflect->name;
+7 -1
View File
@@ -10,6 +10,8 @@ use RuntimeException, DirectoryIterator, Generator;
class Breadcrumb extends RouteFetcher { class Breadcrumb extends RouteFetcher {
public bool $ignoreSoleRoute = true;
public function getRouteTree(\Notes\Route\Attribute\Method\Route $route) : array public function getRouteTree(\Notes\Route\Attribute\Method\Route $route) : array
{ {
$tree = []; $tree = [];
@@ -36,7 +38,11 @@ class Breadcrumb extends RouteFetcher {
else { else {
$route = null; $route = null;
} }
}; }
if ($this->ignoreSoleRoute && count($tree) === 1) {
return [];
}
return array_reverse($tree); return array_reverse($tree);
} }
+1 -1
View File
@@ -87,7 +87,7 @@ class CommandFetcher {
$class = $this->generateClassname($basename, $namespace); $class = $this->generateClassname($basename, $namespace);
$objectResolver = new ObjectResolver($class, true, false, false); $objectResolver = new ObjectResolver($class, $this->cache);
$attributes = $objectResolver->getAttributeListFromClassname( $this->annotations['object'], false ) ; $attributes = $objectResolver->getAttributeListFromClassname( $this->annotations['object'], false ) ;
+25 -1
View File
@@ -21,7 +21,6 @@ abstract class Reflected
$type = strtolower($type); $type = strtolower($type);
foreach($this->getTypes() as $item) { foreach($this->getTypes() as $item) {
if ($type === "null") { if ($type === "null") {
if ($item->type === "null" || $item->nullable) { if ($item->type === "null" || $item->nullable) {
return true; return true;
@@ -46,6 +45,8 @@ abstract class Reflected
$type = $property->getType(); $type = $property->getType();
if ($type instanceof \ReflectionUnionType ) { if ($type instanceof \ReflectionUnionType ) {
$this->type = [];
foreach($type->getTypes() as $item) { foreach($type->getTypes() as $item) {
$this->type[] = new ReflectedPropertyType($item->getName(), $item->isBuiltIn(), $item->allowsNull()); $this->type[] = new ReflectedPropertyType($item->getName(), $item->isBuiltIn(), $item->allowsNull());
} }
@@ -56,4 +57,27 @@ abstract class Reflected
} }
} }
public function getAttributes(null|string|array $attributeType = null): array
{
if ($attributeType) {
$list = [];
foreach($this->attributes as $attribute) {
foreach((array) $attributeType as $type) {
if ($attribute->object instanceof $type) {
$list[] = $attribute;
}
}
}
return array_reverse($list);
}
return array_reverse($this->attributes);
}
public function getAttribute(string|array $attributeType): ?object
{
return $this->getAttributes($attributeType)[0] ?? null;
}
} }
+40 -6
View File
@@ -14,10 +14,16 @@ class ReflectedClass implements ReflectedInterface
public array $traits = [], public array $traits = [],
) {} ) {}
public function getClassName() : string
{
$exploded = explode('\\', $this->name);
return end($exploded);
}
public function getProperties(bool $deep = true) : array public function getProperties(bool $deep = true) : array
{ {
return $deep ? array_replace( return $deep ? array_replace(
# $this->parent ? $this->parent->getProperties(true) : [],
$this->parent ? $this->parent->getProperties(true) : [], $this->parent ? $this->parent->getProperties(true) : [],
$this->properties $this->properties
) : $this->properties; ) : $this->properties;
@@ -35,14 +41,33 @@ class ReflectedClass implements ReflectedInterface
) : $this->methods; ) : $this->methods;
} }
public function getAttributes(bool $deep = true) : array public function getAttributes(bool $deep = true, ? string $attributeType = null) : array
{ {
return $deep ? array_merge( return array_reverse($this->getOrderedAttributes($deep, $attributeType));
$this->parent ? $this->parent->getAttributes(true) : [], }
array_merge(...array_map(fn($e) => $e->getAttributes(true), $this->interfaces)),
array_merge(...array_map(fn($e) => $e->getAttributes(true), $this->traits)), protected function getOrderedAttributes(bool $deep = true, ? string $attributeType = null) : array
{
$attributes = $deep ? array_merge(
$this->parent ? $this->parent->getOrderedAttributes(true) : [],
array_merge(...array_map(fn($e) => $e->getOrderedAttributes(true), $this->interfaces)),
array_merge(...array_map(fn($e) => $e->getOrderedAttributes(true), $this->traits)),
$this->attributes $this->attributes
) : $this->attributes; ) : $this->attributes;
if ($attributeType) {
$list = [];
foreach($attributes as $attribute) {
if ($attribute->object instanceof $attributeType) {
$list[] = $attribute;
}
}
return $list;
}
return $attributes;
} }
# Get Attributes based on Native attributes's target # Get Attributes based on Native attributes's target
@@ -146,4 +171,13 @@ class ReflectedClass implements ReflectedInterface
return $list; return $list;
} }
public function getAttribute(string $attributeType): ?object
{
foreach($this->getAttributes(true, $attributeType) as $attribute) {
return $attribute;
}
return null;
}
} }
-4
View File
@@ -15,8 +15,4 @@ class ReflectedMethod extends Reflected implements ReflectedInterface
public array $parameters = [], public array $parameters = [],
) {} ) {}
public function getAttributes(): array
{
return $this->attributes;
}
} }
-6
View File
@@ -14,12 +14,6 @@ class ReflectedProperty extends Reflected implements ReflectedInterface, \ArrayA
public array $attributes = [], public array $attributes = [],
) {} ) {}
public function getAttributes(): array
{
return $this->attributes;
}
## BACKWARD COMPATIBILITY ONLY To be removed really soon ! ## BACKWARD COMPATIBILITY ONLY To be removed really soon !
public function offsetExists(mixed $offset): bool public function offsetExists(mixed $offset): bool
{ {
+10
View File
@@ -18,4 +18,14 @@ class ReflectedPropertyType
return $this->type === $type; return $this->type === $type;
} }
public function isObject() : bool
{
return ! $this->builtIn && class_exists($this->type);
}
public function isEnum() : bool
{
return ! $this->builtIn && enum_exists($this->type);
}
} }
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace Notes\Common;
class ReflectedPropertyType
{
public function __construct(
public string $type,
public bool $builtIn,
public bool $nullable,
) {}
public function isType(string $type) : bool
{
if ($type === "null" || $this->nullable) {
return true;
}
return $this->type === $type;
}
public function isObject() : bool
{
return ! $this->builtIn && class_exists($this->type);
}
}
+1
View File
@@ -7,5 +7,6 @@ class Cronard implements \Notes\Attribute {
public function __construct( public function __construct(
public string $cron, public string $cron,
public null|string $method = null, public null|string $method = null,
public null|string $name = null,
) {} ) {}
} }
+15 -15
View File
@@ -23,15 +23,17 @@ class ObjectReflection {
$this->classname = ltrim($class instanceof ReflectionClass ? $class->getName() : $class, '\\'); $this->classname = ltrim($class instanceof ReflectionClass ? $class->getName() : $class, '\\');
$this->cache = $cache; $this->cache = $cache;
$this->classReflection = $class instanceof ReflectionClass ? $class : new ReflectionClass($class);
#if ( ! $this->cache || ! $this->cache->has($class) ) {
$this->classReflection = $class instanceof ReflectionClass ? $class : new ReflectionClass($class);
# }
} }
public static function fromClass(ReflectionClass|string $class, ? CacheInterface $cache = null) : self public static function fromClass(ReflectionClass|string $class, ? CacheInterface $cache = null) : self
{ {
return new static($class, null, $cache); return new static($class, $cache);
}
public function getClassReflection() : ReflectionClass
{
return $this->classReflection;
} }
public function reflectClass() : ReflectedClass public function reflectClass() : ReflectedClass
@@ -50,9 +52,9 @@ class ObjectReflection {
} }
public function reflectProperties(int $filter = public function reflectProperties(int $filter =
ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PUBLIC |
ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PROTECTED |
ReflectionProperty::IS_PRIVATE ReflectionProperty::IS_PRIVATE
) : array ) : array
{ {
$defaultValues = $this->classReflection->getDefaultProperties(); $defaultValues = $this->classReflection->getDefaultProperties();
@@ -61,7 +63,6 @@ class ObjectReflection {
foreach($this->classReflection->getProperties($filter) as $property) { foreach($this->classReflection->getProperties($filter) as $property) {
$reflected = new ReflectedProperty($property->getName()); $reflected = new ReflectedProperty($property->getName());
$reflected->attributes = AttributeReader::reflectAttributes($property); $reflected->attributes = AttributeReader::reflectAttributes($property);
if ( $reflected->hasIgnoreAttribute() ) { if ( $reflected->hasIgnoreAttribute() ) {
@@ -82,18 +83,17 @@ class ObjectReflection {
} }
public function reflectMethods(int $filter = public function reflectMethods(int $filter =
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED |
ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE | ReflectionMethod::IS_STATIC |
ReflectionMethod::IS_PRIVATE | ReflectionMethod::IS_FINAL,
ReflectionMethod::IS_STATIC | bool $includeParents = false
ReflectionMethod::IS_FINAL
) : array ) : array
{ {
$list = []; $list = [];
foreach($this->classReflection->getMethods($filter) as $method) { foreach($this->classReflection->getMethods($filter) as $method) {
# Skipping parent's methods, we'll retrieve them in its own reflection # Skipping parent's methods, we'll retrieve them in its own reflection
if ( $method->class !== $this->classname ) { if ( ! $includeParents && $method->class !== $this->classname ) {
continue; continue;
} }
+10
View File
@@ -17,6 +17,11 @@ class Route implements \Notes\Attribute {
public string $description = "", public string $description = "",
) {} ) {}
public function getBase() : ?string
{
return $this->base ? rtrim($this->base, '/') : null;
}
public function getRoute() : string public function getRoute() : string
{ {
return rtrim("/" . trim(isset($this->base) ? return rtrim("/" . trim(isset($this->base) ?
@@ -25,6 +30,11 @@ class Route implements \Notes\Attribute {
"/" . ltrim($this->route, "/"), "/"), '/'); "/" . ltrim($this->route, "/"), "/"), '/');
} }
public function getRegistrableRoute() : string
{
return preg_replace('/(\=.*)(?=\})/i', '', $this->getRoute());
}
public function matchRouteName(string $name) : bool public function matchRouteName(string $name) : bool
{ {
return strtolower($this->name) === strtolower($name); return strtolower($this->name) === strtolower($name);
+6
View File
@@ -8,5 +8,11 @@ class Route implements \Notes\Attribute {
public string|array $method = [ "GET", "POST" ], public string|array $method = [ "GET", "POST" ],
# NULL will fallback on nearest base # NULL will fallback on nearest base
public null|string $base = null, public null|string $base = null,
public null|bool $inheritRoutes = null, # true or false will skip others attribute from parents after, null = skipped for this one.
){} ){}
public function getBase() : ?string
{
return $this->base ? rtrim($this->base, '/') : null;
}
} }
+15 -6
View File
@@ -81,6 +81,14 @@ class RouteFetcher {
{ {
$attributes ??= $this->attributes; $attributes ??= $this->attributes;
/*
8.5: return $attributes
|> serialize(...)
|> md5(...)
|> (fn($x) => substr($x, 0, 7))
|> (fn($x) => $this->handleCaching())
*/
return $this->handleCaching(substr(md5(serialize($attributes)), 0, 7), function() use ($attributes) : array { return $this->handleCaching(substr(md5(serialize($attributes)), 0, 7), function() use ($attributes) : array {
$list = []; $list = [];
@@ -92,24 +100,27 @@ class RouteFetcher {
$base = null; $base = null;
$class = $this->generateClassname($file->getBasename(".php"), $namespace); $class = $this->generateClassname($file->getBasename(".php"), $namespace);
$methods = null; $methods = null;
$inheritRoutes = null;
$objectResolver = new ObjectResolver($class, $this->cache); $objectResolver = new ObjectResolver($class, $this->cache);
if ( isset($attributes['object']) ) { if ( isset($attributes['object']) ) {
# Also loading Routes attribute from parent classes
$objects = $objectResolver->reflectedClass->getClassAttributeListFromClassname($attributes['object'], true, false); $objects = $objectResolver->reflectedClass->getClassAttributeListFromClassname($attributes['object'], true, false);
foreach(array_reverse($objects) as $object) { foreach($objects as $object) {
if ($object->method ?? false ) { if ($object->method ?? false ) {
$methods ??= (array) $object->method; $methods ??= (array) $object->method;
} }
$inheritRoutes ??= $object->inheritRoutes;
$base ??= $object->base ?? null; $base ??= $object->base ?? null;
} }
} }
if ( isset($attributes['method']) ) { if ( isset($attributes['method']) ) {
$routeList = $objectResolver->reflectedClass->getMethodAttributeListFromClassname( $attributes['method'], false, false ); $routeList = $objectResolver->reflectedClass->getMethodAttributeListFromClassname( $attributes['method'], (bool) $inheritRoutes, false );
foreach($routeList as $func => $routes) { foreach($routeList as $func => $routes) {
if (is_array($routes)) { if (is_array($routes)) {
@@ -118,12 +129,10 @@ class RouteFetcher {
if ($route instanceof \Notes\Route\Attribute\Method\Route) { if ($route instanceof \Notes\Route\Attribute\Method\Route) {
$route->base = $route->base ?: $base; $route->base = $route->base ?: $base;
} }
$route->class = $class; $route->class = $class;
$route->classMethod = $func; $route->classMethod = $func;
$route->method ??= $methods ?? $this->defaultMethods;
if (false === ($route->methods ?? false)) {
$route->methods = $methods ?? $this->defaultMethods;
}
if (false !== ($this->callback ?? false)) { if (false !== ($this->callback ?? false)) {
call_user_func_array($this->callback, [$route]); call_user_func_array($this->callback, [$route]);
+1
View File
@@ -7,5 +7,6 @@ class Taxus implements \Notes\Attribute {
public function __construct( public function __construct(
public null|string|\BackedEnum $privilege = null, public null|string|\BackedEnum $privilege = null,
public null|string $module = null, public null|string $module = null,
public string|array $method = "*",
) {} ) {}
} }
+63 -24
View File
@@ -2,21 +2,23 @@
namespace Notes\Security; namespace Notes\Security;
use Notes\Common\ReflectedAttribute;
use Taxus\Taxus; use Taxus\Taxus;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Notes\ObjectResolver; use Notes\ObjectResolver;
use Taxus\TaxusGrantEnum;
class SecurityHandler { class SecurityHandler {
protected ResponseInterface $redirectResponse; protected ResponseInterface $redirectResponse;
protected ? \Closure $unauthorizeResponse; protected \Closure|ResponseInterface|null $unauthorizeResponse;
protected ? Taxus $taxus; protected ? Taxus $taxus;
public function __construct(ResponseInterface $redirectResponse, ? \Closure $unauthorizeResponse = null, ? Taxus $taxus = null) { public function __construct(ResponseInterface $redirectResponse, \Closure|ResponseInterface|null $unauthorizeResponse = null, ? Taxus $taxus = null) {
$this->redirectResponse = $redirectResponse; $this->redirectResponse = $redirectResponse;
$this->unauthorizeResponse = $unauthorizeResponse; $this->unauthorizeResponse = $unauthorizeResponse;
$this->taxus = $taxus; $this->taxus = $taxus;
@@ -27,51 +29,88 @@ class SecurityHandler {
return $this->isLocked($className, $methodName) ? $this->redirectResponse : null; return $this->isLocked($className, $methodName) ? $this->redirectResponse : null;
} }
# @TODO Must HANDLE REALM !
public function isLocked(string $className, string $methodName) : bool public function isLocked(string $className, string $methodName) : bool
{ {
if ( $security = $this->getClassAttributes(Attribute\Security::class, $className, $methodName) ) { # Searching method first and fallbacking on object if none found
return array_pop($security)->locked; if ( $list = $this->findAttributes(Attribute\Security::class, $className, $methodName) ) {
return array_shift($list)->locked;
} }
return true; return true;
} }
public function taxus(string $className, string $methodName, object $user = null) : ? ResponseInterface public function taxus(string $className, string $methodName, ... $arguments) : ? ResponseInterface
{ {
if ($taxus = $this->getClassAttributes(Attribute\Taxus::class, $className, $methodName)) { $granted = $this->hasGrantPermission($className , $methodName, ...$arguments);
if ($granted) {
return null;
}
else {
if ($this->unauthorizeResponse) { if ($this->unauthorizeResponse) {
foreach ($taxus as $item) { if ($this->unauthorizeResponse instanceof ResponseInterface) {
if (!isset($item->privilege) || $this->taxus->granted($item->privilege, $user, $item)) { return $this->unauthorizeResponse;
return null;
}
} }
return call_user_func_array($this->unauthorizeResponse, [ $user, $taxus, $className, $methodName ]); return call_user_func_array($this->unauthorizeResponse, [ ...$arguments, ['method' => $methodName, 'object' => $className ], $className, $methodName ]);
} }
else { else {
throw new \ErrorException("Unauthorized response given."); throw new \ErrorException("Unauthorized response given.");
} }
} }
return null;
} }
protected function getClassAttributes(string $annotationClass, string $className, string $methodName)/* : \Notes\Attribute|array */ public function hasGrantPermission(string $className, string $methodName, ... $arguments) : bool
{ {
$objectResolver = new ObjectResolver($className); $fromObject = $this->findAttributes(Attribute\Taxus::class, $className);
$fromMethod = $this->findAttributes(Attribute\Taxus::class, $className, $methodName);
try { if ($fromMethod) {
$method = $objectResolver->reflectedClass->getClassAttributeList( $annotationClass, true, false ); return $this->taxusGrantPermission($fromMethod, ... $arguments);
}
elseif ($fromObject) {
return $this->taxusGrantPermission($fromObject, ... $arguments);
}
# var_dump($method); return true;
} }
catch(\Exception $e) { }
if ( $method[$methodName] ?? false ) { protected function findAttributes(string $attribute, string $class, ? string $method = null) : array
return $method[$methodName]; {
$objectResolver = new ObjectResolver($class);
if ($method) {
$fromMethod = $objectResolver->reflectedClass->getMethods( false );
if (isset($fromMethod[$method])) {
$attributeList = $fromMethod[$method]->getAttributes($attribute);
if ($attributeList) {
return array_map(fn(ReflectedAttribute $ref) => $ref->object, $attributeList);
}
}
} }
else {
return array_filter($method, fn($e) => is_object($e)); $attributeList = $objectResolver->reflectedClass->getAttributes(false, $attribute) ?: $objectResolver->reflectedClass->getAttributes(true, $attribute);
if ($attributeList) {
return array_map(fn(ReflectedAttribute $ref) => $ref->object, $attributeList);
} }
return [];
}
protected function taxusGrantPermission(array $attributeList, ...$arguments) : bool
{
foreach ($attributeList as $item) {
if ( $grant = $this->taxus->granted($item->privilege, ... array_merge($arguments, [ $item ])) ) {
if (is_bool($grant) ? $grant : $grant === TaxusGrantEnum::Authorized) {
return true;
}
}
}
return false;
} }
} }