diff --git a/cli.php b/cli.php new file mode 100755 index 0000000..e6ea1f2 --- /dev/null +++ b/cli.php @@ -0,0 +1,11 @@ +#!/usr/local/bin/php +run()); diff --git a/composer.json b/composer.json index cce99a9..d768687 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "siteworxpro/config": "^1.1.1", "predis/predis": "^v3.2.0", "siteworxpro/http-status": "0.0.2", - "lcobucci/jwt": "^5.6" + "lcobucci/jwt": "^5.6", + "adhocore/cli": "^1.9" }, "require-dev": { "phpunit/phpunit": "^12.4", diff --git a/composer.lock b/composer.lock index 3f85ab1..450b663 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,81 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d9509da999bae9517bf79ee251ccdd32", + "content-hash": "f7dc2e6131715ed6eec2d9f851949b80", "packages": [ + { + "name": "adhocore/cli", + "version": "v1.9.4", + "source": { + "type": "git", + "url": "https://github.com/adhocore/php-cli.git", + "reference": "474dc3d7ab139796be98b104d891476e3916b6f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/adhocore/php-cli/zipball/474dc3d7ab139796be98b104d891476e3916b6f4", + "reference": "474dc3d7ab139796be98b104d891476e3916b6f4", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ahc\\Cli\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jitendra Adhikari", + "email": "jiten.adhikary@gmail.com" + } + ], + "description": "Command line interface library for PHP", + "keywords": [ + "argument-parser", + "argv-parser", + "cli", + "cli-action", + "cli-app", + "cli-color", + "cli-option", + "cli-writer", + "command", + "console", + "console-app", + "php-cli", + "php8", + "stream-input", + "stream-output" + ], + "support": { + "issues": "https://github.com/adhocore/php-cli/issues", + "source": "https://github.com/adhocore/php-cli/tree/v1.9.4" + }, + "funding": [ + { + "url": "https://paypal.me/ji10", + "type": "custom" + }, + { + "url": "https://github.com/adhocore", + "type": "github" + } + ], + "time": "2025-05-11T13:23:54+00:00" + }, { "name": "brick/math", "version": "0.14.0", diff --git a/docker-compose.yml b/docker-compose.yml index aaa5cfd..544b7c7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,8 @@ services: dockerfile: Dockerfile entrypoint: "/bin/sh -c 'while true; do sleep 30; done;'" depends_on: + traefik: + condition: service_healthy redis: condition: service_healthy postgres: diff --git a/server.php b/server.php index 816508a..dd031c6 100644 --- a/server.php +++ b/server.php @@ -1,12 +1,12 @@ startServer(); diff --git a/src/Server.php b/src/Api.php similarity index 60% rename from src/Server.php rename to src/Api.php index faa1a22..4393069 100644 --- a/src/Server.php +++ b/src/Api.php @@ -4,25 +4,18 @@ declare(strict_types=1); namespace Siteworxpro\App; -use Illuminate\Container\Container; -use Illuminate\Database\Capsule\Manager; -use Illuminate\Support\ServiceProvider; use League\Route\Http\Exception\MethodNotAllowedException; use League\Route\Http\Exception\NotFoundException; use League\Route\Router; use Nyholm\Psr7\Factory\Psr17Factory; -use Siteworx\Config\Config as SWConfig; use Siteworxpro\App\Controllers\HealthcheckController; use Siteworxpro\App\Controllers\IndexController; use Siteworxpro\App\Http\JsonResponseFactory; use Siteworxpro\App\Http\Middleware\CorsMiddleware; use Siteworxpro\App\Http\Middleware\JwtMiddleware; use Siteworxpro\App\Http\Middleware\ScopeMiddleware; -use Siteworxpro\App\Services\Facade; use Siteworxpro\App\Services\Facades\Config; use Siteworxpro\App\Services\Facades\Logger; -use Siteworxpro\App\Services\ServiceProviders\LoggerServiceProvider; -use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider; use Siteworxpro\HttpStatus\CodesEnum; use Spiral\RoadRunner\Http\PSR7Worker; use Spiral\RoadRunner\Worker; @@ -36,7 +29,7 @@ use Spiral\RoadRunner\Worker; * * @package Siteworxpro\App */ -class Server +class Api { /** * @var Router The router instance for handling routes. @@ -48,87 +41,13 @@ class Server */ protected PSR7Worker $worker; - public static array $serviceProviders = [ - LoggerServiceProvider::class, - RedisServiceProvider::class - ]; - - /** - * Server constructor. - * - * Initializes the server by booting the PSR-7 worker and router. * @throws \ReflectionException */ public function __construct() { - $this->boot(); - } - - /** - * Bootstraps the server by initializing the PSR-7 worker and router. - * - * This method sets up the PSR-7 worker and router instances, and registers - * the routes for the server. It should be called in the constructor of - * subclasses to ensure proper initialization. - * - * @return void - * @throws \ReflectionException - */ - private function boot(): void - { - $container = new Container(); - Facade::setFacadeContainer($container); - - // Bind the container to the Config facade first so that it can be used by service providers - $container->bind(SWConfig::class, function () { - return SWConfig::load(__DIR__ . '/../config.php'); - }); - - foreach (self::$serviceProviders as $serviceProvider) { - if (class_exists($serviceProvider)) { - $provider = new $serviceProvider($container); - if ($provider instanceof ServiceProvider) { - $provider->register(); - } else { - throw new \RuntimeException(sprintf( - 'Service provider %s is not an instance of ServiceProvider.', - $serviceProvider - )); - } - } else { - throw new \RuntimeException(sprintf('Service provider %s not found.', $serviceProvider)); - } - } - - $this->worker = new PSR7Worker( - Worker::create(), - new Psr17Factory(), - new Psr17Factory(), - new Psr17Factory() - ); - - $this->router = new Router(); - + Kernel::boot(); $this->registerRoutes(); - $this->bootModelCapsule(); - } - - /** - * Bootstraps the model capsule for database connections. - * - * This method sets up the database connection using the Eloquent ORM. - * It retrieves the database configuration from the Config facade and - * initializes the Eloquent capsule manager. - * - * @return void - */ - public function bootModelCapsule(): void - { - $capsule = new Manager(); - $capsule->addConnection(Config::get('db')); - $capsule->setAsGlobal(); - $capsule->bootEloquent(); } /** @@ -139,8 +58,16 @@ class Server * * @return void */ - protected function registerRoutes(): void + public function registerRoutes(): void { + $this->worker = new PSR7Worker( + Worker::create(), + new Psr17Factory(), + new Psr17Factory(), + new Psr17Factory() + ); + + $this->router = new Router(); $this->router->get('/', IndexController::class . '::get'); $this->router->get('/healthz', HealthcheckController::class . '::get'); @@ -197,7 +124,9 @@ class Server ]; } - $this->worker->respond(JsonResponseFactory::createJsonResponse($json, CodesEnum::INTERNAL_SERVER_ERROR)); + $this->worker->respond( + JsonResponseFactory::createJsonResponse($json, CodesEnum::INTERNAL_SERVER_ERROR) + ); } } } diff --git a/src/Cli/App.php b/src/Cli/App.php new file mode 100644 index 0000000..fc8a664 --- /dev/null +++ b/src/Cli/App.php @@ -0,0 +1,41 @@ +app = new Application('Php-Template', Config::get('app.version') ?? 'dev-master'); + + $this->app->add(new DemoCommand()); + } + + public function run(): int + { + $this->app->logo( + <<app->handle($_SERVER['argv']); + } +} diff --git a/src/Cli/Commands/CommandInterface.php b/src/Cli/Commands/CommandInterface.php new file mode 100644 index 0000000..990c3c2 --- /dev/null +++ b/src/Cli/Commands/CommandInterface.php @@ -0,0 +1,15 @@ +argument('[name]', 'Your name') + ->option('-g, --greet', 'Include a greeting message'); + } + + public function execute(): int + { + $pb = $this->progress(100); + + for ($i = 0; $i < 100; $i += 10) { + usleep(100000); // Simulate work + $pb->advance(10); + } + + $pb->finish(); + + $this->writer()->boldBlue("Demo Command Executed!\n"); + + if ($this->values()['name']) { + $name = $this->values()['name']; + $greet = $this->values()['greet'] ?? false; + } else { + return 0; + } + + if ($greet) { + $this->writer()->green("Hello, $name! Welcome to the CLI demo.\n"); + } else { + $this->writer()->yellow("Name provided: {$name}\n"); + } + + return 0; + } +} diff --git a/src/Http/Middleware/JwtMiddleware.php b/src/Http/Middleware/JwtMiddleware.php index 110377f..2f4690d 100644 --- a/src/Http/Middleware/JwtMiddleware.php +++ b/src/Http/Middleware/JwtMiddleware.php @@ -18,48 +18,35 @@ use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\StrictValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use League\Route\Dispatcher; -use League\Route\Route; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Siteworxpro\App\Annotations\Guards\Jwt; +use Siteworxpro\App\Controllers\Controller; use Siteworxpro\App\Http\JsonResponseFactory; use Siteworxpro\App\Services\Facades\Config; use Siteworxpro\HttpStatus\CodesEnum; -class JwtMiddleware implements MiddlewareInterface +class JwtMiddleware extends Middleware { /** * @throws \JsonException * @throws \Exception */ - public function process(ServerRequestInterface $request, RequestHandlerInterface|Dispatcher $handler): ResponseInterface - { + public function process( + ServerRequestInterface $request, + RequestHandlerInterface|Dispatcher $handler + ): ResponseInterface { - if (!$handler instanceof Dispatcher) { + $callable = $this->extractRouteCallable($request, $handler); + if ($callable === null) { return $handler->handle($request); } - /** @var Route | null $lastSegment */ - $lastSegment = array_last($handler->getMiddlewareStack()); + /** @var Controller $class */ + [$class, $method] = $callable; - if ($lastSegment === null) { - return $handler->handle($request); - } - - $callable = $lastSegment->getCallable(); - $class = null; - $method = null; - - if (is_array($callable) && count($callable) === 2) { - [$class, $method] = $callable; - } elseif (is_string($callable)) { - // Handle the case where the callable is a string (e.g., 'ClassName::methodName') - [$class, $method] = explode('::', $callable); - } - - if (class_exists($class)) { + if (class_exists($class::class)) { $reflectionClass = new \ReflectionClass($class); if ($reflectionClass->hasMethod($method)) { @@ -150,4 +137,4 @@ class JwtMiddleware implements MiddlewareInterface return new SignedWith(new Hmac256(), $key); } -} \ No newline at end of file +} diff --git a/src/Http/Middleware/Middleware.php b/src/Http/Middleware/Middleware.php new file mode 100644 index 0000000..bbb0696 --- /dev/null +++ b/src/Http/Middleware/Middleware.php @@ -0,0 +1,41 @@ +getMiddlewareStack()); + + if ($lastSegment === null) { + return null; + } + + $callable = $lastSegment->getCallable(); + $class = null; + $method = null; + + if (is_array($callable) && count($callable) === 2) { + [$class, $method] = $callable; + } elseif (is_string($callable)) { + // Handle the case where the callable is a string (e.g., 'ClassName::methodName') + [$class, $method] = explode('::', $callable); + } + + return [$class, $method]; + } +} diff --git a/src/Http/Middleware/ScopeMiddleware.php b/src/Http/Middleware/ScopeMiddleware.php index 0b30133..b4ee8e7 100644 --- a/src/Http/Middleware/ScopeMiddleware.php +++ b/src/Http/Middleware/ScopeMiddleware.php @@ -5,45 +5,32 @@ declare(strict_types=1); namespace Siteworxpro\App\Http\Middleware; use League\Route\Dispatcher; -use League\Route\Route; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Siteworxpro\App\Annotations\Guards\Scope; +use Siteworxpro\App\Controllers\Controller; use Siteworxpro\App\Http\JsonResponseFactory; use Siteworxpro\HttpStatus\CodesEnum; -class ScopeMiddleware implements MiddlewareInterface +class ScopeMiddleware extends Middleware { /** * @throws \JsonException */ - public function process(ServerRequestInterface $request, RequestHandlerInterface | Dispatcher $handler): ResponseInterface - { - if (!$handler instanceof Dispatcher) { + public function process( + ServerRequestInterface $request, + RequestHandlerInterface | Dispatcher $handler + ): ResponseInterface { + $callable = $this->extractRouteCallable($request, $handler); + if ($callable === null) { return $handler->handle($request); } - /** @var Route | null $lastSegment */ - $lastSegment = array_last($handler->getMiddlewareStack()); + /** @var Controller $class */ + [$class, $method] = $callable; - if ($lastSegment === null) { - return $handler->handle($request); - } - - $callable = $lastSegment->getCallable(); - $class = null; - $method = null; - - if (is_array($callable) && count($callable) === 2) { - [$class, $method] = $callable; - } elseif (is_string($callable)) { - // Handle the case where the callable is a string (e.g., 'ClassName::methodName') - [$class, $method] = explode('::', $callable); - } - - if (class_exists($class)) { + if (class_exists($class::class)) { $reflectionClass = new \ReflectionClass($class); if ($reflectionClass->hasMethod($method)) { $reflectionMethod = $reflectionClass->getMethod($method); @@ -56,10 +43,16 @@ class ScopeMiddleware implements MiddlewareInterface $userScopes = $request->getAttribute('scopes', []); - if (array_any($requiredScopes, fn($requiredScope) => !in_array($requiredScope, $userScopes, true))) { + if ( + array_any( + $requiredScopes, + fn($requiredScope) => !in_array($requiredScope, $userScopes, true) + ) + ) { return JsonResponseFactory::createJsonResponse([ 'error' => 'insufficient_scope', - 'error_description' => 'The request requires higher privileges than provided by the access token.' + 'error_description' => + 'The request requires higher privileges than provided by the access token.' ], CodesEnum::FORBIDDEN); } } @@ -68,4 +61,4 @@ class ScopeMiddleware implements MiddlewareInterface return $handler->handle($request); } -} \ No newline at end of file +} diff --git a/src/Kernel.php b/src/Kernel.php new file mode 100644 index 0000000..b104533 --- /dev/null +++ b/src/Kernel.php @@ -0,0 +1,76 @@ +bind(SWConfig::class, function () { + return SWConfig::load(__DIR__ . '/../config.php'); + }); + + foreach (self::$serviceProviders as $serviceProvider) { + if (class_exists($serviceProvider)) { + $provider = new $serviceProvider($container); + if ($provider instanceof ServiceProvider) { + $provider->register(); + } else { + throw new \RuntimeException(sprintf( + 'Service provider %s is not an instance of ServiceProvider.', + $serviceProvider + )); + } + } else { + throw new \RuntimeException(sprintf('Service provider %s not found.', $serviceProvider)); + } + } + + self::bootModelCapsule(); + } + + /** + * Bootstraps the model capsule for database connections. + * + * This method sets up the database connection using the Eloquent ORM. + * It retrieves the database configuration from the Config facade and + * initializes the Eloquent capsule manager. + * + * @return void + */ + private static function bootModelCapsule(): void + { + $capsule = new Manager(); + $capsule->addConnection(Config::get('db')); + $capsule->setAsGlobal(); + $capsule->bootEloquent(); + } +}