16 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 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
13 changed files with 110 additions and 165 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": { "require": {
"mcnd/kash" : "dev-master" "mcnd/kash" : "2.1.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
+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;
+2 -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());
} }
+4 -4
View File
@@ -16,7 +16,9 @@ class ReflectedClass implements ReflectedInterface
public function getClassName() : string public function getClassName() : string
{ {
return end(explode('\\', $this->name)); $exploded = explode('\\', $this->name);
return end($exploded);
} }
public function getProperties(bool $deep = true) : array public function getProperties(bool $deep = true) : array
@@ -173,9 +175,7 @@ class ReflectedClass implements ReflectedInterface
public function getAttribute(string $attributeType): ?object public function getAttribute(string $attributeType): ?object
{ {
foreach($this->getAttributes(true, $attributeType) as $attribute) { foreach($this->getAttributes(true, $attributeType) as $attribute) {
if ($attribute->object instanceof $attributeType) { return $attribute;
return $attribute;
}
} }
return null; return null;
+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,
) {} ) {}
} }
+5 -10
View File
@@ -23,10 +23,7 @@ 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;
#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 public static function fromClass(ReflectionClass|string $class, ? CacheInterface $cache = null) : self
@@ -66,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() ) {
@@ -87,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;
}
} }
+14 -5
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,11 +100,13 @@ 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($objects as $object) { foreach($objects as $object) {
@@ -104,12 +114,13 @@ class RouteFetcher {
$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]);
+29 -14
View File
@@ -14,11 +14,11 @@ 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;
@@ -40,25 +40,40 @@ class SecurityHandler {
return true; return true;
} }
public function taxus(string $className, string $methodName, object $user = null) : ? ResponseInterface public function taxus(string $className, string $methodName, ... $arguments) : ? ResponseInterface
{ {
$fromObject = $this->findAttributes(Attribute\Taxus::class, $className); $granted = $this->hasGrantPermission($className , $methodName, ...$arguments);
$fromMethod = $this->findAttributes(Attribute\Taxus::class, $className, $methodName);
if ($fromMethod || $fromObject) {
if ( $this->taxusGrantPermission($fromMethod, $user) || $this->taxusGrantPermission($fromObject, $user) ) {
return null;
}
if ($granted) {
return null;
}
else {
if ($this->unauthorizeResponse) { if ($this->unauthorizeResponse) {
return call_user_func_array($this->unauthorizeResponse, [ $user, ['method' => $fromMethod, 'object' => $fromObject ], $className, $methodName ]); if ($this->unauthorizeResponse instanceof ResponseInterface) {
return $this->unauthorizeResponse;
}
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; public function hasGrantPermission(string $className, string $methodName, ... $arguments) : bool
{
$fromObject = $this->findAttributes(Attribute\Taxus::class, $className);
$fromMethod = $this->findAttributes(Attribute\Taxus::class, $className, $methodName);
if ($fromMethod) {
return $this->taxusGrantPermission($fromMethod, ... $arguments);
}
elseif ($fromObject) {
return $this->taxusGrantPermission($fromObject, ... $arguments);
}
return true;
} }
protected function findAttributes(string $attribute, string $class, ? string $method = null) : array protected function findAttributes(string $attribute, string $class, ? string $method = null) : array
@@ -86,10 +101,10 @@ class SecurityHandler {
return []; return [];
} }
protected function taxusGrantPermission(array $attributeList, object $user = null) : bool protected function taxusGrantPermission(array $attributeList, ...$arguments) : bool
{ {
foreach ($attributeList as $item) { foreach ($attributeList as $item) {
if ( $grant = $this->taxus->granted($item->privilege, $user, $item) ) { if ( $grant = $this->taxus->granted($item->privilege, ... array_merge($arguments, [ $item ])) ) {
if (is_bool($grant) ? $grant : $grant === TaxusGrantEnum::Authorized) { if (is_bool($grant) ? $grant : $grant === TaxusGrantEnum::Authorized) {
return true; return true;
} }