- Merged multiple sub-libraries
This commit is contained in:
		
							parent
							
								
									767152e0c3
								
							
						
					
					
						commit
						cee97fca25
					
				
							
								
								
									
										24
									
								
								src/Breadcrumb/Attribute/Method/Breadcrumb.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/Breadcrumb/Attribute/Method/Breadcrumb.php
									
									
									
									
									
										Normal file
									
								
							| @ -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 = "", | ||||
|     ) {} | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										69
									
								
								src/Breadcrumb/Breadcrumb.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/Breadcrumb/Breadcrumb.php
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								src/Cli/Attribute/Command.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/Cli/Attribute/Command.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||
|     ) {} | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/Cli/Attribute/Option.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Cli/Attribute/Option.php
									
									
									
									
									
										Normal file
									
								
							| @ -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 { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										136
									
								
								src/Cli/CommandFetcher.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/Cli/CommandFetcher.php
									
									
									
									
									
										Normal file
									
								
							| @ -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"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/Cronard/Attribute/Method/Cronard.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Cronard/Attribute/Method/Cronard.php
									
									
									
									
									
										Normal 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, | ||||
|     ) {} | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/Cronard/Attribute/Object/Cronard.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Cronard/Attribute/Object/Cronard.php
									
									
									
									
									
										Normal file
									
								
							| @ -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", | ||||
|     ) {} | ||||
| } | ||||
							
								
								
									
										99
									
								
								src/Cronard/TaskFetcher.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/Cronard/TaskFetcher.php
									
									
									
									
									
										Normal file
									
								
							| @ -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"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/Event/Attribute/Method/Event.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Event/Attribute/Method/Event.php
									
									
									
									
									
										Normal 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, | ||||
|     ) {} | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/Event/Attribute/Object/Event.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Event/Attribute/Object/Event.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||
|     ) {} | ||||
| } | ||||
							
								
								
									
										99
									
								
								src/Event/EventFetcher.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/Event/EventFetcher.php
									
									
									
									
									
										Normal file
									
								
							| @ -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"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										37
									
								
								src/Route/Attribute/Method/Route.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Route/Attribute/Method/Route.php
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/Route/Attribute/Object/Route.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Route/Attribute/Object/Route.php
									
									
									
									
									
										Normal file
									
								
							| @ -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 = "", | ||||
|     ){} | ||||
| } | ||||
							
								
								
									
										146
									
								
								src/Route/RouteFetcher.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/Route/RouteFetcher.php
									
									
									
									
									
										Normal file
									
								
							| @ -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"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/Security/Attribute/Security.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Security/Attribute/Security.php
									
									
									
									
									
										Normal 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, | ||||
|     ) {} | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/Security/Attribute/Taxus.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Security/Attribute/Taxus.php
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||
|     ) {} | ||||
| } | ||||
							
								
								
									
										75
									
								
								src/Security/SecurityHandler.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/Security/SecurityHandler.php
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user