diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fa5f5077..10e21a63 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,7 +45,7 @@ jobs: - run: docker run -p 1080:1080 -p 1025:1025 -d --name maildev soulteary/maildev - run: docker run -p 6379:6379 -d --name redis redis - run: docker run -p 5432:5432 --name postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres -e POSTGRES_PASSWORD=postgres -d postgis/postgis - - run: docker run -d -p 11301:11300 schickling/beanstalkd + - run: docker run -d -p 11300:11300 schickling/beanstalkd - name: Cache Composer packages id: composer-cache diff --git a/src/Auth/Auth.php b/src/Auth/Auth.php index d8323df9..190e090a 100644 --- a/src/Auth/Auth.php +++ b/src/Auth/Auth.php @@ -8,6 +8,7 @@ use Bow\Auth\Guards\SessionGuard; use Bow\Auth\Guards\GuardContract; use Bow\Auth\Exception\AuthenticationException; +use ErrorException; class Auth { @@ -100,10 +101,16 @@ public static function guard(?string $guard = null): GuardContract * * @param string $method * @param array $params - * @return GuardContract + * @return ?GuardContract */ public static function __callStatic(string $method, array $params) { + if (is_null(static::$instance)) { + throw new ErrorException( + "Unable to get auth instance before configuration" + ); + } + if (method_exists(static::$instance, $method)) { return call_user_func_array([static::$instance, $method], $params); } diff --git a/src/Auth/Guards/JwtGuard.php b/src/Auth/Guards/JwtGuard.php index 6f962924..4edf0309 100644 --- a/src/Auth/Guards/JwtGuard.php +++ b/src/Auth/Guards/JwtGuard.php @@ -80,6 +80,16 @@ public function attempts(array $credentials): bool */ public function check(): bool { + $policier = $this->getPolicier(); + + if (is_null($this->token)) { + try { + $this->token = $policier->getParsedToken(); + } catch (\Exception $e) { + return false; + } + } + if (is_null($this->token)) { return false; } diff --git a/src/Cache/Cache.php b/src/Cache/Cache.php index 17b7a98c..9e51d06f 100644 --- a/src/Cache/Cache.php +++ b/src/Cache/Cache.php @@ -115,13 +115,22 @@ public static function addAdapters(array $adapters): void * @param array $arguments * @return mixed * @throws BadMethodCallException + * @throws ErrorException */ public static function __callStatic(string $name, array $arguments) { + if (is_null(static::$instance)) { + throw new ErrorException( + "Unable to get cache instance before configuration" + ); + } + if (method_exists(static::$instance, $name)) { return call_user_func_array([static::$instance, $name], $arguments); } - throw new BadMethodCallException("The $name method does not exist"); + throw new BadMethodCallException( + "The $name method does not exist" + ); } } diff --git a/src/Console/Command.php b/src/Console/Command.php index 8e631f96..586e44c2 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -44,6 +44,9 @@ class Command extends AbstractCommand "server" => \Bow\Console\Command\ServerCommand::class, "worker" => \Bow\Console\Command\WorkerCommand::class, ], + "flush" => [ + "worker" => \Bow\Console\Command\WorkerCommand::class, + ], ]; /** @@ -79,5 +82,7 @@ public function call(string $command, string $action, ...$rest): mixed if (method_exists($instance, $method)) { return call_user_func_array([$instance, $method], $rest); } + + return null; } } diff --git a/src/Console/Command/WorkerCommand.php b/src/Console/Command/WorkerCommand.php index 4d4b4c1c..a47491c6 100644 --- a/src/Console/Command/WorkerCommand.php +++ b/src/Console/Command/WorkerCommand.php @@ -16,8 +16,11 @@ class WorkerCommand extends AbstractCommand */ public function run(?string $connection = null): void { - $retry = (int) $this->arg->getParameter('--retry', 3); + $tries = (int) $this->arg->getParameter('--tries', 3); $default = $this->arg->getParameter('--queue', "default"); + $memory = $this->arg->getParameter('--memory', 126); + $timout = $this->arg->getParameter('--timout', 60); + $sleep = $this->arg->getParameter('--sleep', 60); $queue = app("queue"); @@ -25,8 +28,38 @@ public function run(?string $connection = null): void $queue->setConnection($connection); } - $worker = new WorkerService(); + $worker = $this->getWorderService(); $worker->setConnection($queue->getAdapter()); - $worker->run($default, $retry); + $worker->run($default, $tries, $sleep, $timout, $memory); + } + + /** + * Flush the queue + * + * @param ?string $connection + * @return void + */ + public function flush(?string $connection = null) + { + $connection_queue = $this->arg->getParameter('--queue'); + + $queue = app("queue"); + + if (!is_null($connection)) { + $queue->setConnection($connection); + } + + $adapter = $queue->getAdapter(); + $adapter->flush($connection_queue); + } + + /** + * Get the worker service + * + * @return WorkerService + */ + private function getWorderService() + { + return new WorkerService(); } } diff --git a/src/Console/Console.php b/src/Console/Console.php index 2d2901e0..43cea024 100644 --- a/src/Console/Console.php +++ b/src/Console/Console.php @@ -79,7 +79,7 @@ class Console * @var array */ private const COMMAND = [ - 'add', 'migration', 'migrate', 'run', 'generate', 'gen', 'seed', 'help', 'launch', 'clear' + 'add', 'migration', 'migrate', 'run', 'generate', 'gen', 'seed', 'help', 'launch', 'clear', 'flush' ]; /** @@ -144,7 +144,7 @@ public function bind(Loader $kernel): void /** * Launch Bow task runner * - * @return void + * @return mixed * @throws */ public function run(): mixed @@ -436,6 +436,23 @@ private function clear(): void $this->command->call('clear', "make", $action); } + /** + * Flush the connections + * + * @return void + * @throws \ErrorException + */ + private function flush(): void + { + $action = $this->arg->getAction(); + + if (!in_array($action, ['worker'])) { + $this->throwFailsCommand('This action is not exists', 'help flush'); + } + + $this->command->call('flush', $action); + } + /** * Display global help or helper command. * @@ -458,9 +475,9 @@ private function help(?string $command = null): int \033[0;32mGENERATE\033[00m create a new app key and resources \033[0;33mgenerate:resource\033[00m Create new REST controller - \033[0;33mgenerate:session\033[00m For generate session table - \033[0;33mgenerate:cache\033[00m For generate cache table + \033[0;33mgenerate:table\033[00m For generate the preset table for session, cache, queue \033[0;33mgenerate:key\033[00m Create new app key + \033[0;33mflush:worker\033[00m Flush all queues \033[0;32mADD\033[00m Create a user class \033[0;33madd:middleware\033[00m Create new middleware @@ -545,7 +562,7 @@ private function help(?string $command = null): int --model=[model_name] Define the usable model \033[0;33m$\033[00m php \033[0;34mbow\033[00m generate:resource name [option] For create a new REST controller - \033[0;33m$\033[00m php \033[0;34mbow\033[00m generate:session For generate session table + \033[0;33m$\033[00m php \033[0;34mbow\033[00m generate:table For generate the table for session, cache, queue \033[0;33m$\033[00m php \033[0;34mbow\033[00m generate:key For generate a new APP KEY \033[0;33m$\033[00m php \033[0;34mbow\033[00m generate help For display this @@ -597,13 +614,23 @@ private function help(?string $command = null): int \033[0;33m$\033[00m php \033[0;34mbow\033[00m seed:all\033[00m Make seeding for all \033[0;33m$\033[00m php \033[0;34mbow\033[00m seed:table\033[00m table_name Make seeding for one table +U; + break; + + case 'flush': + echo <<throwFailsCommand("Please make php bow help for show whole docs !"); exit(1); - break; } exit(0); diff --git a/src/Database/Barry/Model.php b/src/Database/Barry/Model.php index 9ce4c9ea..e26aa4cd 100644 --- a/src/Database/Barry/Model.php +++ b/src/Database/Barry/Model.php @@ -255,14 +255,23 @@ public static function findBy(string $column, mixed $value): Collection * @param mixed $id * @param array $select * - * @return Collection|static|null + * @return Collection|Model|null */ public static function findAndDelete( int | string | array $id, array $select = ['*'] - ): Model { + ): Collection|Model|null { $model = static::find($id, $select); + if (is_null($model)) { + return $model; + } + + if ($model instanceof Collection) { + $model->dropAll(); + return $model; + } + $model->delete(); return $model; @@ -876,7 +885,11 @@ public function __get(string $name): mixed $attribute_exists = isset($this->attributes[$name]); if (!$attribute_exists && method_exists($this, $name)) { - return $this->$name()->getResults(); + $result = $this->$name(); + if ($result instanceof Relation) { + return $result->getResults(); + } + return $result; } if (!$attribute_exists) { diff --git a/src/Database/Database.php b/src/Database/Database.php index 77cb0c16..1198b559 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -12,6 +12,7 @@ use Bow\Database\Connection\Adapter\MysqlAdapter; use Bow\Database\Connection\Adapter\SqliteAdapter; use Bow\Database\Connection\Adapter\PostgreSQLAdapter; +use ErrorException; class Database { @@ -76,9 +77,8 @@ public static function getInstance(): Database /** * Connection, starts the connection on the DB * - * @param null $name - * @return null|Database - * + * @param ?string $name + * @return ?Database * @throws ConnectionException */ public static function connection(?string $name = null): ?Database @@ -398,7 +398,7 @@ public static function transaction(callable $callback): mixed } catch (DatabaseException $e) { static::rollback(); - throw $e; + throw $e; } } @@ -418,9 +418,9 @@ private static function verifyConnection(): void * Retrieves the identifier of the last record. * * @param ?string $name - * @return int|string + * @return int|string|PDO */ - public static function lastInsertId(?string $name = null): int|string + public static function lastInsertId(?string $name = null): int|string|PDO { static::verifyConnection(); @@ -488,6 +488,12 @@ public static function setPdo(PDO $pdo) */ public function __call(string $method, array $arguments) { + if (is_null(static::$instance)) { + throw new ErrorException( + "Unable to get database instance before configuration" + ); + } + if (method_exists(static::$instance, $method)) { return call_user_func_array( [static::$instance, $method], diff --git a/src/Database/QueryBuilder.php b/src/Database/QueryBuilder.php index 97a55c09..458017a9 100644 --- a/src/Database/QueryBuilder.php +++ b/src/Database/QueryBuilder.php @@ -279,6 +279,25 @@ public function whereRaw(string $where): QueryBuilder return $this; } + /** + * Add orWhere clause into the request + * + * WHERE column1 $comparator $value|column + * + * @param string $where + * @return QueryBuilder + */ + public function orWhereRaw(string $where): QueryBuilder + { + if ($this->where == null) { + $this->where = $where; + } else { + $this->where .= ' or ' . $where; + } + + return $this; + } + /** * orWhere, add a condition of type: * diff --git a/src/Event/Event.php b/src/Event/Event.php index 99029333..32430cf5 100644 --- a/src/Event/Event.php +++ b/src/Event/Event.php @@ -4,10 +4,8 @@ namespace Bow\Event; -use Bow\Session\Session; -use Bow\Container\Action; -use Bow\Support\Collection; use Bow\Event\Contracts\AppEvent; +use ErrorException; class Event { @@ -143,6 +141,12 @@ public static function bound(string $event): bool */ public function __call(string $name, array $arguments) { + if (is_null(static::$instance)) { + throw new ErrorException( + "Unable to get event instance before configuration" + ); + } + if (method_exists(static::$instance, $name)) { return call_user_func_array([static::$instance, $name], $arguments); } diff --git a/src/Http/Request.php b/src/Http/Request.php index 7e8dd774..c148ef4f 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -5,6 +5,7 @@ namespace Bow\Http; use Bow\Auth\Authentication; +use Bow\Http\Exception\BadRequestException; use Bow\Session\Session; use Bow\Support\Collection; use Bow\Support\Str; @@ -52,7 +53,11 @@ private function __construct() $this->id = "req_" . sha1(uniqid() . time()); if ($this->getHeader('content-type') == 'application/json') { - $data = json_decode(file_get_contents("php://input"), true, 1024, JSON_THROW_ON_ERROR); + try { + $data = json_decode(file_get_contents("php://input"), true, 1024, JSON_THROW_ON_ERROR); + } catch (\Throwable $e) { + throw new BadRequestException("Invalid JSON syntax"); + } $this->input = array_merge((array) $data, $_GET); } else { $data = $_POST ?? []; diff --git a/src/Queue/Adapters/BeanstalkdAdapter.php b/src/Queue/Adapters/BeanstalkdAdapter.php index 31fdf17d..b8cf9cbc 100644 --- a/src/Queue/Adapters/BeanstalkdAdapter.php +++ b/src/Queue/Adapters/BeanstalkdAdapter.php @@ -7,7 +7,7 @@ use Pheanstalk\Pheanstalk; use Bow\Queue\ProducerService; use Bow\Queue\Adapters\QueueAdapter; -use Pheanstalk\Job as PheanstalkJob; +use Pheanstalk\Contract\PheanstalkInterface; use RuntimeException; class BeanstalkdAdapter extends QueueAdapter @@ -24,12 +24,21 @@ class BeanstalkdAdapter extends QueueAdapter * * @var string */ - private string $default = "default"; + private string $queue = "default"; /** + * The number of working attempts + * + * @var int + */ + private int $tries; + + /** + * Define the sleep time + * * @var int */ - private int $retry; + private int $sleep = 5; /** * Configure Beanstalkd driver @@ -43,7 +52,11 @@ public function configure(array $queue): BeanstalkdAdapter throw new RuntimeException("Please install the pda/pheanstalk package"); } - $this->pheanstalk = Pheanstalk::create($queue["hostname"], $queue["port"], $queue["timeout"]); + $this->pheanstalk = Pheanstalk::create( + $queue["hostname"], + $queue["port"], + $queue["timeout"] + ); return $this; } @@ -56,32 +69,29 @@ public function configure(array $queue): BeanstalkdAdapter */ public function setWatch(string $name): void { - $this->default = $name; + $this->queue = $name; } /** - * Get connexion + * Set job tries * - * @param int $retry - * @return Pheanstalk + * @param int $tries + * @return void */ - public function setRetry(int $retry): void + public function setTries(int $tries): void { - $this->retry = $retry; + $this->tries = $tries; } /** - * Delete a message from the Beanstalk queue. + * Get connexion * - * @param string $queue - * @param string|int $id + * @param int $sleep * @return void */ - public function deleteJob(string $queue, string|int $id): void + public function setSleep(int $sleep): void { - $queue = $this->getQueue($queue); - - $this->pheanstalk->useTube($queue)->delete(new PheanstalkJob($id, '')); + $this->sleep = $sleep; } /** @@ -92,7 +102,7 @@ public function deleteJob(string $queue, string|int $id): void */ public function getQueue(?string $queue = null): string { - return $queue ?: $this->default; + return $queue ?: $this->queue; } /** @@ -116,9 +126,23 @@ public function size(?string $queue = null): int */ public function push(ProducerService $producer): void { + // TODO: should be removed + // $this->flush(); + $queues = (array) cache("beanstalkd:queues"); + + if (!in_array($producer->getQueue(), $queues)) { + $queues[] = $producer->getQueue(); + cache("beanstalkd:queues", $queues); + } + $this->pheanstalk ->useTube($producer->getQueue()) - ->put(serialize($producer), $producer->getDelay(), $producer->getRetry()); + ->put( + $this->serializeProducer($producer), + $this->getPriority($producer->getPriority()), + $producer->getDelay(), + $producer->getRetry() + ); } /** @@ -129,22 +153,78 @@ public function push(ProducerService $producer): void */ public function run(string $queue = null): void { - // we want jobs from 'testtube' only. + // we want jobs from define queue only. $queue = $this->getQueue($queue); $this->pheanstalk->watch($queue); // This hangs until a Job is produced. $job = $this->pheanstalk->reserve(); + if (is_null($job)) { + sleep($this->sleep ?? 5); + return; + } + try { $payload = $job->getData(); - $producer = unserialize($payload); + $producer = $this->unserializeProducer($payload); call_user_func([$producer, "process"]); + $this->sleep(2); $this->pheanstalk->touch($job); - $this->deleteJob($queue, $job->getId()); - } catch (\Exception $e) { - cache($job->getId(), $job->getData()); - $this->pheanstalk->release($job); + $this->sleep(2); + $this->pheanstalk->delete($job); + } catch (\Throwable $e) { + error_log($e->getMessage()); + app('logger')->error($e->getMessage(), $e->getTrace()); + cache("failed:job:" . $job->getId(), $job->getData()); + if ($producer->jobShouldBeDelete()) { + $this->pheanstalk->delete($job); + } else { + $this->pheanstalk->release($job, $this->getPriority($producer->getPriority()), $producer->getDelay()); + } + $this->sleep(1); + } + } + + /** + * Flush the queue + * + * @return void + */ + public function flush(?string $queue = null): void + { + $queues = (array) $queue; + + if (count($queues) == 0) { + $queues = cache("beanstalkd:queues"); + } + + foreach ($queues as $queue) { + $this->pheanstalk->useTube($queue); + + while ($job = $this->pheanstalk->reserve()) { + $this->pheanstalk->delete($job); + } + } + } + + /** + * Get the priority + * + * @param int $priority + * @return int + */ + public function getPriority(int $priority): int + { + switch ($priority) { + case $priority > 2: + return 4294967295; + case 1: + return PheanstalkInterface::DEFAULT_PRIORITY; + case 0: + return 0; + default: + return PheanstalkInterface::DEFAULT_PRIORITY; } } } diff --git a/src/Queue/Adapters/QueueAdapter.php b/src/Queue/Adapters/QueueAdapter.php index e2f4e341..5e203c78 100644 --- a/src/Queue/Adapters/QueueAdapter.php +++ b/src/Queue/Adapters/QueueAdapter.php @@ -8,14 +8,40 @@ abstract class QueueAdapter { + const EXIT_SUCCESS = 0; + const EXIT_ERROR = 1; + const EXIT_MEMORY_LIMIT = 12; + + /** + * Define the start time + * + * @var int + */ + private int $start_time; + + /** + * Determine if the worker should quit + * + * @var bool + */ + protected bool $should_quit = false; + + /** + * Determine if the worker is paused + * + * @var bool + */ + protected bool $paused = false; + /** * Create producer serialization * * @param ProducerService $producer * @return string */ - public function serializeProducer(ProducerService $producer): string - { + public function serializeProducer( + ProducerService $producer + ): string { return serialize($producer); } @@ -25,11 +51,115 @@ public function serializeProducer(ProducerService $producer): string * @param string $producer * @return ProducerService */ - public function unserializeProducer(string $producer): ProducerService - { + public function unserializeProducer( + string $producer + ): ProducerService { return unserialize($producer); } + /** + * Sleep the process + * + * @param int $seconds + * @return void + */ + public function sleep(int $seconds): void + { + if ($seconds < 1) { + usleep($seconds * 1000000); + } else { + sleep($seconds); + } + } + + /** + * Laund the worker + * + * @param integer $timeout + * @param integer $memory + * @return void + */ + public function work(int $timeout, int $memory): void + { + [$this->start_time, $jobs_processed] = [hrtime(true) / 1e9, 0]; + + if ($supportsAsyncSignals = $this->supportsAsyncSignals()) { + $this->listenForSignals(); + } + + while (true) { + $this->run(); + + if ($this->timeoutReached($timeout)) { + $this->kill(static::EXIT_ERROR); + } elseif ($this->memoryExceeded($memory)) { + $this->kill(static::EXIT_MEMORY_LIMIT); + } + } + } + + /** + * Kill the process. + * + * @param int $status + * @return never + */ + public function kill($status = 0) + { + if (extension_loaded('posix')) { + posix_kill(getmypid(), SIGKILL); + } + + exit($status); + } + + /** + * Determine if the timeout is reached + * + * @param int $timeout + * @return boolean + */ + protected function timeoutReached(int $timeout): bool + { + return (time() - $this->start_time) >= $timeout; + } + + /** + * Determine if the memory is exceeded + * + * @param int $memory_timit + * @return boolean + */ + private function memoryExceeded(int $memory_timit): bool + { + return (memory_get_usage() / 1024 / 1024) >= $memory_timit; + } + + /** + * Enable async signals for the process. + * + * @return void + */ + protected function listenForSignals() + { + pcntl_async_signals(true); + + pcntl_signal(SIGQUIT, fn () => error_log("bow worker exiting...")); + pcntl_signal(SIGTERM, fn () => error_log("bow worker exit...")); + pcntl_signal(SIGUSR2, fn () => error_log("bow worker restarting...")); + pcntl_signal(SIGCONT, fn () => error_log("bow worker continue...")); + } + + /** + * Determine if "async" signals are supported. + * + * @return bool + */ + protected function supportsAsyncSignals() + { + return extension_loaded('pcntl'); + } + /** * Make adapter configuration * @@ -46,11 +176,18 @@ abstract public function configure(array $config): QueueAdapter; abstract public function setWatch(string $queue): void; /** - * Set the retry value + * Set the tries value * - * @param int $retry + * @param int $tries */ - abstract public function setRetry(int $retry): void; + abstract public function setTries(int $tries): void; + + /** + * Set the sleep value + * + * @param int $sleep + */ + abstract public function setSleep(int $sleep): void; /** * Push new producer @@ -67,22 +204,13 @@ abstract public function push(ProducerService $producer): void; */ abstract public function size(string $queue): int; - /** - * Delete a message from the queue. - * - * @param string $queue - * @param string|int $id - * @return void - */ - abstract public function deleteJob(string $queue, string|int $id): void; - /** * Get the queue or return the default. * * @param ?string $queue * @return string */ - abstract public function getQueue(string $queue = null): string; + abstract public function getQueue(?string $queue = null): string; /** * Start the worker server @@ -90,4 +218,12 @@ abstract public function getQueue(string $queue = null): string; * @param ?string $queue */ abstract public function run(?string $queue = null): void; + + /** + * Flush the queue + * + * @param ?string $queue + * @return void + */ + abstract public function flush(?string $queue = null): void; } diff --git a/src/Queue/Connection.php b/src/Queue/Connection.php index d7255d76..979d4410 100644 --- a/src/Queue/Connection.php +++ b/src/Queue/Connection.php @@ -6,6 +6,7 @@ use Bow\Queue\Adapters\QueueAdapter; use Bow\Queue\Adapters\BeanstalkdAdapter; +use Bow\Queue\Adapters\SQSAdapter; use ErrorException; class Connection @@ -31,6 +32,7 @@ class Connection */ private static array $connections = [ "beanstalkd" => BeanstalkdAdapter::class, + "sqs" => SQSAdapter::class, ]; /** @@ -67,11 +69,13 @@ public static function pushConnection(string $name, string $classname): bool * Set connection * * @param string $connection - * @return void + * @return Connection */ - public function setConnection(string $connection): void + public function setConnection(string $connection): Connection { $this->connection = $connection; + + return $this; } /** @@ -84,6 +88,7 @@ public function getAdapter(): QueueAdapter $driver = $this->connection ?: $this->config["default"]; $connection = $this->config["connections"][$driver]; + $queue = new static::$connections[$driver](); return $queue->configure($connection); diff --git a/src/Queue/ProducerService.php b/src/Queue/ProducerService.php index f60ece1c..18ff747d 100644 --- a/src/Queue/ProducerService.php +++ b/src/Queue/ProducerService.php @@ -4,25 +4,25 @@ namespace Bow\Queue; -use Bow\Queue\Traits\SerializesModels; +use Bow\Support\Serializes; abstract class ProducerService { - use SerializesModels; + use Serializes; /** - * Define the delay + * Define the queue * - * @var int + * @var string */ - protected int $delay = 30; + protected string $queue = "default"; /** - * Define the queue + * Define the delay * - * @var string + * @var int */ - protected string $queue = "default"; + protected int $delay = 30; /** * Define the time of retry @@ -38,6 +38,30 @@ abstract class ProducerService */ protected int $priority = 1; + /** + * Determine if the job can be deleted + * + * @var bool + */ + protected bool $delete = false; + + /** + * Define the job id + * + * @return integer + */ + protected string $id; + + /** + * ProducerService constructor + * + * @return mixed + */ + public function __construct() + { + $this->id = sha1(uniqid(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), true)); + } + /** * Get the producer priority * @@ -48,6 +72,16 @@ final public function getPriority(): int return $this->priority; } + /** + * Get the producer id + * + * @return string + */ + public function getId(): string + { + return $this->id; + } + /** * Get the producer retry * @@ -78,6 +112,26 @@ final public function getDelay(): int return $this->delay; } + /** + * Delete the job from queue. + * + * @return void + */ + public function deleteJob(): void + { + $this->delete = true; + } + + /** + * Delete the job from queue. + * + * @return bool + */ + public function jobShouldBeDelete(): bool + { + return $this->delete; + } + /** * Process the producer * diff --git a/src/Queue/WorkerService.php b/src/Queue/WorkerService.php index 6d6f7429..47d070b7 100644 --- a/src/Queue/WorkerService.php +++ b/src/Queue/WorkerService.php @@ -30,16 +30,22 @@ public function setConnection(QueueAdapter $connection): void * Start the consumer * * @param string $queue - * @param integer $retry + * @param int $tries + * @param int $sleep + * @param int $timeout + * @param int $memory * @return void */ - public function run(string $queue = "default", int $retry = 60): void - { + public function run( + string $queue = "default", + int $tries = 3, + int $sleep = 5, + int $timeout = 60, + int $memory = 128 + ): void { $this->connection->setWatch($queue); - $this->connection->setRetry($retry); - - while (true) { - $this->connection->run(); - } + $this->connection->setTries($tries); + $this->connection->setSleep($sleep); + $this->connection->work($timeout, $memory); } } diff --git a/src/Storage/Exception/ServiceNotFoundException.php b/src/Storage/Exception/ServiceNotFoundException.php index dfedb2f0..f8ecc7b9 100644 --- a/src/Storage/Exception/ServiceNotFoundException.php +++ b/src/Storage/Exception/ServiceNotFoundException.php @@ -19,18 +19,19 @@ class ServiceNotFoundException extends ErrorException * Set the service name * * @param string $service_name - * - * @return void + * @return ServiceNotFoundException */ - public function setService($service_name) + public function setService(string $service_name): ServiceNotFoundException { $this->service_name = $service_name; + + return $this; } /** * Get the service name * - * @return void + * @return string */ public function getService() { diff --git a/src/Storage/Storage.php b/src/Storage/Storage.php index cd3923b4..631eb283 100644 --- a/src/Storage/Storage.php +++ b/src/Storage/Storage.php @@ -5,6 +5,7 @@ namespace Bow\Storage; use BadMethodCallException; +use Bow\Storage\Contracts\FilesystemInterface; use InvalidArgumentException; use Bow\Storage\Exception\DiskNotFoundException; use Bow\Storage\Exception\ServiceConfigurationNotFoundException; @@ -12,6 +13,7 @@ use Bow\Storage\Service\DiskFilesystemService; use Bow\Storage\Service\FTPService; use Bow\Storage\Service\S3Service; +use ErrorException; class Storage { @@ -47,7 +49,7 @@ class Storage * @return DiskFilesystemService * @throws DiskNotFoundException */ - public static function disk(?string $disk = null) + public static function disk(?string $disk = null): DiskFilesystemService { // Use the default disk as fallback if (is_null($disk)) { @@ -72,6 +74,8 @@ public static function disk(?string $disk = null) * * @param string $service * @return FTPService|S3Service + * @throws ServiceConfigurationNotFoundException + * @throws ServiceNotFoundException */ public static function service(string $service) { @@ -90,14 +94,14 @@ public static function service(string $service) throw (new ServiceNotFoundException(sprintf( '"%s" driver is not support.', $driver - )))->setService($driver); + )))->setService($service); } if (!array_key_exists($driver, self::$available_services_driviers)) { throw (new ServiceNotFoundException(sprintf( '"%s" is not registered as a service.', $driver - )))->setService($driver); + )))->setService($service); } $service_class = static::$available_services_driviers[$driver]; @@ -126,10 +130,10 @@ public static function pushService(array $drivers) * Configure Storage * * @param array $config - * @return MountFilesystem + * @return FilesystemInterface * @throws */ - public static function configure(array $config) + public static function configure(array $config): FilesystemInterface { static::$config = $config; @@ -149,6 +153,12 @@ public static function configure(array $config) */ public function __call($name, array $arguments) { + if (is_null(static::$disk)) { + throw new ErrorException( + "Unable to get storage instance before configuration" + ); + } + if (method_exists(static::$disk, $name)) { return call_user_func_array([static::$disk, $name], $arguments); } @@ -165,6 +175,12 @@ public function __call($name, array $arguments) */ public static function __callStatic($name, array $arguments) { + if (is_null(static::$disk)) { + throw new ErrorException( + "Unable to get storage instance before configuration" + ); + } + if (method_exists(static::$disk, $name)) { return call_user_func_array([static::$disk, $name], $arguments); } diff --git a/src/Queue/Traits/SerializesModels.php b/src/Support/Serializes.php similarity index 90% rename from src/Queue/Traits/SerializesModels.php rename to src/Support/Serializes.php index 5d895c31..b9e5506a 100644 --- a/src/Queue/Traits/SerializesModels.php +++ b/src/Support/Serializes.php @@ -1,11 +1,11 @@ setAccessible(true); - - if (! $property->isInitialized($this)) { + if (!$property->isInitialized($this)) { continue; } $value = $this->getPropertyValue($property); - if ($property->hasDefaultValue() && $value === $property->getDefaultValue()) { continue; } @@ -76,7 +74,7 @@ public function __unserialize(array $values) $name = "\0*\0{$name}"; } - if (! array_key_exists($name, $values)) { + if (!array_key_exists($name, $values)) { continue; } @@ -95,8 +93,9 @@ public function __unserialize(array $values) * @param \ReflectionProperty $property * @return mixed */ - protected function getPropertyValue(ReflectionProperty $property) - { + protected function getPropertyValue( + ReflectionProperty $property + ) { $property->setAccessible(true); return $property->getValue($this); diff --git a/src/Support/helpers.php b/src/Support/helpers.php index c0632b6d..5d32a4eb 100644 --- a/src/Support/helpers.php +++ b/src/Support/helpers.php @@ -335,7 +335,14 @@ function create_csrf_token(int $time = null): ?array */ function csrf_token(): string { - $csrf = create_csrf_token(); + $csrf = (array) create_csrf_token(); + + if (count($csrf) == 0) { + throw new HttpException( + "CSRF token is not generated", + 500 + ); + } return $csrf['token']; } @@ -349,7 +356,14 @@ function csrf_token(): string */ function csrf_field(): string { - $csrf = create_csrf_token(); + $csrf = (array) create_csrf_token(); + + if (count($csrf) == 0) { + throw new HttpException( + "CSRF token is not generated", + 500 + ); + } return $csrf['field']; } @@ -928,7 +942,7 @@ function storage_service(string $service) */ function mount(string $mount): \Bow\Storage\Service\DiskFilesystemService { - return Storage::mount($mount); + return Storage::disk($mount); } } @@ -936,13 +950,13 @@ function mount(string $mount): \Bow\Storage\Service\DiskFilesystemService /** * Alias on the mount method * - * @param string $mount + * @param string $disk * @return \Bow\Storage\Service\DiskFilesystemService * @throws \Bow\Storage\Exception\ResourceException */ - function file_system(string $mount): \Bow\Storage\Service\DiskFilesystemService + function file_system(string $disk): \Bow\Storage\Service\DiskFilesystemService { - return mount($mount); + return Storage::disk($disk); } } @@ -950,13 +964,17 @@ function file_system(string $mount): \Bow\Storage\Service\DiskFilesystemService /** * Cache help * - * @param string $key - * @param mixed $value - * @param int $ttl + * @param ?string $key + * @param ?mixed $value + * @param ?int $ttl * @return mixed */ function cache(string $key = null, mixed $value = null, int $ttl = null) { + if ($key === null) { + return \Bow\Cache\Cache::getInstance(); + } + if ($key !== null && $value === null) { return \Bow\Cache\Cache::get($key); } @@ -970,9 +988,9 @@ function cache(string $key = null, mixed $value = null, int $ttl = null) * Make redirection to back * * @param int $status - * @return Bow\Http\Redirect + * @return Redirect */ - function redirect_back(int $status = 302): \Bow\Http\Redirect + function redirect_back(int $status = 302): Redirect { return redirect()->back($status); } @@ -1200,16 +1218,13 @@ function auth(string $guard = null): \Bow\Auth\Guards\GuardContract } } -if (!function_exists('log')) { +if (!function_exists('logger')) { /** * Log error message * - * @param string $level - * @param string $message - * @param array $context * @return Logger */ - function log(): Logger + function logger(): Logger { return app('logger'); } diff --git a/src/Testing/KernelTesting.php b/src/Testing/KernelTesting.php index 3ac570e8..9badc418 100644 --- a/src/Testing/KernelTesting.php +++ b/src/Testing/KernelTesting.php @@ -16,7 +16,7 @@ class KernelTesting extends ConfigurationLoader * @param array $configurations * @return void */ - public static function withConfiguations(array $configurations): void + public static function withConfigurations(array $configurations): void { static::$configurations = $configurations; } diff --git a/tests/Auth/AuthenticationTest.php b/tests/Auth/AuthenticationTest.php index f0e3cb5d..9a957317 100644 --- a/tests/Auth/AuthenticationTest.php +++ b/tests/Auth/AuthenticationTest.php @@ -138,6 +138,7 @@ public function test_attempt_login_with_jwt_provider_fail() public function test_attempt_login_with_session_provider() { $this->expectException(AuthenticationException::class); + $auth = Auth::guard('web'); $auth->attempts([ "username" => "papac", diff --git a/tests/Config/TestingConfiguration.php b/tests/Config/TestingConfiguration.php index 63009e33..d542fb42 100644 --- a/tests/Config/TestingConfiguration.php +++ b/tests/Config/TestingConfiguration.php @@ -15,6 +15,39 @@ public function __construct() is_dir(TESTING_RESOURCE_BASE_DIRECTORY) || mkdir(TESTING_RESOURCE_BASE_DIRECTORY, 0777); } + /** + * Configure the testing + * + * @param array $configurations + * @return void + */ + public static function withConfigurations(array $configurations) + { + KernelTesting::withConfigurations($configurations); + } + + /** + * Configure the testing + * + * @param array $middlewares + * @return void + */ + public static function withMiddlewares(array $middlewares) + { + KernelTesting::withMiddlewares($middlewares); + } + + /** + * Set the loading events + * + * @param array $events + * @return void + */ + public static function withEvents(array $events): void + { + KernelTesting::withEvents($events); + } + /** * Get the configuration for testing * diff --git a/tests/Config/stubs/queue.php b/tests/Config/stubs/queue.php index 241290e1..88af732b 100644 --- a/tests/Config/stubs/queue.php +++ b/tests/Config/stubs/queue.php @@ -21,8 +21,8 @@ * The beanstalkd connexion */ "beanstalkd" => [ - "hostname" => "127.0.0.0", - "port" => 11301, + "hostname" => "127.0.0.1", + "port" => 11300, "timeout" => 10, ], @@ -30,9 +30,14 @@ * The sqs connexion */ "sqs" => [ - "hostname" => "127.0.0.0", - "port" => 11300, - "timeout" => 10, + 'url' => app_env('SQS_URL', 'https://sqs.ap-south-1.amazonaws.com/242848748621/messaging'), + ], + + /** + * The sqs connexion + */ + "database" => [ + 'table' => "queue_jobs", ] ] ]; diff --git a/tests/Queue/QueueTest.php b/tests/Queue/QueueTest.php index 53e16e64..16a0a4ff 100644 --- a/tests/Queue/QueueTest.php +++ b/tests/Queue/QueueTest.php @@ -2,11 +2,18 @@ namespace Bow\Tests\Queue; +use Bow\Cache\Adapter\RedisAdapter; +use Bow\Cache\Cache; +use Bow\Cache\CacheConfiguration; +use Bow\Configuration\LoggerConfiguration; use Bow\Database\Database; +use Bow\Database\DatabaseConfiguration; use Bow\Queue\Adapters\BeanstalkdAdapter; +use Bow\Queue\Adapters\SQSAdapter; use Bow\Tests\Config\TestingConfiguration; use Bow\Tests\Queue\Stubs\PetModelStub; use Bow\Queue\Connection as QueueConnection; +use Bow\Testing\KernelTesting; use Bow\Tests\Queue\Stubs\ModelProducerStub; use Bow\Tests\Queue\Stubs\BasicProducerStubs; @@ -16,8 +23,15 @@ class QueueTest extends \PHPUnit\Framework\TestCase public static function setUpBeforeClass(): void { + TestingConfiguration::withConfigurations([ + LoggerConfiguration::class, + DatabaseConfiguration::class, + CacheConfiguration::class, + ]); + $config = TestingConfiguration::getConfig(); - @unlink(TESTING_RESOURCE_BASE_DIRECTORY . '/producer.txt'); + $config->boot(); + static::$connection = new QueueConnection($config["queue"]); Database::connection('mysql'); @@ -25,34 +39,85 @@ public static function setUpBeforeClass(): void Database::statement('create table pets (id int primary key auto_increment, name varchar(255))'); } - public function test_instance_of_adapter() + /** + * @dataProvider getConnection + * + * @param string $connection + * @return void + */ + public function test_instance_of_adapter($connection) { - $this->assertInstanceOf(BeanstalkdAdapter::class, static::$connection->getAdapter()); + $adapter = static::$connection->setConnection($connection)->getAdapter(); + + if ($connection == "beanstalkd") { + $this->assertInstanceOf(BeanstalkdAdapter::class, $adapter); + } elseif ($connection == "sqs") { + $this->assertInstanceOf(SQSAdapter::class, $adapter); + } elseif ($connection == "redis") { + $this->assertInstanceOf(RedisAdapter::class, $adapter); + } elseif ($connection == "database") { + $this->assertInstanceOf(DatabaseAdapter::class, $adapter); + } } - public function test_push_service_adapter() + /** + * @dataProvider getConnection + * + * @param string $connection + * @return void + */ + public function test_push_service_adapter($connection) { - $adapter = static::$connection->getAdapter(); - $adapter->push(new BasicProducerStubs("running")); + $adapter = static::$connection->setConnection($connection)->getAdapter(); + $filename = TESTING_RESOURCE_BASE_DIRECTORY . "/{$connection}_producer.txt"; + + $adapter->push(new BasicProducerStubs($connection)); $adapter->run(); - $this->assertTrue(file_exists(TESTING_RESOURCE_BASE_DIRECTORY . '/producer.txt')); - $this->assertEquals(file_get_contents(TESTING_RESOURCE_BASE_DIRECTORY . '/producer.txt'), 'running'); + $this->assertTrue(file_exists($filename)); + $this->assertEquals(file_get_contents($filename), BasicProducerStubs::class); + + @unlink($filename); } - public function test_push_service_adapter_with_model() + /** + * @dataProvider getConnection + * @param string $connection + * @return void + */ + public function test_push_service_adapter_with_model($connection) { - $adapter = static::$connection->getAdapter(); + $adapter = static::$connection->setConnection($connection)->getAdapter(); $pet = new PetModelStub(["name" => "Filou"]); - $producer = new ModelProducerStub($pet); + $producer = new ModelProducerStub($pet, $connection); $adapter->push($producer); $adapter->run(); - $this->assertTrue(file_exists(TESTING_RESOURCE_BASE_DIRECTORY . '/producer.txt')); - $this->assertEquals(file_get_contents(TESTING_RESOURCE_BASE_DIRECTORY . '/producer.txt'), 'running'); + $this->assertTrue(file_exists(TESTING_RESOURCE_BASE_DIRECTORY . "/{$connection}_queue_pet_model_stub.txt")); + $content = file_get_contents(TESTING_RESOURCE_BASE_DIRECTORY . "/{$connection}_queue_pet_model_stub.txt"); + $data = json_decode($content); + $this->assertEquals($data->name, "Filou"); $pet = PetModelStub::first(); $this->assertNotNull($pet); + + @unlink(TESTING_RESOURCE_BASE_DIRECTORY . "/{$connection}_producer.txt"); + } + + /** + * Get the connection data + * + * @return array + */ + public function getConnection(): array + { + return [ + ["beanstalkd"], + // ["sqs"], + // ["redis"], + // ["rabbitmq"], + // ["database"] + ]; } } diff --git a/tests/Queue/Stubs/BasicProducerStubs.php b/tests/Queue/Stubs/BasicProducerStubs.php index 911bf889..1f720027 100644 --- a/tests/Queue/Stubs/BasicProducerStubs.php +++ b/tests/Queue/Stubs/BasicProducerStubs.php @@ -6,15 +6,13 @@ class BasicProducerStubs extends ProducerService { - private ?string $name = null; - - public function __construct(string $name) - { - $this->name = $name; + public function __construct( + private string $connection + ) { } public function process(): void { - file_put_contents(TESTING_RESOURCE_BASE_DIRECTORY . '/producer.txt', $this->name); + file_put_contents(TESTING_RESOURCE_BASE_DIRECTORY . "/{$this->connection}_producer.txt", BasicProducerStubs::class); } } diff --git a/tests/Queue/Stubs/MixedProducerStub.php b/tests/Queue/Stubs/MixedProducerStub.php index eb8d85f8..ed17d1da 100644 --- a/tests/Queue/Stubs/MixedProducerStub.php +++ b/tests/Queue/Stubs/MixedProducerStub.php @@ -7,15 +7,14 @@ class MixedProducerStub extends ProducerService { - private ServiceStub $service; - - public function __construct(ServiceStub $service) - { - $this->service = $service; + public function __construct( + private ServiceStub $service, + private string $connection + ) { } public function process(): void { - $this->service->fire(); + $this->service->fire($this->connection); } } diff --git a/tests/Queue/Stubs/ModelProducerStub.php b/tests/Queue/Stubs/ModelProducerStub.php index 245fb29d..50ebe323 100644 --- a/tests/Queue/Stubs/ModelProducerStub.php +++ b/tests/Queue/Stubs/ModelProducerStub.php @@ -7,16 +7,20 @@ class ModelProducerStub extends ProducerService { - private PetModelStub $pet; - - public function __construct(PetModelStub $pet) - { + public function __construct( + private PetModelStub $pet, + private string $connection + ) { $this->pet = $pet; + $this->connection = $connection; } public function process(): void { $this->pet->save(); - file_put_contents(TESTING_RESOURCE_BASE_DIRECTORY . '/queue_pet_model_stub.txt', $this->pet->toJson()); + + file_put_contents(TESTING_RESOURCE_BASE_DIRECTORY . "/{$this->connection}_queue_pet_model_stub.txt", $this->pet->toJson()); + + $this->deleteJob(); } } diff --git a/tests/Queue/Stubs/ServiceStub.php b/tests/Queue/Stubs/ServiceStub.php index 173a6b89..e3979647 100644 --- a/tests/Queue/Stubs/ServiceStub.php +++ b/tests/Queue/Stubs/ServiceStub.php @@ -4,8 +4,14 @@ class ServiceStub { - public function fire(): void + /** + * The fire method + * + * @param string $connection + * @return void + */ + public function fire(string $connection): void { - file_put_contents(TESTING_RESOURCE_BASE_DIRECTORY . '/producer_service.txt', ServiceStub::class); + file_put_contents(TESTING_RESOURCE_BASE_DIRECTORY . "/{$connection}_producer_service.txt", ServiceStub::class); } } diff --git a/tests/Support/HttpClientTest.php b/tests/Support/HttpClientTest.php index 13a0d3f4..b5220b81 100644 --- a/tests/Support/HttpClientTest.php +++ b/tests/Support/HttpClientTest.php @@ -11,7 +11,7 @@ public function test_get_method() { $http = new HttpClient(); - $response = $http->get("https://google.com"); + $response = $http->get("https://www.oogle.com"); $this->assertEquals($response->statusCode(), 200); } @@ -21,14 +21,14 @@ public function test_get_method_with_custom_headers() $http = new HttpClient(); $http->addHeaders(["X-Api-Key" => "Fake-Key"]); - $response = $http->get("https://google.com"); + $response = $http->get("https://www.google.com"); $this->assertEquals($response->statusCode(), 200); } public function test_should_be_fail_with_get_method() { - $http = new HttpClient("https://google.com"); + $http = new HttpClient("https://www.google.com"); $http->addHeaders(["X-Api-Key" => "Fake-Key"]); $response = $http->get("/the-fake-url");