29 Commits
Author SHA1 Message Date
mcndave c6bd5707bc - Added default lean's version to 1.4.2 2026-07-16 11:57:25 -04:00
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
Dave Mc Nicoll 2a6ec3cd1b Merge branch 'master' into v2.0.0b 2024-05-21 13:03:31 +00:00
Dave Mc Nicoll 1599284334 - Added route description 2024-05-21 13:03:18 +00:00
mcndave 2345c5453f - Added MatchRoute Method 2024-05-09 19:52:10 +00:00
19 changed files with 240 additions and 195 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());
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
}
],
"require": {
"mcnd/kash" : "dev-master"
"mcnd/kash" : "2.1.0"
},
"autoload": {
"psr-4": {
+2
View File
@@ -99,6 +99,8 @@ class AnnotationReader
case $reflect instanceof ReflectionClass :
return $reflect->name;
}
return "#UNKNOWN#";
}
protected function getObjectNamespace(Reflector $reflect) : string
+2 -1
View File
@@ -33,8 +33,9 @@ class AttributeReader
{
switch(true) {
case $reflect instanceof ReflectionMethod :
case $reflect instanceof ReflectionProperty :
return $reflect->class . "::" . $reflect->name;
case $reflect instanceof ReflectionProperty :
return $reflect->class . "::$" . $reflect->name;
case $reflect instanceof ReflectionClass :
return $reflect->name;
+7 -1
View File
@@ -10,6 +10,8 @@ use RuntimeException, DirectoryIterator, Generator;
class Breadcrumb extends RouteFetcher {
public bool $ignoreSoleRoute = true;
public function getRouteTree(\Notes\Route\Attribute\Method\Route $route) : array
{
$tree = [];
@@ -36,7 +38,11 @@ class Breadcrumb extends RouteFetcher {
else {
$route = null;
}
};
}
if ($this->ignoreSoleRoute && count($tree) === 1) {
return [];
}
return array_reverse($tree);
}
+1 -1
View File
@@ -87,7 +87,7 @@ class CommandFetcher {
$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 ) ;
+25 -1
View File
@@ -21,7 +21,6 @@ abstract class Reflected
$type = strtolower($type);
foreach($this->getTypes() as $item) {
if ($type === "null") {
if ($item->type === "null" || $item->nullable) {
return true;
@@ -46,6 +45,8 @@ abstract class Reflected
$type = $property->getType();
if ($type instanceof \ReflectionUnionType ) {
$this->type = [];
foreach($type->getTypes() as $item) {
$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 function getClassName() : string
{
$exploded = explode('\\', $this->name);
return end($exploded);
}
public function getProperties(bool $deep = true) : array
{
return $deep ? array_replace(
# $this->parent ? $this->parent->getProperties(true) : [],
$this->parent ? $this->parent->getProperties(true) : [],
$this->properties
) : $this->properties;
@@ -35,14 +41,33 @@ class ReflectedClass implements ReflectedInterface
) : $this->methods;
}
public function getAttributes(bool $deep = true) : array
public function getAttributes(bool $deep = true, ? string $attributeType = null) : array
{
return $deep ? array_merge(
$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)),
return array_reverse($this->getOrderedAttributes($deep, $attributeType));
}
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;
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
@@ -146,4 +171,13 @@ class ReflectedClass implements ReflectedInterface
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 function getAttributes(): array
{
return $this->attributes;
}
}
-6
View File
@@ -14,12 +14,6 @@ class ReflectedProperty extends Reflected implements ReflectedInterface, \ArrayA
public array $attributes = [],
) {}
public function getAttributes(): array
{
return $this->attributes;
}
## BACKWARD COMPATIBILITY ONLY To be removed really soon !
public function offsetExists(mixed $offset): bool
{
+10
View File
@@ -18,4 +18,14 @@ class ReflectedPropertyType
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 string $cron,
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->cache = $cache;
#if ( ! $this->cache || ! $this->cache->has($class) ) {
$this->classReflection = $class instanceof ReflectionClass ? $class : new ReflectionClass($class);
# }
$this->classReflection = $class instanceof ReflectionClass ? $class : new ReflectionClass($class);
}
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
@@ -50,9 +52,9 @@ class ObjectReflection {
}
public function reflectProperties(int $filter =
ReflectionProperty::IS_PUBLIC |
ReflectionProperty::IS_PROTECTED |
ReflectionProperty::IS_PRIVATE
ReflectionProperty::IS_PUBLIC |
ReflectionProperty::IS_PROTECTED |
ReflectionProperty::IS_PRIVATE
) : array
{
$defaultValues = $this->classReflection->getDefaultProperties();
@@ -61,7 +63,6 @@ class ObjectReflection {
foreach($this->classReflection->getProperties($filter) as $property) {
$reflected = new ReflectedProperty($property->getName());
$reflected->attributes = AttributeReader::reflectAttributes($property);
if ( $reflected->hasIgnoreAttribute() ) {
@@ -82,18 +83,17 @@ class ObjectReflection {
}
public function reflectMethods(int $filter =
ReflectionMethod::IS_PUBLIC |
ReflectionMethod::IS_PROTECTED |
ReflectionMethod::IS_PRIVATE |
ReflectionMethod::IS_STATIC |
ReflectionMethod::IS_FINAL
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED |
ReflectionMethod::IS_PRIVATE | ReflectionMethod::IS_STATIC |
ReflectionMethod::IS_FINAL,
bool $includeParents = false
) : array
{
$list = [];
foreach($this->classReflection->getMethods($filter) as $method) {
# Skipping parent's methods, we'll retrieve them in its own reflection
if ( $method->class !== $this->classname ) {
if ( ! $includeParents && $method->class !== $this->classname ) {
continue;
}
+25
View File
@@ -2,6 +2,8 @@
namespace Notes\Route\Attribute\Method;
use Psr\Http\Message\ServerRequestInterface;
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)]
class Route implements \Notes\Attribute {
public function __construct(
@@ -12,8 +14,14 @@ class Route implements \Notes\Attribute {
public ? string $class = null,
public ? string $classMethod = null,
public bool $currentRoute = false,
public string $description = "",
) {}
public function getBase() : ?string
{
return $this->base ? rtrim($this->base, '/') : null;
}
public function getRoute() : string
{
return rtrim("/" . trim(isset($this->base) ?
@@ -22,11 +30,28 @@ class Route implements \Notes\Attribute {
"/" . ltrim($this->route, "/"), "/"), '/');
}
public function getRegistrableRoute() : string
{
return preg_replace('/(\=.*)(?=\})/i', '', $this->getRoute());
}
public function matchRouteName(string $name) : bool
{
return strtolower($this->name) === strtolower($name);
}
public function match(mixed $name) : bool
{
foreach((array) $name as $item) {
if ( fnmatch($item, $this->name) ) {
return true;
}
}
return false;
}
public function isRoute(string $name) {
return $this->name === $name;
}
+6
View File
@@ -8,5 +8,11 @@ class Route implements \Notes\Attribute {
public string|array $method = [ "GET", "POST" ],
# NULL will fallback on nearest base
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;
/*
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 {
$list = [];
@@ -92,24 +100,27 @@ class RouteFetcher {
$base = null;
$class = $this->generateClassname($file->getBasename(".php"), $namespace);
$methods = null;
$inheritRoutes = null;
$objectResolver = new ObjectResolver($class, $this->cache);
if ( isset($attributes['object']) ) {
# Also loading Routes attribute from parent classes
$objects = $objectResolver->reflectedClass->getClassAttributeListFromClassname($attributes['object'], true, false);
foreach(array_reverse($objects) as $object) {
foreach($objects as $object) {
if ($object->method ?? false ) {
$methods ??= (array) $object->method;
}
$inheritRoutes ??= $object->inheritRoutes;
$base ??= $object->base ?? null;
}
}
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) {
if (is_array($routes)) {
@@ -118,12 +129,10 @@ class RouteFetcher {
if ($route instanceof \Notes\Route\Attribute\Method\Route) {
$route->base = $route->base ?: $base;
}
$route->class = $class;
$route->classMethod = $func;
if (false === ($route->methods ?? false)) {
$route->methods = $methods ?? $this->defaultMethods;
}
$route->method ??= $methods ?? $this->defaultMethods;
if (false !== ($this->callback ?? false)) {
call_user_func_array($this->callback, [$route]);
+1
View File
@@ -7,5 +7,6 @@ class Taxus implements \Notes\Attribute {
public function __construct(
public null|string|\BackedEnum $privilege = null,
public null|string $module = null,
public string|array $method = "*",
) {}
}
+63 -24
View File
@@ -2,21 +2,23 @@
namespace Notes\Security;
use Notes\Common\ReflectedAttribute;
use Taxus\Taxus;
use Psr\Http\Message\ResponseInterface;
use Notes\ObjectResolver;
use Taxus\TaxusGrantEnum;
class SecurityHandler {
protected ResponseInterface $redirectResponse;
protected ? \Closure $unauthorizeResponse;
protected \Closure|ResponseInterface|null $unauthorizeResponse;
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->unauthorizeResponse = $unauthorizeResponse;
$this->taxus = $taxus;
@@ -27,51 +29,88 @@ class SecurityHandler {
return $this->isLocked($className, $methodName) ? $this->redirectResponse : null;
}
# @TODO Must HANDLE REALM !
public function isLocked(string $className, string $methodName) : bool
{
if ( $security = $this->getClassAttributes(Attribute\Security::class, $className, $methodName) ) {
return array_pop($security)->locked;
# Searching method first and fallbacking on object if none found
if ( $list = $this->findAttributes(Attribute\Security::class, $className, $methodName) ) {
return array_shift($list)->locked;
}
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) {
foreach ($taxus as $item) {
if (!isset($item->privilege) || $this->taxus->granted($item->privilege, $user, $item)) {
return null;
}
if ($this->unauthorizeResponse instanceof ResponseInterface) {
return $this->unauthorizeResponse;
}
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 {
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 {
$method = $objectResolver->reflectedClass->getClassAttributeList( $annotationClass, true, false );
if ($fromMethod) {
return $this->taxusGrantPermission($fromMethod, ... $arguments);
}
elseif ($fromObject) {
return $this->taxusGrantPermission($fromObject, ... $arguments);
}
# var_dump($method);
}
catch(\Exception $e) { }
return true;
}
if ( $method[$methodName] ?? false ) {
return $method[$methodName];
protected function findAttributes(string $attribute, string $class, ? string $method = null) : array
{
$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;
}
}