From 56d7b9ff1067a2ee2e3396d0afaa81560c39afc9 Mon Sep 17 00:00:00 2001 From: abdulmohsen Date: Sat, 2 Mar 2024 20:28:24 +0300 Subject: [PATCH] Re-factored the routing to support API in the future. --- src/API/Backends/Create.php | 23 +++ .../Plex/Commands/AccessTokenCommand.php | 4 +- .../Plex/Commands/DiscoverCommand.php | 4 +- src/Commands/Backend/Ignore/ListCommand.php | 4 +- src/Commands/Backend/Ignore/ManageCommand.php | 4 +- src/Commands/Backend/InfoCommand.php | 4 +- .../Backend/Library/IgnoreCommand.php | 4 +- src/Commands/Backend/Library/ListCommand.php | 4 +- .../Backend/Library/MismatchCommand.php | 4 +- .../Backend/Library/UnmatchedCommand.php | 4 +- src/Commands/Backend/RestoreCommand.php | 4 +- src/Commands/Backend/Search/IdCommand.php | 4 +- src/Commands/Backend/Search/QueryCommand.php | 4 +- src/Commands/Backend/Users/ListCommand.php | 4 +- .../Backend/Users/SessionsCommand.php | 4 +- src/Commands/Backend/VersionCommand.php | 4 +- src/Commands/Config/AddCommand.php | 4 +- src/Commands/Config/DeleteCommand.php | 4 +- src/Commands/Config/EditCommand.php | 4 +- src/Commands/Config/ListCommand.php | 4 +- src/Commands/Config/ManageCommand.php | 4 +- src/Commands/Config/UnifyCommand.php | 4 +- src/Commands/Config/ViewCommand.php | 4 +- src/Commands/Database/ListCommand.php | 4 +- src/Commands/Database/ParityCommand.php | 4 +- src/Commands/Database/QueueCommand.php | 4 +- src/Commands/State/BackupCommand.php | 4 +- src/Commands/State/ExportCommand.php | 4 +- src/Commands/State/ImportCommand.php | 4 +- src/Commands/State/ProgressCommand.php | 6 +- src/Commands/State/PushCommand.php | 4 +- src/Commands/State/RequestsCommand.php | 4 +- src/Commands/System/EnvCommand.php | 4 +- src/Commands/System/IndexCommand.php | 4 +- src/Commands/System/LogsCommand.php | 4 +- src/Commands/System/MaintenanceCommand.php | 4 +- src/Commands/System/MakeCommand.php | 4 +- src/Commands/System/MigrationsCommand.php | 4 +- src/Commands/System/PHPCommand.php | 4 +- src/Commands/System/PruneCommand.php | 4 +- src/Commands/System/ReportCommand.php | 4 +- src/Commands/System/RoutesCommand.php | 10 +- src/Commands/System/ServerCommand.php | 4 +- src/Commands/System/TasksCommand.php | 4 +- src/Commands/System/TinkerCommand.php | 4 +- src/Libs/Attributes/Route/Cli.php | 16 ++ src/Libs/Attributes/Route/Delete.php | 30 ++++ src/Libs/Attributes/Route/Get.php | 30 ++++ src/Libs/Attributes/Route/Head.php | 30 ++++ src/Libs/Attributes/Route/Options.php | 30 ++++ src/Libs/Attributes/Route/Patch.php | 30 ++++ src/Libs/Attributes/Route/Post.php | 30 ++++ src/Libs/Attributes/Route/Put.php | 30 ++++ src/Libs/Attributes/Route/Route.php | 64 ++++++++ src/Libs/HTTP_STATUS.php | 73 +++++++++ src/Libs/Initializer.php | 11 +- src/Libs/Routable.php | 26 ---- src/Libs/Router.php | 144 ++++++++++++------ src/Libs/helpers.php | 89 ++++++++--- tests/Libs/HelpersTest.php | 3 +- 60 files changed, 649 insertions(+), 194 deletions(-) create mode 100644 src/API/Backends/Create.php create mode 100644 src/Libs/Attributes/Route/Cli.php create mode 100644 src/Libs/Attributes/Route/Delete.php create mode 100644 src/Libs/Attributes/Route/Get.php create mode 100644 src/Libs/Attributes/Route/Head.php create mode 100644 src/Libs/Attributes/Route/Options.php create mode 100644 src/Libs/Attributes/Route/Patch.php create mode 100644 src/Libs/Attributes/Route/Post.php create mode 100644 src/Libs/Attributes/Route/Put.php create mode 100644 src/Libs/Attributes/Route/Route.php create mode 100644 src/Libs/HTTP_STATUS.php delete mode 100644 src/Libs/Routable.php diff --git a/src/API/Backends/Create.php b/src/API/Backends/Create.php new file mode 100644 index 00000000..6d65d67b --- /dev/null +++ b/src/API/Backends/Create.php @@ -0,0 +1,23 @@ + 'Create' + ], HTTP_STATUS::HTTP_OK); + } +} diff --git a/src/Backends/Plex/Commands/AccessTokenCommand.php b/src/Backends/Plex/Commands/AccessTokenCommand.php index 1043454f..1c202e01 100644 --- a/src/Backends/Plex/Commands/AccessTokenCommand.php +++ b/src/Backends/Plex/Commands/AccessTokenCommand.php @@ -6,9 +6,9 @@ namespace App\Backends\Plex\Commands; use App\Command; use App\Commands\Backend\Users\ListCommand; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use Psr\Log\LoggerInterface as iLogger; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -19,7 +19,7 @@ use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class AccessTokenCommand extends Command { public const ROUTE = 'plex:accesstoken'; diff --git a/src/Backends/Plex/Commands/DiscoverCommand.php b/src/Backends/Plex/Commands/DiscoverCommand.php index c1fb6b2e..70635c37 100644 --- a/src/Backends/Plex/Commands/DiscoverCommand.php +++ b/src/Backends/Plex/Commands/DiscoverCommand.php @@ -6,9 +6,9 @@ namespace App\Backends\Plex\Commands; use App\Backends\Plex\PlexClient; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Exceptions\Backends\RuntimeException; use App\Libs\Options; -use App\Libs\Routable; use Psr\Log\LoggerInterface as iLogger; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -19,7 +19,7 @@ use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp; -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class DiscoverCommand extends Command { public const ROUTE = 'plex:discover'; diff --git a/src/Commands/Backend/Ignore/ListCommand.php b/src/Commands/Backend/Ignore/ListCommand.php index c619ef2d..364cba03 100644 --- a/src/Commands/Backend/Ignore/ListCommand.php +++ b/src/Commands/Backend/Ignore/ListCommand.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace App\Commands\Backend\Ignore; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Container; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Entity\StateInterface as iState; use App\Libs\Guid; -use App\Libs\Routable; use PDO; use Psr\Http\Message\UriInterface; use Symfony\Component\Console\Completion\CompletionInput; @@ -24,7 +24,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * Represents a command for listing ignored external ids. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ListCommand extends Command { public const ROUTE = 'backend:ignore:list'; diff --git a/src/Commands/Backend/Ignore/ManageCommand.php b/src/Commands/Backend/Ignore/ManageCommand.php index 64cd4375..03d903c7 100644 --- a/src/Commands/Backend/Ignore/ManageCommand.php +++ b/src/Commands/Backend/Ignore/ManageCommand.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace App\Commands\Backend\Ignore; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Entity\StateInterface as iState; use App\Libs\Exceptions\InvalidArgumentException; use App\Libs\Exceptions\RuntimeException; use App\Libs\Guid; -use App\Libs\Routable; use App\Libs\Stream; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -22,7 +22,7 @@ use Symfony\Component\Yaml\Yaml; * Class ManageCommand * This class is responsible for adding or removing an external id from the ignore list. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ManageCommand extends Command { public const ROUTE = 'backend:ignore:manage'; diff --git a/src/Commands/Backend/InfoCommand.php b/src/Commands/Backend/InfoCommand.php index fbc6c5c5..3198d8b0 100644 --- a/src/Commands/Backend/InfoCommand.php +++ b/src/Commands/Backend/InfoCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\Backend; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -21,7 +21,7 @@ use Symfony\Component\Yaml\Yaml; * * @Routable(command: self::ROUTE) */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class InfoCommand extends Command { public const ROUTE = 'backend:info'; diff --git a/src/Commands/Backend/Library/IgnoreCommand.php b/src/Commands/Backend/Library/IgnoreCommand.php index b48ef157..90ed7020 100644 --- a/src/Commands/Backend/Library/IgnoreCommand.php +++ b/src/Commands/Backend/Library/IgnoreCommand.php @@ -6,8 +6,8 @@ namespace App\Commands\Backend\Library; use App\Command; use App\Commands\Config\EditCommand; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; -use App\Libs\Routable; use App\Libs\Stream; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -21,7 +21,7 @@ use Symfony\Component\Yaml\Yaml; * * This class represents a command for managing ignored libraries in the Backend. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class IgnoreCommand extends Command { public const ROUTE = 'backend:library:ignore'; diff --git a/src/Commands/Backend/Library/ListCommand.php b/src/Commands/Backend/Library/ListCommand.php index 91bc1125..4316f03b 100644 --- a/src/Commands/Backend/Library/ListCommand.php +++ b/src/Commands/Backend/Library/ListCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\Backend\Library; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -19,7 +19,7 @@ use Symfony\Component\Yaml\Yaml; * * This command list the backend libraries. This help you to know which library are supported. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ListCommand extends Command { public const ROUTE = 'backend:library:list'; diff --git a/src/Commands/Backend/Library/MismatchCommand.php b/src/Commands/Backend/Library/MismatchCommand.php index 19260a09..8ea77107 100644 --- a/src/Commands/Backend/Library/MismatchCommand.php +++ b/src/Commands/Backend/Library/MismatchCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\Backend\Library; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputArgument; @@ -21,7 +21,7 @@ use Symfony\Component\Yaml\Yaml; * * Find possible mis-matched item in a libraries. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class MismatchCommand extends Command { public const ROUTE = 'backend:library:mismatch'; diff --git a/src/Commands/Backend/Library/UnmatchedCommand.php b/src/Commands/Backend/Library/UnmatchedCommand.php index bf56f2c4..7512ee73 100644 --- a/src/Commands/Backend/Library/UnmatchedCommand.php +++ b/src/Commands/Backend/Library/UnmatchedCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\Backend\Library; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -19,7 +19,7 @@ use Symfony\Component\Yaml\Yaml; * * This command helps find unmatched items in user libraries. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class UnmatchedCommand extends Command { public const ROUTE = 'backend:library:unmatched'; diff --git a/src/Commands/Backend/RestoreCommand.php b/src/Commands/Backend/RestoreCommand.php index caa3c5da..ce37ea8b 100644 --- a/src/Commands/Backend/RestoreCommand.php +++ b/src/Commands/Backend/RestoreCommand.php @@ -5,11 +5,11 @@ declare(strict_types=1); namespace App\Commands\Backend; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Mappers\Import\RestoreMapper; use App\Libs\Options; use App\Libs\QueueRequests; -use App\Libs\Routable; use DirectoryIterator; use Psr\Log\LoggerInterface as iLogger; use RuntimeException; @@ -28,7 +28,7 @@ use Throwable; * * Command used to restore a backend's play state from a backup file. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class RestoreCommand extends Command { public const ROUTE = 'backend:restore'; diff --git a/src/Commands/Backend/Search/IdCommand.php b/src/Commands/Backend/Search/IdCommand.php index 64dcefb7..d34b613d 100644 --- a/src/Commands/Backend/Search/IdCommand.php +++ b/src/Commands/Backend/Search/IdCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\Backend\Search; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -20,7 +20,7 @@ use Throwable; * * This class represents a command for getting backend metadata related to a specific id. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class IdCommand extends Command { public const ROUTE = 'backend:search:id'; diff --git a/src/Commands/Backend/Search/QueryCommand.php b/src/Commands/Backend/Search/QueryCommand.php index ed99e865..44b3d856 100644 --- a/src/Commands/Backend/Search/QueryCommand.php +++ b/src/Commands/Backend/Search/QueryCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\Backend\Search; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -21,7 +21,7 @@ use Symfony\Component\Yaml\Yaml; * * @note Investigate the possibility of using the command to search all backends at once. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class QueryCommand extends Command { public const ROUTE = 'backend:search:query'; diff --git a/src/Commands/Backend/Users/ListCommand.php b/src/Commands/Backend/Users/ListCommand.php index 12da998d..9cf7a8eb 100644 --- a/src/Commands/Backend/Users/ListCommand.php +++ b/src/Commands/Backend/Users/ListCommand.php @@ -6,9 +6,9 @@ namespace App\Commands\Backend\Users; use App\Backends\Plex\Commands\AccessTokenCommand; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -22,7 +22,7 @@ use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; * This command lists the users from the backend. The configured backend token should have access to do so otherwise, * or an error will be thrown. This mainly concerns plex managed users as their tokens are limited. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ListCommand extends Command { public const ROUTE = 'backend:users:list'; diff --git a/src/Commands/Backend/Users/SessionsCommand.php b/src/Commands/Backend/Users/SessionsCommand.php index bf9d244f..15a77bed 100644 --- a/src/Commands/Backend/Users/SessionsCommand.php +++ b/src/Commands/Backend/Users/SessionsCommand.php @@ -5,11 +5,11 @@ declare(strict_types=1); namespace App\Commands\Backend\Users; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Entity\StateInterface as iState; use App\Libs\Options; -use App\Libs\Routable; use DateTimeInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -19,7 +19,7 @@ use Symfony\Component\Yaml\Yaml; /** * Get backend active sessions. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class SessionsCommand extends Command { public const ROUTE = 'backend:users:sessions'; diff --git a/src/Commands/Backend/VersionCommand.php b/src/Commands/Backend/VersionCommand.php index a1252821..5af3ab06 100644 --- a/src/Commands/Backend/VersionCommand.php +++ b/src/Commands/Backend/VersionCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\Backend; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -18,7 +18,7 @@ use Symfony\Component\Yaml\Yaml; * * The VersionCommand class is used to retrieve the backend product version. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class VersionCommand extends Command { public const ROUTE = 'backend:version'; diff --git a/src/Commands/Config/AddCommand.php b/src/Commands/Config/AddCommand.php index 893a7465..40c5b36e 100644 --- a/src/Commands/Config/AddCommand.php +++ b/src/Commands/Config/AddCommand.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace App\Commands\Config; use App\Command; -use App\Libs\Routable; +use App\Libs\Attributes\Route\Cli; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; @@ -21,7 +21,7 @@ use Symfony\Component\Console\Question\Question; * And act as shortcut for running the following command: * config:manage --add -- backend_name */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class AddCommand extends Command { public const ROUTE = 'config:add'; diff --git a/src/Commands/Config/DeleteCommand.php b/src/Commands/Config/DeleteCommand.php index 11160495..47f008c8 100644 --- a/src/Commands/Config/DeleteCommand.php +++ b/src/Commands/Config/DeleteCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\Config; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Database\DatabaseInterface as iDB; -use App\Libs\Routable; use App\Libs\Stream; use PDO; use Symfony\Component\Console\Input\InputArgument; @@ -24,7 +24,7 @@ use Symfony\Component\Yaml\Yaml; * * @package App\Command */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class DeleteCommand extends Command { public const ROUTE = 'config:delete'; diff --git a/src/Commands/Config/EditCommand.php b/src/Commands/Config/EditCommand.php index 66571c69..40ac79f0 100644 --- a/src/Commands/Config/EditCommand.php +++ b/src/Commands/Config/EditCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\Config; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; -use App\Libs\Routable; use App\Libs\Stream; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -22,7 +22,7 @@ use Throwable; * * This class allows the user to edit backend config settings inline. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class EditCommand extends Command { public const ROUTE = 'config:edit'; diff --git a/src/Commands/Config/ListCommand.php b/src/Commands/Config/ListCommand.php index e6bf6bde..a5738336 100644 --- a/src/Commands/Config/ListCommand.php +++ b/src/Commands/Config/ListCommand.php @@ -5,16 +5,16 @@ declare(strict_types=1); namespace App\Commands\Config; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use DateTimeInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Yaml\Yaml; -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ListCommand extends Command { public const ROUTE = 'config:list'; diff --git a/src/Commands/Config/ManageCommand.php b/src/Commands/Config/ManageCommand.php index ae1ac717..649950f8 100644 --- a/src/Commands/Config/ManageCommand.php +++ b/src/Commands/Config/ManageCommand.php @@ -7,9 +7,9 @@ namespace App\Commands\Config; use App\Command; use App\Commands\State\ImportCommand; use App\Commands\System\IndexCommand; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Options; -use App\Libs\Routable; use App\Libs\Stream; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Input\ArrayInput; @@ -27,7 +27,7 @@ use Throwable; * * This class allows the user to manage backend settings interactively. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ManageCommand extends Command { public const ROUTE = 'config:manage'; diff --git a/src/Commands/Config/UnifyCommand.php b/src/Commands/Config/UnifyCommand.php index d27d593d..41cf9389 100644 --- a/src/Commands/Config/UnifyCommand.php +++ b/src/Commands/Config/UnifyCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\Config; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; -use App\Libs\Routable; use App\Libs\Stream; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -24,7 +24,7 @@ use Throwable; * * @package Your\Namespace */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class UnifyCommand extends Command { public const ROUTE = 'config:unify'; diff --git a/src/Commands/Config/ViewCommand.php b/src/Commands/Config/ViewCommand.php index af8cbbe8..7afb4a53 100644 --- a/src/Commands/Config/ViewCommand.php +++ b/src/Commands/Config/ViewCommand.php @@ -5,10 +5,10 @@ declare(strict_types=1); namespace App\Commands\Config; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Exceptions\RuntimeException; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Helper\Table; @@ -24,7 +24,7 @@ use Symfony\Component\Yaml\Yaml; * * This command display all backend's information. User can select and/or filter the displayed information. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ViewCommand extends Command { public const ROUTE = 'config:view'; diff --git a/src/Commands/Database/ListCommand.php b/src/Commands/Database/ListCommand.php index a35ce7bc..a3d249b1 100644 --- a/src/Commands/Database/ListCommand.php +++ b/src/Commands/Database/ListCommand.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Commands\Database; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Container; use App\Libs\Database\DatabaseInterface as iDB; @@ -12,7 +13,6 @@ use App\Libs\Entity\StateInterface as iState; use App\Libs\Exceptions\RuntimeException; use App\Libs\Guid; use App\Libs\Mappers\Import\DirectMapper; -use App\Libs\Routable; use PDO; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -27,7 +27,7 @@ use Symfony\Component\Yaml\Yaml; * * This class act as frontend for the state table, it allows the user to see and manipulate view of state table. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ListCommand extends Command { public const ROUTE = 'db:list'; diff --git a/src/Commands/Database/ParityCommand.php b/src/Commands/Database/ParityCommand.php index ea3c3c90..3a3931ea 100644 --- a/src/Commands/Database/ParityCommand.php +++ b/src/Commands/Database/ParityCommand.php @@ -5,11 +5,11 @@ declare(strict_types=1); namespace App\Commands\Database; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Container; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Entity\StateInterface as iState; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -21,7 +21,7 @@ use Symfony\Component\Yaml\Yaml; * * This command helps find possible conflicting records that are being reported by backends. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ParityCommand extends Command { public const ROUTE = 'db:parity'; diff --git a/src/Commands/Database/QueueCommand.php b/src/Commands/Database/QueueCommand.php index 387b7a46..8b6513b7 100644 --- a/src/Commands/Database/QueueCommand.php +++ b/src/Commands/Database/QueueCommand.php @@ -5,10 +5,10 @@ declare(strict_types=1); namespace App\Commands\Database; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Container; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Entity\StateInterface as iState; -use App\Libs\Routable; use Psr\SimpleCache\CacheInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -21,7 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * @package YourPackageNamespace */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class QueueCommand extends Command { public const ROUTE = 'db:queue'; diff --git a/src/Commands/State/BackupCommand.php b/src/Commands/State/BackupCommand.php index a28576f5..b32eb005 100644 --- a/src/Commands/State/BackupCommand.php +++ b/src/Commands/State/BackupCommand.php @@ -5,10 +5,10 @@ declare(strict_types=1); namespace App\Commands\State; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Mappers\Import\DirectMapper; use App\Libs\Options; -use App\Libs\Routable; use App\Libs\Stream; use Psr\Http\Message\StreamInterface; use Psr\Log\LoggerInterface; @@ -24,7 +24,7 @@ use Throwable; * * Generate portable backup of backends play state. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class BackupCommand extends Command { public const ROUTE = 'state:backup'; diff --git a/src/Commands/State/ExportCommand.php b/src/Commands/State/ExportCommand.php index e3ee25fa..99bb969a 100644 --- a/src/Commands/State/ExportCommand.php +++ b/src/Commands/State/ExportCommand.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Commands\State; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Entity\StateInterface as iState; @@ -13,7 +14,6 @@ use App\Libs\Mappers\Import\DirectMapper; use App\Libs\Message; use App\Libs\Options; use App\Libs\QueueRequests; -use App\Libs\Routable; use App\Libs\Stream; use Monolog\Logger; use Psr\Log\LoggerInterface as iLogger; @@ -30,7 +30,7 @@ use Throwable; * * @package App\Console\Commands\State */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class ExportCommand extends Command { public const ROUTE = 'state:export'; diff --git a/src/Commands/State/ImportCommand.php b/src/Commands/State/ImportCommand.php index 86a7f770..7b150ecc 100644 --- a/src/Commands/State/ImportCommand.php +++ b/src/Commands/State/ImportCommand.php @@ -7,6 +7,7 @@ namespace App\Commands\State; use App\Command; use App\Commands\Backend\Library\UnmatchedCommand; use App\Commands\Config\EditCommand; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Container; use App\Libs\Database\DatabaseInterface as iDB; @@ -16,7 +17,6 @@ use App\Libs\Mappers\Import\DirectMapper; use App\Libs\Mappers\ImportInterface as iImport; use App\Libs\Message; use App\Libs\Options; -use App\Libs\Routable; use App\Libs\Stream; use Monolog\Logger; use Psr\Log\LoggerInterface as iLogger; @@ -34,7 +34,7 @@ use Throwable; * * This command imports metadata and play state of items from backends and updates the local database. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class ImportCommand extends Command { public const ROUTE = 'state:import'; diff --git a/src/Commands/State/ProgressCommand.php b/src/Commands/State/ProgressCommand.php index 53b9441b..99b8817b 100644 --- a/src/Commands/State/ProgressCommand.php +++ b/src/Commands/State/ProgressCommand.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace App\Commands\State; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Entity\StateInterface as iState; use App\Libs\Exceptions\Backends\UnexpectedVersionException; use App\Libs\Options; use App\Libs\QueueRequests; -use App\Libs\Routable; use Psr\Log\LoggerInterface as iLogger; use Psr\SimpleCache\CacheInterface as iCache; use Symfony\Component\Console\Input\InputInterface; @@ -29,7 +29,7 @@ use Throwable; * If no metadata is available for a backend, * the watch progress update won't be sent to that backend */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class ProgressCommand extends Command { public const ROUTE = 'state:progress'; @@ -213,7 +213,7 @@ class ProgressCommand extends Command $backend['options'] = $opts; $backend['class'] = $this->getBackend(name: $name, config: $backend); - + $backend['class']->progress(entities: $entities, queue: $this->queue); } /** @noinspection PhpRedundantCatchClauseInspection */ catch (UnexpectedVersionException $e) { diff --git a/src/Commands/State/PushCommand.php b/src/Commands/State/PushCommand.php index 5a315eff..21b6b283 100644 --- a/src/Commands/State/PushCommand.php +++ b/src/Commands/State/PushCommand.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace App\Commands\State; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Container; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Entity\StateInterface as iState; use App\Libs\Options; use App\Libs\QueueRequests; -use App\Libs\Routable; use Psr\Log\LoggerInterface as iLogger; use Psr\SimpleCache\CacheInterface as iCache; use Symfony\Component\Console\Input\InputInterface; @@ -25,7 +25,7 @@ use Throwable; * This class represents a command that pushes webhook queued events. * It sends change play state requests to the supported backends. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class PushCommand extends Command { public const ROUTE = 'state:push'; diff --git a/src/Commands/State/RequestsCommand.php b/src/Commands/State/RequestsCommand.php index b88e746a..e8086d34 100644 --- a/src/Commands/State/RequestsCommand.php +++ b/src/Commands/State/RequestsCommand.php @@ -5,11 +5,11 @@ declare(strict_types=1); namespace App\Commands\State; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Entity\StateInterface as iState; use App\Libs\Mappers\Import\DirectMapper; use App\Libs\Options; -use App\Libs\Routable; use Psr\Log\LoggerInterface as iLogger; use Psr\SimpleCache\CacheInterface as iCache; use Symfony\Component\Console\Helper\Table; @@ -25,7 +25,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * @package Your\Namespace */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] class RequestsCommand extends Command { public const ROUTE = 'state:requests'; diff --git a/src/Commands/System/EnvCommand.php b/src/Commands/System/EnvCommand.php index 6b688bc4..7cb9f92a 100644 --- a/src/Commands/System/EnvCommand.php +++ b/src/Commands/System/EnvCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -15,7 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * This command displays the environment variables that were loaded during the execution of the tool. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class EnvCommand extends Command { public const ROUTE = 'system:env'; diff --git a/src/Commands/System/IndexCommand.php b/src/Commands/System/IndexCommand.php index db965d56..d3a7df43 100644 --- a/src/Commands/System/IndexCommand.php +++ b/src/Commands/System/IndexCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Options; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -17,7 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * This command ensures that the database has correct indexes. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class IndexCommand extends Command { public const ROUTE = 'system:index'; diff --git a/src/Commands/System/LogsCommand.php b/src/Commands/System/LogsCommand.php index 367ee466..094ee84d 100644 --- a/src/Commands/System/LogsCommand.php +++ b/src/Commands/System/LogsCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Exceptions\InvalidArgumentException; -use App\Libs\Routable; use LimitIterator; use SplFileObject; use Symfony\Component\Console\Completion\CompletionInput; @@ -22,7 +22,7 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; * * This class is used to view and clear log files. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class LogsCommand extends Command { public const ROUTE = 'system:logs'; diff --git a/src/Commands/System/MaintenanceCommand.php b/src/Commands/System/MaintenanceCommand.php index 54ab21ac..a335a12e 100644 --- a/src/Commands/System/MaintenanceCommand.php +++ b/src/Commands/System/MaintenanceCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Database\DatabaseInterface as iDB; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -15,7 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * Runs maintenance tasks on the database. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class MaintenanceCommand extends Command { public const ROUTE = 'system:db:maintenance'; diff --git a/src/Commands/System/MakeCommand.php b/src/Commands/System/MakeCommand.php index 295000c5..b6c598c3 100644 --- a/src/Commands/System/MakeCommand.php +++ b/src/Commands/System/MakeCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Database\DatabaseInterface as iDB; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -16,7 +16,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * This class represents a command to create a database schema migration file. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class MakeCommand extends Command { public const ROUTE = 'system:db:make'; diff --git a/src/Commands/System/MigrationsCommand.php b/src/Commands/System/MigrationsCommand.php index b5b12b77..46850b01 100644 --- a/src/Commands/System/MigrationsCommand.php +++ b/src/Commands/System/MigrationsCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Database\DatabaseInterface as iDB; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -16,7 +16,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * Database migrations runner. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class MigrationsCommand extends Command { public const ROUTE = 'system:db:migrations'; diff --git a/src/Commands/System/PHPCommand.php b/src/Commands/System/PHPCommand.php index 401eadaf..77ca6b37 100644 --- a/src/Commands/System/PHPCommand.php +++ b/src/Commands/System/PHPCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; -use App\Libs\Routable; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -17,7 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface; * This command is used to generate expected values for php.ini and fpm pool worker. * To generate fpm values, use the "--fpm" option. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class PHPCommand extends Command { public const ROUTE = 'system:php'; diff --git a/src/Commands/System/PruneCommand.php b/src/Commands/System/PruneCommand.php index 2843b7ae..51188e71 100644 --- a/src/Commands/System/PruneCommand.php +++ b/src/Commands/System/PruneCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; -use App\Libs\Routable; use Psr\Log\LoggerInterface; use SplFileInfo; use Symfony\Component\Console\Input\InputInterface; @@ -19,7 +19,7 @@ use Symfony\Component\Console\Output\OutputInterface; * This command removes automatically generated files like logs and backups. * It provides an option to run in dry-run mode to see what files will be removed without actually removing them. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class PruneCommand extends Command { public const ROUTE = 'system:prune'; diff --git a/src/Commands/System/ReportCommand.php b/src/Commands/System/ReportCommand.php index dfd381fd..6b68f652 100644 --- a/src/Commands/System/ReportCommand.php +++ b/src/Commands/System/ReportCommand.php @@ -5,12 +5,12 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Database\DatabaseInterface as iDB; use App\Libs\Entity\StateEntity; use App\Libs\Extends\Date; use App\Libs\Options; -use App\Libs\Routable; use App\Libs\Stream; use Cron\CronExpression; use LimitIterator; @@ -25,7 +25,7 @@ use Throwable; * * Show basic information for diagnostics. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ReportCommand extends Command { public const ROUTE = 'system:report'; diff --git a/src/Commands/System/RoutesCommand.php b/src/Commands/System/RoutesCommand.php index 0aa5e9ca..47554ddc 100644 --- a/src/Commands/System/RoutesCommand.php +++ b/src/Commands/System/RoutesCommand.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; -use App\Libs\Routable; +use App\Libs\Attributes\Route\Cli; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -14,7 +14,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * This command is used to generate routes for commands. It is automatically run on container startup. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class RoutesCommand extends Command { public const ROUTE = 'system:routes'; @@ -25,11 +25,11 @@ final class RoutesCommand extends Command protected function configure(): void { $this->setName(self::ROUTE) - ->setDescription('Generate commands routes.')->setHelp( + ->setDescription('Generate routes')->setHelp( <<regeneration for commands. - You do not need to run this command unless told by the team. + This command force routes regeneration for commands & API endpoint. + You do not need to run this command unless told by the devs. This is done automatically on container startup. HELP diff --git a/src/Commands/System/ServerCommand.php b/src/Commands/System/ServerCommand.php index 5745282f..534e8f51 100644 --- a/src/Commands/System/ServerCommand.php +++ b/src/Commands/System/ServerCommand.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; -use App\Libs\Routable; +use App\Libs\Attributes\Route\Cli; use App\Libs\Server; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -18,7 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface; * * @package YourPackage */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class ServerCommand extends Command { public const ROUTE = 'system:server'; diff --git a/src/Commands/System/TasksCommand.php b/src/Commands/System/TasksCommand.php index 149d983b..074e0349 100644 --- a/src/Commands/System/TasksCommand.php +++ b/src/Commands/System/TasksCommand.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; +use App\Libs\Attributes\Route\Cli; use App\Libs\Config; use App\Libs\Extends\ConsoleOutput; -use App\Libs\Routable; use App\Libs\Stream; use Cron\CronExpression; use Exception; @@ -25,7 +25,7 @@ use Throwable; * * Automates the runs of scheduled tasks. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class TasksCommand extends Command { public const ROUTE = 'system:tasks'; diff --git a/src/Commands/System/TinkerCommand.php b/src/Commands/System/TinkerCommand.php index 252a25e7..bb59aec2 100644 --- a/src/Commands/System/TinkerCommand.php +++ b/src/Commands/System/TinkerCommand.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace App\Commands\System; use App\Command; -use App\Libs\Routable; +use App\Libs\Attributes\Route\Cli; use Psy\Configuration; use Psy\Shell; use Psy\VersionUpdater\Checker; @@ -19,7 +19,7 @@ use Symfony\Component\Console\Output\OutputInterface as iOutput; * * Interactive shell command to manually write scripts. */ -#[Routable(command: self::ROUTE)] +#[Cli(command: self::ROUTE)] final class TinkerCommand extends Command { public const ROUTE = 'system:tinker'; diff --git a/src/Libs/Attributes/Route/Cli.php b/src/Libs/Attributes/Route/Cli.php new file mode 100644 index 00000000..7fba726e --- /dev/null +++ b/src/Libs/Attributes/Route/Cli.php @@ -0,0 +1,16 @@ + true]); + } +} diff --git a/src/Libs/Attributes/Route/Delete.php b/src/Libs/Attributes/Route/Delete.php new file mode 100644 index 00000000..63df63c9 --- /dev/null +++ b/src/Libs/Attributes/Route/Delete.php @@ -0,0 +1,30 @@ +methods = $methods; + $this->pattern = $pattern; + $this->middleware = is_string($middleware) ? [$middleware] : $middleware; + $this->name = $name; + $this->port = null !== $port ? $this->fromConfig($port) : $port; + $this->scheme = null !== $scheme ? $this->fromConfig($scheme) : $scheme; + $this->host = null !== $host ? $this->fromConfig($host, fn($v) => parse_url($v, PHP_URL_HOST)) : $host; + + $this->isCli = true === (bool)ag($opts, 'cli', false); + } + + private function fromConfig(mixed $value, Closure|null $callback = null): mixed + { + if (is_string($value) && preg_match('#%{(.+?)}#s', $value, $match)) { + $config = Config::get($match[1]); + return null !== $callback && null !== $config ? $callback($config) : $config; + } + + return $value; + } +} diff --git a/src/Libs/HTTP_STATUS.php b/src/Libs/HTTP_STATUS.php new file mode 100644 index 00000000..aa642e8d --- /dev/null +++ b/src/Libs/HTTP_STATUS.php @@ -0,0 +1,73 @@ +has('routes') ? generateRoutes() : $cache->get('routes', []); + $routes = []; + $loader = false === $cache->has('routes_cli') ? generateRoutes() : $cache->get('routes_cli', []); - $this->cli->setCommandLoader( - new ContainerCommandLoader(Container::getContainer(), $routes) - ); + foreach ($loader as $route) { + $routes[ag($route, 'path')] = ag($route, 'callable'); + } + + $this->cli->setCommandLoader(new ContainerCommandLoader(Container::getContainer(), $routes)); $this->cli->run(output: $this->cliOutput); } catch (Throwable $e) { diff --git a/src/Libs/Routable.php b/src/Libs/Routable.php deleted file mode 100644 index 4a03bded..00000000 --- a/src/Libs/Routable.php +++ /dev/null @@ -1,26 +0,0 @@ -dirs; + } + public function generate(): array { $routes = []; foreach ($this->dirs as $path) { - $routes += $this->scanDirectory($path); + array_push($routes, ...$this->scanDirectory($path)); } + usort($routes, fn($a, $b) => strlen($a['path']) < strlen($b['path']) ? -1 : 1); + return $routes; } - /** - * Scans the given directory and returns an array of routes. - * - * @param string $dir The directory to scan for files. - * - * @return array An array of routes. - */ private function scanDirectory(string $dir): array { $classes = $routes = []; @@ -86,75 +75,130 @@ final readonly class Router continue; } - $routes += $this->getRoutes(new ReflectionClass($className)); + array_push($routes, ...$this->getRoutes(new ReflectionClass($className))); } return $routes; } - /** - * Get the routes for a given class. - * - * @param ReflectionClass $class The reflection instance of given class to get the routes for. - * - * @return array The routes for the class. - */ protected function getRoutes(ReflectionClass $class): array { $routes = []; - $attributes = $class->getAttributes(Routable::class, ReflectionAttribute::IS_INSTANCEOF); + $attributes = $class->getAttributes(Route::class, ReflectionAttribute::IS_INSTANCEOF); + + $invokable = false; + + foreach ($class->getMethods() as $method) { + if ($method->getName() === '__invoke') { + $invokable = true; + } + } foreach ($attributes as $attribute) { try { $attributeClass = $attribute->newInstance(); + + if (!$attributeClass instanceof Route) { + continue; + } } catch (Throwable) { continue; } - if (!$attributeClass instanceof Routable) { - continue; + if (false === $invokable && !$attributeClass->isCli) { + throw new InvalidArgumentException( + r( + 'Trying to route \'{route}\' to un-invokable class/method \'{callable}\'.', + [ + 'route' => $attributeClass->pattern, + 'callable' => $class->getName() + ] + ) + ); } - $routes[$attributeClass->command] = $class->getName(); + $routes[] = [ + 'path' => $attributeClass->pattern, + 'method' => $attributeClass->methods, + 'callable' => $class->getName(), + 'host' => $attributeClass->host, + 'middlewares' => $attributeClass->middleware, + 'name' => $attributeClass->name, + 'port' => $attributeClass->port, + 'scheme' => $attributeClass->scheme, + ]; + } + + foreach ($class->getMethods() as $method) { + $attributes = $method->getAttributes(Route::class, ReflectionAttribute::IS_INSTANCEOF); + + foreach ($attributes as $attribute) { + try { + $attributeClass = $attribute->newInstance(); + if (!$attributeClass instanceof Route) { + continue; + } + } catch (Throwable) { + continue; + } + + $call = $method->getName() === '__invoke' ? $class->getName() : [$class->getName(), $method->getName()]; + + $routes[] = [ + 'path' => $attributeClass->pattern, + 'method' => $attributeClass->methods, + 'callable' => $call, + 'host' => $attributeClass->host, + 'middlewares' => $attributeClass->middleware, + 'name' => $attributeClass->name, + 'port' => $attributeClass->port, + 'scheme' => $attributeClass->scheme, + ]; + } } return $routes; } - /** - * Parses a file and extracts the namespaces and classes. - * - * @param string $file The path to the file to parse. - * - * @return array|false An array of fully qualified class names if classes are found, otherwise false. - * @throws RuntimeException If the file cannot be read. - */ private function parseFile(string $file): array|false { $classes = []; $namespace = ''; - $tokens = PhpToken::tokenize((string)(new Stream($file, 'r'))); + try { + $stream = new Stream($file, 'r'); + $content = $stream->getContents(); + $stream->close(); + } catch (InvalidArgumentException $e) { + throw new RuntimeException( + r('Unable to read \'{file}\'. {error}', [ + 'file' => $file, + 'error' => $e->getMessage(), + ]) + ); + } + + $tokens = PhpToken::tokenize($content); $count = count($tokens); foreach ($tokens as $i => $iValue) { - if ('T_NAMESPACE' === $iValue->getTokenName()) { + if ($iValue->getTokenName() === 'T_NAMESPACE') { for ($j = $i + 1; $j < $count; $j++) { - if ('T_NAME_QUALIFIED' === $tokens[$j]->getTokenName()) { + if ($tokens[$j]->getTokenName() === 'T_NAME_QUALIFIED') { $namespace = $tokens[$j]->text; break; } } } - if ('T_CLASS' === $iValue->getTokenName()) { + if ($iValue->getTokenName() === 'T_CLASS') { for ($j = $i + 1; $j < $count; $j++) { - if ('T_WHITESPACE' === $tokens[$j]->getTokenName()) { + if ($tokens[$j]->getTokenName() === 'T_WHITESPACE') { continue; } - if ('T_STRING' === $tokens[$j]->getTokenName()) { + if ($tokens[$j]->getTokenName() === 'T_STRING') { $classes[] = $namespace . '\\' . $tokens[$j]->text; } else { break; diff --git a/src/Libs/helpers.php b/src/Libs/helpers.php index 8cd93435..98129c49 100644 --- a/src/Libs/helpers.php +++ b/src/Libs/helpers.php @@ -11,6 +11,7 @@ use App\Libs\Entity\StateInterface as iFace; use App\Libs\Exceptions\InvalidArgumentException; use App\Libs\Exceptions\RuntimeException; use App\Libs\Extends\Date; +use App\Libs\HTTP_STATUS; use App\Libs\Options; use App\Libs\Router; use App\Libs\Stream; @@ -378,28 +379,70 @@ if (!function_exists('saveRequestPayload')) { } } -if (!function_exists('jsonResponse')) { +if (!function_exists('api_response')) { /** - * Create a JSON response. + * Create a API response. * - * @param int $status The HTTP status code. * @param array $body The JSON data to include in the response body. + * @param HTTP_STATUS $status Optional. The HTTP status code. Default is {@see HTTP_STATUS::HTTP_OK}. * @param array $headers Optional. Additional headers to include in the response (default is an empty array). * * @return ResponseInterface A PSR-7 compatible response object. */ - function jsonResponse(int $status, array $body, array $headers = []): ResponseInterface - { - $headers['Content-Type'] = 'application/json'; - - return new Response( - status: $status, + function api_response( + array $body, + HTTP_STATUS $status = HTTP_STATUS::HTTP_OK, + array $headers = [] + ): ResponseInterface { + return (new Response( + status: $status->value, headers: $headers, body: json_encode( $body, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES ) + ))->withHeader('Content-Type', 'application/json'); + } +} + +if (!function_exists('api_error')) { + /** + * Create a API error response. + * + * @param string $message The error message. + * @param HTTP_STATUS $httpCode Optional. The HTTP status code. Default is {@see HTTP_STATUS::HTTP_BAD_REQUEST}. + * @param array $fields Optional. Additional fields to include in the response body. + * @param array $opts Optional. Additional options. + * + * @return ResponseInterface A PSR-7 compatible response object. + */ + function api_error( + string $message, + HTTP_STATUS $httpCode = HTTP_STATUS::HTTP_BAD_REQUEST, + array $fields = [], + array $opts = [] + ): ResponseInterface { + $response = api_response( + array_replace_recursive($fields, [ + 'error' => [ + 'code' => $httpCode->value, + 'message' => $message + ] + ]), + $httpCode, ); + + if (array_key_exists('callback', $opts) && ($opts['callback'] instanceof Closure)) { + $response = $opts['callback']($response); + } + + if (array_key_exists('headers', $opts)) { + foreach ($opts['headers'] as $key => $val) { + $response = $response->withHeader($key, $val); + } + } + + return $response; } } @@ -902,14 +945,13 @@ if (false === function_exists('generateRoutes')) { /** * Generate routes based on the available commands. * + * @param string $type The type of routes to return. defaults to is cli. + * * @return array The generated routes. */ - function generateRoutes(): array + function generateRoutes(string $type = 'cli'): array { - $dirs = [ - __DIR__ . '/../Commands', - ]; - + $dirs = [__DIR__ . '/../Commands']; foreach (array_keys(Config::get('supported', [])) as $backend) { $dir = r(__DIR__ . '/../Backends/{backend}/Commands', ['backend' => ucfirst($backend)]); @@ -920,18 +962,23 @@ if (false === function_exists('generateRoutes')) { $dirs[] = $dir; } - $routes = (new Router($dirs))->generate(); + $routes_cli = (new Router($dirs))->generate(); + + $cache = Container::get(CacheInterface::class); try { - Container::get(CacheInterface::class)->set( - 'routes', - $routes, - new DateInterval('PT1H') - ); + $cache->set('routes_cli', $routes_cli, new DateInterval('PT1H')); } catch (\Psr\SimpleCache\InvalidArgumentException) { } - return $routes; + $routes_http = (new Router([__DIR__ . '/../API']))->generate(); + + try { + $cache->set('routes_http', $routes_http, new DateInterval('P1D')); + } catch (\Psr\SimpleCache\InvalidArgumentException) { + } + + return 'http' === $type ? $routes_http : $routes_cli; } } diff --git a/tests/Libs/HelpersTest.php b/tests/Libs/HelpersTest.php index c00d8e3e..839addee 100644 --- a/tests/Libs/HelpersTest.php +++ b/tests/Libs/HelpersTest.php @@ -7,6 +7,7 @@ namespace Tests\Libs; use App\Libs\Config; use App\Libs\Entity\StateEntity; use App\Libs\Exceptions\RuntimeException; +use App\Libs\HTTP_STATUS; use App\Libs\TestCase; use JsonMachine\Items; use JsonMachine\JsonDecoder\ErrorWrappingDecoder; @@ -319,7 +320,7 @@ class HelpersTest extends TestCase public function test_jsonResponse(): void { $data = ['foo' => 'bar']; - $response = jsonResponse(200, $data); + $response = api_response($data, HTTP_STATUS::HTTP_OK); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('application/json', $response->getHeaderLine('Content-Type')); $this->assertSame($data, json_decode($response->getBody()->getContents(), true));