diff --git a/FAQ.md b/FAQ.md
index 8e893e6e..193871c0 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -275,22 +275,21 @@ $ docker exec -ti watchstate console system:env
These environment variables relates to the tool itself, you can load them via the recommended methods.
-| Key | Type | Description | Default |
-|--------------------------|---------|-------------------------------------------------------------------------|--------------------|
-| WS_DATA_PATH | string | Where to store main data. (config, db). | `${BASE_PATH}/var` |
-| WS_TMP_DIR | string | Where to store temp data. (logs, cache) | `${WS_DATA_PATH}` |
-| WS_TZ | string | Set timezone. | `UTC` |
-| WS_CRON_{TASK} | bool | Enable {task} task. Value casted to bool. | `false` |
-| WS_CRON_{TASK}_AT | string | When to run {task} task. Valid Cron Expression Expected. | `*/1 * * * *` |
-| WS_CRON_{TASK}_ARGS | string | Flags to pass to the {task} command. | `-v` |
-| WS_LOGS_CONTEXT | bool | Add context to console output messages. | `false` |
-| WS_LOGGER_FILE_ENABLE | bool | Save logs to file. | `true` |
-| WS_LOGGER_FILE_LEVEL | string | File Logger Level. | `ERROR` |
-| WS_WEBHOOK_DUMP_REQUEST | bool | If enabled, will dump all received requests. | `false` |
-| WS_EPISODES_DISABLE_GUID | bool | Disable external id parsing for episodes and rely on relative ids. | `true` |
-| WS_TRUST_PROXY | bool | Trust `WS_TRUST_HEADER` ip. Value casted to bool. | `false` |
-| WS_TRUST_HEADER | string | Which header contain user true IP. | `X-Forwarded-For` |
-| WS_LIBRARY_SEGMENT | integer | Paginate backend library items request. Per request get total X number. | `1000` |
+| Key | Type | Description | Default |
+|-------------------------|---------|-------------------------------------------------------------------------|--------------------|
+| WS_DATA_PATH | string | Where to store main data. (config, db). | `${BASE_PATH}/var` |
+| WS_TMP_DIR | string | Where to store temp data. (logs, cache) | `${WS_DATA_PATH}` |
+| WS_TZ | string | Set timezone. | `UTC` |
+| WS_CRON_{TASK} | bool | Enable {task} task. Value casted to bool. | `false` |
+| WS_CRON_{TASK}_AT | string | When to run {task} task. Valid Cron Expression Expected. | `*/1 * * * *` |
+| WS_CRON_{TASK}_ARGS | string | Flags to pass to the {task} command. | `-v` |
+| WS_LOGS_CONTEXT | bool | Add context to console output messages. | `false` |
+| WS_LOGGER_FILE_ENABLE | bool | Save logs to file. | `true` |
+| WS_LOGGER_FILE_LEVEL | string | File Logger Level. | `ERROR` |
+| WS_WEBHOOK_DUMP_REQUEST | bool | If enabled, will dump all received requests. | `false` |
+| WS_TRUST_PROXY | bool | Trust `WS_TRUST_HEADER` ip. Value casted to bool. | `false` |
+| WS_TRUST_HEADER | string | Which header contain user true IP. | `X-Forwarded-For` |
+| WS_LIBRARY_SEGMENT | integer | Paginate backend library items request. Per request get total X number. | `1000` |
> [!IMPORTANT]
> for environment variables that has `{TASK}` tag, you **MUST** replace it with one
diff --git a/src/Commands/State/ExportCommand.php b/src/Commands/State/ExportCommand.php
index 7a2f3893..5f146bf2 100644
--- a/src/Commands/State/ExportCommand.php
+++ b/src/Commands/State/ExportCommand.php
@@ -17,13 +17,19 @@ use App\Libs\Routable;
use App\Libs\Stream;
use Monolog\Logger;
use Psr\Log\LoggerInterface as iLogger;
-use RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
use Throwable;
+/**
+ * Class ExportCommand
+ *
+ * Command for exporting play state to backends.
+ *
+ * @package App\Console\Commands\State
+ */
#[Routable(command: self::ROUTE)]
class ExportCommand extends Command
{
@@ -31,6 +37,14 @@ class ExportCommand extends Command
public const TASK_NAME = 'export';
+ /**
+ * Class Constructor.
+ *
+ * @param iDB $db The instance of the iDB class.
+ * @param DirectMapper $mapper The instance of the DirectMapper class.
+ * @param QueueRequests $queue The instance of the QueueRequests class.
+ * @param iLogger $logger The instance of the iLogger class.
+ */
public function __construct(
private iDB $db,
private DirectMapper $mapper,
@@ -43,6 +57,9 @@ class ExportCommand extends Command
parent::__construct();
}
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -100,15 +117,30 @@ class ExportCommand extends Command
);
}
+ /**
+ * Make sure the command is not running in parallel.
+ *
+ * @param InputInterface $input The input object containing the command data.
+ * @param OutputInterface $output The output object for displaying command output.
+ *
+ * @return int The exit code of the command execution.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
return $this->single(fn(): int => $this->process($input, $output), $output);
}
+ /**
+ * Process the command by pulling and comparing status and then pushing.
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int
+ */
protected function process(InputInterface $input, OutputInterface $output): int
{
if (null !== ($logfile = $input->getOption('logfile')) && true === ($this->logger instanceof Logger)) {
- $this->logger->pushHandler(new StreamLogHandler(new Stream(fopen($logfile, 'a')), $output));
+ $this->logger->pushHandler(new StreamLogHandler(new Stream($logfile, 'a'), $output));
}
// -- Use Custom servers.yaml file.
@@ -116,7 +148,7 @@ class ExportCommand extends Command
try {
$custom = true;
Config::save('servers', Yaml::parseFile($this->checkCustomBackendsFile($config)));
- } catch (RuntimeException $e) {
+ } catch (\App\Libs\Exceptions\RuntimeException $e) {
$output->writeln(sprintf('%s', $e->getMessage()));
return self::FAILURE;
}
@@ -448,13 +480,22 @@ class ExportCommand extends Command
copy($config, $config . '.bak');
}
- file_put_contents($config, Yaml::dump(Config::get('servers', []), 8, 2));
+ $stream = new Stream($config, 'w');
+ $stream->write(Yaml::dump(Config::get('servers', []), 8, 2));
+ $stream->close();
}
-
return self::SUCCESS;
}
+ /**
+ * Push entities to backends if applicable.
+ *
+ * @param array $backends An array of backends.
+ * @param array $entities An array of entities to be pushed.
+ *
+ * @return int The success status code.
+ */
protected function push(array $backends, array $entities): int
{
$this->logger->notice('Push mode start.', [
@@ -477,10 +518,10 @@ class ExportCommand extends Command
}
/**
- * Pull and compare status and then push.
+ * Fallback to export mode if push mode is not supported for the backend.
*
- * @param array $backends
- * @param InputInterface $input
+ * @param array $backends An array of backends to export data to.
+ * @param InputInterface $input The input containing export options.
*/
protected function export(array $backends, InputInterface $input): void
{
diff --git a/src/Commands/State/ImportCommand.php b/src/Commands/State/ImportCommand.php
index e1d1eb2f..d7dd1d7c 100644
--- a/src/Commands/State/ImportCommand.php
+++ b/src/Commands/State/ImportCommand.php
@@ -20,7 +20,6 @@ use App\Libs\Routable;
use App\Libs\Stream;
use Monolog\Logger;
use Psr\Log\LoggerInterface as iLogger;
-use RuntimeException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
@@ -30,6 +29,11 @@ use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Throwable;
+/**
+ * Class ImportCommand
+ *
+ * This command imports metadata and play state of items from backends and updates the local database.
+ */
#[Routable(command: self::ROUTE)]
class ImportCommand extends Command
{
@@ -37,6 +41,13 @@ class ImportCommand extends Command
public const TASK_NAME = 'import';
+ /**
+ * Class Constructor.
+ *
+ * @param iDB $db The database interface object.
+ * @param iImport $mapper The import interface object.
+ * @param iLogger $logger The logger interface object.
+ */
public function __construct(private iDB $db, private iImport $mapper, private iLogger $logger)
{
set_time_limit(0);
@@ -45,6 +56,9 @@ class ImportCommand extends Command
parent::__construct();
}
+ /**
+ * Configure the method.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -201,15 +215,31 @@ class ImportCommand extends Command
);
}
+ /**
+ * Make sure the command is not running in parallel.
+ *
+ * @param InputInterface $input The input interface object.
+ * @param OutputInterface $output The output interface object.
+ *
+ * @return int The status code of the command execution.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
return $this->single(fn(): int => $this->process($input, $output), $output);
}
+ /**
+ * Import the state from the backends.
+ *
+ * @param InputInterface $input The input interface object.
+ * @param OutputInterface $output The output interface object.
+ *
+ * @return int The return status code.
+ */
protected function process(InputInterface $input, OutputInterface $output): int
{
if (null !== ($logfile = $input->getOption('logfile')) && true === ($this->logger instanceof Logger)) {
- $this->logger->pushHandler(new StreamLogHandler(new Stream(fopen($logfile, 'a')), $output));
+ $this->logger->pushHandler(new StreamLogHandler(new Stream($logfile, 'a'), $output));
}
// -- Use Custom servers.yaml file.
@@ -217,7 +247,7 @@ class ImportCommand extends Command
try {
$custom = true;
Config::save('servers', Yaml::parseFile($this->checkCustomBackendsFile($config)));
- } catch (RuntimeException $e) {
+ } catch (\App\Libs\Exceptions\RuntimeException $e) {
$output->writeln(sprintf('%s', $e->getMessage()));
return self::FAILURE;
}
@@ -482,7 +512,9 @@ class ImportCommand extends Command
copy($config, $config . '.bak');
}
- file_put_contents($config, Yaml::dump(Config::get('servers', []), 8, 2));
+ $stream = new Stream($config, 'w');
+ $stream->write(Yaml::dump(Config::get('servers', []), 8, 2));
+ $stream->close();
}
if ($input->getOption('show-messages')) {
diff --git a/src/Commands/State/ProgressCommand.php b/src/Commands/State/ProgressCommand.php
index bebf4903..e536873d 100644
--- a/src/Commands/State/ProgressCommand.php
+++ b/src/Commands/State/ProgressCommand.php
@@ -13,11 +13,21 @@ use App\Libs\QueueRequests;
use App\Libs\Routable;
use Psr\Log\LoggerInterface as iLogger;
use Psr\SimpleCache\CacheInterface as iCache;
-use Psr\SimpleCache\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+use Throwable;
+/**
+ * Class ProgressCommand
+ *
+ * This command is used to push user watch progress to export enabled backends.
+ * It should not be run manually and should be scheduled to run as a task.
+ *
+ * This command requires the watch progress metadata to be already saved in the database.
+ * If no metadata is available for a backend,
+ * the watch progress update won't be sent to that backend
+ */
#[Routable(command: self::ROUTE)]
class ProgressCommand extends Command
{
@@ -25,6 +35,14 @@ class ProgressCommand extends Command
public const TASK_NAME = 'progress';
+ /**
+ * Class Constructor.
+ *
+ * @param iLogger $logger The logger instance.
+ * @param iCache $cache The cache instance.
+ * @param iDB $db The database instance.
+ * @param QueueRequests $queue The queue requests instance.
+ */
public function __construct(
private iLogger $logger,
private iCache $cache,
@@ -37,6 +55,9 @@ class ProgressCommand extends Command
parent::__construct();
}
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -69,10 +90,12 @@ class ProgressCommand extends Command
}
/**
+ * Make sure the command is not running in parallel.
+ *
* @param InputInterface $input
* @param OutputInterface $output
* @return int
- * @throws InvalidArgumentException
+ * @throws \Psr\Cache\InvalidArgumentException if the cache key is not a legal value
*/
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
@@ -80,7 +103,12 @@ class ProgressCommand extends Command
}
/**
- * @throws InvalidArgumentException
+ * Run the command.
+ *
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ * @return int Returns the status code.
+ * @throws \Psr\Cache\InvalidArgumentException if the cache key is not a legal value
*/
protected function process(InputInterface $input, OutputInterface $output): int
{
@@ -219,7 +247,7 @@ class ProgressCommand extends Command
...$context,
'status_code' => $response->getStatusCode(),
]);
- } catch (\Throwable $e) {
+ } catch (Throwable $e) {
$this->logger->error(
message: 'SYSTEM: Exception [{error.kind}] was thrown unhandled during [{backend}] request to change watch progress of {item.type} [{item.title}]. Error [{error.message} @ {error.file}:{error.line}].',
context: [
@@ -266,12 +294,13 @@ class ProgressCommand extends Command
}
/**
- * List Items.
+ * Renders and displays a list of items based on the specified output mode.
*
- * @param InputInterface $input
- * @param OutputInterface $output
- * @param array $items
- * @return int
+ * @param InputInterface $input The input interface object.
+ * @param OutputInterface $output The output interface object.
+ * @param array $items An array of items to be listed.
+ *
+ * @return int The status code indicating the success of the method execution.
*/
private function listItems(InputInterface $input, OutputInterface $output, array $items): int
{
diff --git a/src/Commands/State/PushCommand.php b/src/Commands/State/PushCommand.php
index 25a78200..5a315eff 100644
--- a/src/Commands/State/PushCommand.php
+++ b/src/Commands/State/PushCommand.php
@@ -14,11 +14,17 @@ use App\Libs\QueueRequests;
use App\Libs\Routable;
use Psr\Log\LoggerInterface as iLogger;
use Psr\SimpleCache\CacheInterface as iCache;
-use Psr\SimpleCache\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+use Throwable;
+/**
+ * Class PushCommand
+ *
+ * This class represents a command that pushes webhook queued events.
+ * It sends change play state requests to the supported backends.
+ */
#[Routable(command: self::ROUTE)]
class PushCommand extends Command
{
@@ -26,6 +32,16 @@ class PushCommand extends Command
public const TASK_NAME = 'push';
+ /**
+ * Constructor for the given class.
+ *
+ * @param iLogger $logger The logger instance.
+ * @param iCache $cache The cache instance.
+ * @param iDB $db The database instance.
+ * @param QueueRequests $queue The queue instance.
+ *
+ * @return void
+ */
public function __construct(
private iLogger $logger,
private iCache $cache,
@@ -38,6 +54,9 @@ class PushCommand extends Command
parent::__construct();
}
+ /**
+ * Configure command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -68,10 +87,13 @@ class PushCommand extends Command
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
- * @return int
- * @throws InvalidArgumentException
+ * Make sure the command is not running in parallel.
+ *
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ *
+ * @return int Returns the process result status code.
+ * @throws \Psr\SimpleCache\InvalidArgumentException if the cache key is not a legal value.
*/
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
@@ -79,7 +101,12 @@ class PushCommand extends Command
}
/**
- * @throws InvalidArgumentException
+ * Process the queue items and send change play state requests to the supported backends.
+ *
+ * @param InputInterface $input The input interface.
+ *
+ * @return int Returns the process result status code.
+ * @throws \Psr\SimpleCache\InvalidArgumentException if the cache key is not a legal value.
*/
protected function process(InputInterface $input): int
{
@@ -197,7 +224,7 @@ class PushCommand extends Command
}
$this->logger->notice('SYSTEM: Marked [{backend}] [{item.title}] as [{play_state}].', $context);
- } catch (\Throwable $e) {
+ } catch (Throwable $e) {
$this->logger->error(
message: 'SYSTEM: Exception [{error.kind}] was thrown unhandled during [{backend}] request to change play state of {item.type} [{item.title}]. Error [{error.message} @ {error.file}:{error.line}].',
context: [
diff --git a/src/Commands/State/RequestsCommand.php b/src/Commands/State/RequestsCommand.php
index fcb40443..b88e746a 100644
--- a/src/Commands/State/RequestsCommand.php
+++ b/src/Commands/State/RequestsCommand.php
@@ -12,13 +12,19 @@ use App\Libs\Options;
use App\Libs\Routable;
use Psr\Log\LoggerInterface as iLogger;
use Psr\SimpleCache\CacheInterface as iCache;
-use Psr\SimpleCache\InvalidArgumentException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class RequestsCommand
+ *
+ * This class is responsible for processing queued HTTP requests.
+ *
+ * @package Your\Namespace
+ */
#[Routable(command: self::ROUTE)]
class RequestsCommand extends Command
{
@@ -26,6 +32,13 @@ class RequestsCommand extends Command
public const TASK_NAME = 'requests';
+ /**
+ * Class constructor.
+ *
+ * @param iLogger $logger The logger object.
+ * @param iCache $cache The cache object.
+ * @param DirectMapper $mapper The DirectMapper object.
+ */
public function __construct(private iLogger $logger, private iCache $cache, private DirectMapper $mapper)
{
set_time_limit(0);
@@ -34,6 +47,9 @@ class RequestsCommand extends Command
parent::__construct();
}
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -46,10 +62,13 @@ class RequestsCommand extends Command
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
- * @return int
- * @throws InvalidArgumentException
+ * Make sure the command is not running in parallel.
+ *
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ *
+ * @return int The exit code of the command.
+ * @throws \Psr\Cache\InvalidArgumentException if the $key string is not a legal value
*/
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
@@ -57,7 +76,13 @@ class RequestsCommand extends Command
}
/**
- * @throws InvalidArgumentException
+ * Run the command.
+ *
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ *
+ * @return int The exit code of the command.
+ * @throws \Psr\Cache\InvalidArgumentException if the $key string is not a legal value
*/
protected function process(InputInterface $input, OutputInterface $output): int
{
@@ -155,12 +180,13 @@ class RequestsCommand extends Command
}
/**
- * List Items.
+ * Lists items based on the provided input and output.
*
- * @param InputInterface $input
- * @param OutputInterface $output
- * @param array $requests
- * @return int
+ * @param InputInterface $input The input interface object.
+ * @param OutputInterface $output The output interface object.
+ * @param array $requests The array of requests.
+ *
+ * @return int Returns the success status code.
*/
private function listItems(InputInterface $input, OutputInterface $output, array $requests): int
{
diff --git a/src/Commands/System/EnvCommand.php b/src/Commands/System/EnvCommand.php
index 3fae953b..6b688bc4 100644
--- a/src/Commands/System/EnvCommand.php
+++ b/src/Commands/System/EnvCommand.php
@@ -10,11 +10,19 @@ use App\Libs\Routable;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class EnvCommand
+ *
+ * This command displays the environment variables that were loaded during the execution of the tool.
+ */
#[Routable(command: self::ROUTE)]
final class EnvCommand extends Command
{
public const ROUTE = 'system:env';
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -78,13 +86,21 @@ final class EnvCommand extends Command
);
}
+ /**
+ * Run the command.
+ *
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ *
+ * @return int The exit code.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
$mode = $input->getOption('output');
$keys = [];
foreach (getenv() as $key => $val) {
- if (false === str_starts_with($key, 'WS_')) {
+ if (false === str_starts_with($key, 'WS_') && $key !== 'HTTP_PORT') {
continue;
}
diff --git a/src/Commands/System/IndexCommand.php b/src/Commands/System/IndexCommand.php
index 1588e0d6..db965d56 100644
--- a/src/Commands/System/IndexCommand.php
+++ b/src/Commands/System/IndexCommand.php
@@ -12,6 +12,11 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class IndexCommand
+ *
+ * This command ensures that the database has correct indexes.
+ */
#[Routable(command: self::ROUTE)]
final class IndexCommand extends Command
{
@@ -19,11 +24,21 @@ final class IndexCommand extends Command
public const TASK_NAME = 'indexes';
+ /**
+ * Class constructor.
+ *
+ * @param iDB $db An instance of the iDB class.
+ */
public function __construct(private iDB $db)
{
parent::__construct();
}
+ /**
+ * Configure the command.
+ *
+ * @return void
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -56,6 +71,14 @@ final class IndexCommand extends Command
);
}
+ /**
+ * Run a command.
+ *
+ * @param InputInterface $input An instance of the InputInterface interface.
+ * @param OutputInterface $output An instance of the OutputInterface interface.
+ *
+ * @return int The status code indicating the success or failure of the command execution.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
$this->db->ensureIndex([
diff --git a/src/Commands/System/LogsCommand.php b/src/Commands/System/LogsCommand.php
index 2a986421..367ee466 100644
--- a/src/Commands/System/LogsCommand.php
+++ b/src/Commands/System/LogsCommand.php
@@ -6,8 +6,8 @@ namespace App\Commands\System;
use App\Command;
use App\Libs\Config;
+use App\Libs\Exceptions\InvalidArgumentException;
use App\Libs\Routable;
-use Exception;
use LimitIterator;
use SplFileObject;
use Symfony\Component\Console\Completion\CompletionInput;
@@ -17,19 +17,33 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
+/**
+ * Class LogsCommand.
+ *
+ * This class is used to view and clear log files.
+ */
#[Routable(command: self::ROUTE)]
final class LogsCommand extends Command
{
public const ROUTE = 'system:logs';
+ /**
+ * @var array Constant array containing names of supported log files.
+ */
private const LOG_FILES = [
'app',
'access',
'task'
];
+ /**
+ * @var int The default limit of how many lines to show.
+ */
public const DEFAULT_LIMIT = 50;
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$defaultDate = makeDate()->format('Ymd');
@@ -121,7 +135,15 @@ final class LogsCommand extends Command
}
/**
- * @throws Exception
+ * Run the command.
+ *
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ *
+ * @return int The exit code of the command.
+ *
+ * @throws InvalidArgumentException If the log type is not one of the supported log files.
+ * @throws InvalidArgumentException If the log date is not in the correct format.
*/
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
@@ -132,7 +154,7 @@ final class LogsCommand extends Command
$type = $input->getOption('type');
if (false === in_array($type, self::LOG_FILES)) {
- throw new \RuntimeException(
+ throw new InvalidArgumentException(
sprintf('Log type has to be one of the supported log files [%s].', implode(', ', self::LOG_FILES))
);
}
@@ -140,12 +162,15 @@ final class LogsCommand extends Command
$date = $input->getOption('date');
if (1 !== preg_match('/^\d{8}$/', $date)) {
- throw new \RuntimeException('Log date must be in [YYYYMMDD] format. For example [20220622].');
+ throw new InvalidArgumentException('Log date must be in [YYYYMMDD] format. For example [20220622].');
}
$limit = (int)$input->getOption('limit');
- $file = sprintf(Config::get('tmpDir') . '/logs/%s.%s.log', $type, $date);
+ $file = r(text: Config::get('tmpDir') . '/logs/{type}.{date}.log', context: [
+ 'type' => $type,
+ 'date' => $date
+ ]);
if (false === file_exists($file) || filesize($file) < 1) {
$output->writeln(
@@ -226,6 +251,14 @@ final class LogsCommand extends Command
return self::SUCCESS;
}
+ /**
+ * Lists the logs.
+ *
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ *
+ * @return int The exit code.
+ */
private function listLogs(InputInterface $input, OutputInterface $output): int
{
$path = fixPath(Config::get('tmpDir') . '/logs');
@@ -259,6 +292,15 @@ final class LogsCommand extends Command
return self::SUCCESS;
}
+ /**
+ * Clears the contents of a log file.
+ *
+ * @param SplFileObject $file The log file object.
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ *
+ * @return int The exit code.
+ */
private function handleClearLog(SplFileObject $file, InputInterface $input, OutputInterface $output): int
{
$logfile = after($file->getRealPath(), Config::get('tmpDir') . '/');
@@ -310,6 +352,12 @@ final class LogsCommand extends Command
return self::SUCCESS;
}
+ /**
+ * Complete the suggestions for the given input.
+ *
+ * @param CompletionInput $input The completion input.
+ * @param CompletionSuggestions $suggestions The completion suggestions.
+ */
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
parent::complete($input, $suggestions);
@@ -329,6 +377,11 @@ final class LogsCommand extends Command
}
}
+ /**
+ * Retrieve the types of log files.
+ *
+ * @return array The array of available log file types.
+ */
public static function getTypes(): array
{
return self::LOG_FILES;
diff --git a/src/Commands/System/MaintenanceCommand.php b/src/Commands/System/MaintenanceCommand.php
index 66d8346b..54ab21ac 100644
--- a/src/Commands/System/MaintenanceCommand.php
+++ b/src/Commands/System/MaintenanceCommand.php
@@ -10,16 +10,29 @@ use App\Libs\Routable;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class MaintenanceCommand
+ *
+ * Runs maintenance tasks on the database.
+ */
#[Routable(command: self::ROUTE)]
final class MaintenanceCommand extends Command
{
public const ROUTE = 'system:db:maintenance';
+ /**
+ * Class constructor.
+ *
+ * @param iDB $db The database connection object.
+ */
public function __construct(private iDB $db)
{
parent::__construct();
}
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -35,6 +48,14 @@ final class MaintenanceCommand extends Command
);
}
+ /**
+ * Runs the command.
+ *
+ * @param InputInterface $input The input interface object.
+ * @param OutputInterface $output The output interface object.
+ *
+ * @return int Returns the exit code.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
$this->db->maintenance();
diff --git a/src/Commands/System/MakeCommand.php b/src/Commands/System/MakeCommand.php
index cec88d9d..295000c5 100644
--- a/src/Commands/System/MakeCommand.php
+++ b/src/Commands/System/MakeCommand.php
@@ -11,16 +11,29 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class MakeCommand
+ *
+ * This class represents a command to create a database schema migration file.
+ */
#[Routable(command: self::ROUTE)]
final class MakeCommand extends Command
{
public const ROUTE = 'system:db:make';
+ /**
+ * Class Constructor.
+ *
+ * @param iDB $db The iDB object used for database operations.
+ */
public function __construct(private iDB $db)
{
parent::__construct();
}
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -46,16 +59,21 @@ final class MakeCommand extends Command
);
}
+ /**
+ * Executes a command.
+ *
+ * @param InputInterface $input The input object containing command arguments and options.
+ * @param OutputInterface $output The output object used for displaying messages.
+ *
+ * @return int The exit code of the command execution. Returns "SUCCESS" constant value.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
$file = $this->db->makeMigration($input->getArgument('filename'));
- $output->writeln(
- sprintf(
- 'Created new migration at \'%s\'.',
- after(realpath($file), ROOT_PATH),
- )
- );
+ $output->writeln(r(text: "Created new migration file at '{file}'.", context: [
+ 'file' => after(realpath($file), ROOT_PATH),
+ ]));
return self::SUCCESS;
}
diff --git a/src/Commands/System/MigrationsCommand.php b/src/Commands/System/MigrationsCommand.php
index 1da18fc4..b5b12b77 100644
--- a/src/Commands/System/MigrationsCommand.php
+++ b/src/Commands/System/MigrationsCommand.php
@@ -11,16 +11,30 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class MigrationsCommand
+ *
+ * Database migrations runner.
+ */
#[Routable(command: self::ROUTE)]
final class MigrationsCommand extends Command
{
public const ROUTE = 'system:db:migrations';
+ /**
+ * Class Constructor.
+ *
+ * @param iDB $db The database connection object.
+ *
+ */
public function __construct(private iDB $db)
{
parent::__construct();
}
+ /**
+ * Configures the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -37,7 +51,27 @@ final class MigrationsCommand extends Command
);
}
+ /**
+ * Make sure the command is not running in parallel.
+ *
+ * @param InputInterface $input The input object containing the command data.
+ * @param OutputInterface $output The output object for displaying command output.
+ *
+ * @return int The exit code of the command execution.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
+ {
+ return $this->single(fn(): int => $this->process($input), $output);
+ }
+
+ /**
+ * Run the command to migrate the database.
+ *
+ * @param InputInterface $input The input object representing the command inputs.
+ *
+ * @return int The exit code of the command execution.
+ */
+ protected function process(InputInterface $input): int
{
$opts = [];
diff --git a/src/Commands/System/PHPCommand.php b/src/Commands/System/PHPCommand.php
index 99765e9a..401eadaf 100644
--- a/src/Commands/System/PHPCommand.php
+++ b/src/Commands/System/PHPCommand.php
@@ -11,11 +11,22 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class PHPCommand
+ *
+ * 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)]
final class PHPCommand extends Command
{
public const ROUTE = 'system:php';
+ /**
+ * Configures the command.
+ *
+ * @return void
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -40,11 +51,25 @@ final class PHPCommand extends Command
);
}
+ /**
+ * Runs the command based on the input options.
+ *
+ * @param InputInterface $input The input options.
+ * @param OutputInterface $output The output interface for displaying messages.
+ * @return int The exit code of the command execution.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
return $input->getOption('fpm') ? $this->makeFPM($output) : $this->makeConfig($output);
}
+ /**
+ * Print the php.ini configuration.
+ *
+ * @param OutputInterface $output The OutputInterface object to write the configuration to.
+ *
+ * @return int The status code indicating the success of the method.
+ */
protected function makeConfig(OutputInterface $output): int
{
$config = Config::get('php.ini', []);
@@ -56,6 +81,13 @@ final class PHPCommand extends Command
return self::SUCCESS;
}
+ /**
+ * Print the PHP-FPM configuration.
+ *
+ * @param OutputInterface $output The OutputInterface object to write the configuration to.
+ *
+ * @return int The status code indicating the success of the method.
+ */
protected function makeFPM(OutputInterface $output): int
{
$config = Config::get('php.fpm', []);
@@ -70,6 +102,13 @@ final class PHPCommand extends Command
return self::SUCCESS;
}
+ /**
+ * Escape the given value.
+ *
+ * @param mixed $val The value to escape.
+ *
+ * @return mixed The escaped value.
+ */
private function escapeValue(mixed $val): mixed
{
if (is_bool($val) || is_int($val)) {
diff --git a/src/Commands/System/PruneCommand.php b/src/Commands/System/PruneCommand.php
index f73b5498..2843b7ae 100644
--- a/src/Commands/System/PruneCommand.php
+++ b/src/Commands/System/PruneCommand.php
@@ -13,6 +13,12 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class PruneCommand
+ *
+ * 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)]
final class PruneCommand extends Command
{
@@ -20,11 +26,19 @@ final class PruneCommand extends Command
public const TASK_NAME = 'prune';
+ /**
+ * Class Constructor.
+ *
+ * @param LoggerInterface $logger The logger implementation used for logging.
+ */
public function __construct(private LoggerInterface $logger)
{
parent::__construct();
}
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -49,6 +63,14 @@ final class PruneCommand extends Command
);
}
+ /**
+ * Executes the command.
+ *
+ * @param InputInterface $input The input interface.
+ * @param OutputInterface $output The output interface.
+ *
+ * @return int The exit status code.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
$time = time();
@@ -139,5 +161,4 @@ final class PruneCommand extends Command
return self::SUCCESS;
}
-
}
diff --git a/src/Commands/System/ReportCommand.php b/src/Commands/System/ReportCommand.php
index 3b25366c..dfd381fd 100644
--- a/src/Commands/System/ReportCommand.php
+++ b/src/Commands/System/ReportCommand.php
@@ -11,8 +11,8 @@ 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 Exception;
use LimitIterator;
use SplFileObject;
use Symfony\Component\Console\Input\InputInterface as iInput;
@@ -20,16 +20,31 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface as iOutput;
use Throwable;
+/**
+ * Class ReportCommand
+ *
+ * Show basic information for diagnostics.
+ */
#[Routable(command: self::ROUTE)]
final class ReportCommand extends Command
{
public const ROUTE = 'system:report';
+ /**
+ * Class Constructor.
+ *
+ * @param iDB $db An instance of the iDB class used for database operations.
+ *
+ * @return void
+ */
public function __construct(private iDB $db)
{
parent::__construct();
}
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -54,6 +69,14 @@ final class ReportCommand extends Command
);
}
+ /**
+ * Display basic information for diagnostics.
+ *
+ * @param iInput $input An instance of the iInput class used for command input.
+ * @param iOutput $output An instance of the iOutput class used for command output.
+ *
+ * @return int Returns the command execution status code.
+ */
protected function runCommand(iInput $input, iOutput $output): int
{
$output->writeln('[ Basic Report ]' . PHP_EOL);
@@ -82,7 +105,7 @@ final class ReportCommand extends Command
}
try {
- $pid = trim(file_get_contents($pidFile));
+ $pid = trim((string)(new Stream($pidFile)));
} catch (Throwable $e) {
return $e->getMessage();
}
@@ -111,6 +134,14 @@ final class ReportCommand extends Command
return self::SUCCESS;
}
+ /**
+ * Get backends and display information about each backend.
+ *
+ * @param iInput $input An instance of the iInput class used for input operations.
+ * @param iOutput $output An instance of the iOutput class used for output operations.
+ *
+ * @return void
+ */
private function getBackends(iInput $input, iOutput $output): void
{
$includeSample = (bool)$input->getOption('include-db-sample');
@@ -239,6 +270,13 @@ final class ReportCommand extends Command
}
}
+ /**
+ * Retrieves the tasks and displays information about each task.
+ *
+ * @param iOutput $output An instance of the iOutput class used for displaying output.
+ *
+ * @return void
+ */
private function getTasks(iOutput $output): void
{
foreach (Config::get('tasks.list', []) as $task) {
@@ -274,7 +312,7 @@ final class ReportCommand extends Command
'answer' => gmdate(Date::ATOM, $timer->getNextRunDate()->getTimestamp()),
])
);
- } catch (Exception $e) {
+ } catch (Throwable $e) {
$output->writeln(
r('Next Run scheduled failed. {answer}', [
'answer' => $e->getMessage(),
@@ -288,6 +326,12 @@ final class ReportCommand extends Command
}
}
+ /**
+ * Get logs.
+ *
+ * @param iInput $input An instance of the iInput class used for input operations.
+ * @param iOutput $output An instance of the iOutput class used for output operations.
+ */
private function getLogs(iInput $input, iOutput $output): void
{
$todayAffix = makeDate()->format('Ymd');
@@ -307,6 +351,16 @@ final class ReportCommand extends Command
}
}
+ /**
+ * Get last X lines from log file.
+ *
+ * @param iOutput $output An instance of the iOutput class used for displaying output.
+ * @param string $type The type of the log.
+ * @param string|int $date The date of the log file.
+ * @param int|string $limit The maximum number of lines to display.
+ *
+ * @return void
+ */
private function handleLog(iOutput $output, string $type, string|int $date, int|string $limit): void
{
$logFile = Config::get('tmpDir') . '/logs/' . r(
diff --git a/src/Commands/System/RoutesCommand.php b/src/Commands/System/RoutesCommand.php
index 41b353df..0aa5e9ca 100644
--- a/src/Commands/System/RoutesCommand.php
+++ b/src/Commands/System/RoutesCommand.php
@@ -9,11 +9,19 @@ use App\Libs\Routable;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class RoutesCommand
+ *
+ * This command is used to generate routes for commands. It is automatically run on container startup.
+ */
#[Routable(command: self::ROUTE)]
final class RoutesCommand extends Command
{
public const ROUTE = 'system:routes';
+ /**
+ * Configures the command.
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -28,6 +36,14 @@ final class RoutesCommand extends Command
);
}
+ /**
+ * Executes the command to generate routes.
+ *
+ * @param InputInterface $input The input interface object.
+ * @param OutputInterface $output The output interface object.
+ *
+ * @return int The exit code of the command execution.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
generateRoutes();
diff --git a/src/Commands/System/ServerCommand.php b/src/Commands/System/ServerCommand.php
index d5678bec..5745282f 100644
--- a/src/Commands/System/ServerCommand.php
+++ b/src/Commands/System/ServerCommand.php
@@ -11,16 +11,35 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * Class ServerCommand
+ *
+ * This class represents a command that starts a minimal HTTP server.
+ *
+ * @package YourPackage
+ */
#[Routable(command: self::ROUTE)]
final class ServerCommand extends Command
{
public const ROUTE = 'system:server';
+ /**
+ * Class Constructor.
+ *
+ * @param Server $server The server object to be injected.
+ *
+ * @return void
+ */
public function __construct(private Server $server)
{
parent::__construct();
}
+ /**
+ * Configure the command options and description.
+ *
+ * @return void
+ */
protected function configure(): void
{
$this->setName(self::ROUTE)
@@ -37,6 +56,14 @@ final class ServerCommand extends Command
);
}
+ /**
+ * Runs Server.
+ *
+ * @param InputInterface $input The input interface object containing command input options.
+ * @param OutputInterface $output The output interface object used to display command output.
+ *
+ * @return int Returns the exit code of the command.
+ */
protected function runCommand(InputInterface $input, OutputInterface $output): int
{
$host = $input->getOption('interface');
diff --git a/src/Commands/System/TasksCommand.php b/src/Commands/System/TasksCommand.php
index d4de4894..149d983b 100644
--- a/src/Commands/System/TasksCommand.php
+++ b/src/Commands/System/TasksCommand.php
@@ -8,6 +8,7 @@ use App\Command;
use App\Libs\Config;
use App\Libs\Extends\ConsoleOutput;
use App\Libs\Routable;
+use App\Libs\Stream;
use Cron\CronExpression;
use Exception;
use Symfony\Component\Console\Completion\CompletionInput;
@@ -17,7 +18,13 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface as iOutput;
use Symfony\Component\Process\Process;
+use Throwable;
+/**
+ * Class TasksCommand
+ *
+ * Automates the runs of scheduled tasks.
+ */
#[Routable(command: self::ROUTE)]
final class TasksCommand extends Command
{
@@ -26,6 +33,9 @@ final class TasksCommand extends Command
private array $logs = [];
private array $taskOutput = [];
+ /**
+ * Class Constructor.
+ */
public function __construct()
{
set_time_limit(0);
@@ -34,6 +44,9 @@ final class TasksCommand extends Command
parent::__construct();
}
+ /**
+ * Configure the command.
+ */
protected function configure(): void
{
$tasksName = implode(
@@ -109,7 +122,12 @@ final class TasksCommand extends Command
}
/**
- * @throws Exception
+ * If the run option is set, run the tasks, otherwise list available tasks.
+ *
+ * @param iInput $input The input instance.
+ * @param iOutput $output The output instance.
+ *
+ * @return int Returns the exit code of the command.
*/
protected function runCommand(iInput $input, iOutput $output): int
{
@@ -117,10 +135,34 @@ final class TasksCommand extends Command
return $this->runTasks($input, $output);
}
- $this->listTasks($input, $output);
+ $list = [];
+
+ $mode = $input->getOption('output');
+
+ foreach ($this->getTasks() as $task) {
+ $list[] = [
+ 'name' => $task['name'],
+ 'command' => $task['command'],
+ 'options' => $task['args'] ?? '',
+ 'timer' => $task['timer']->getExpression(),
+ 'description' => $task['description'] ?? '',
+ 'NextRun' => $task['next'],
+ ];
+ }
+
+ $this->displayContent($list, $output, $mode);
+
return self::SUCCESS;
}
+ /**
+ * Runs the tasks.
+ *
+ * @param iInput $input The input object.
+ * @param iOutput $output The output object.
+ *
+ * @return int The exit code of the command.
+ */
private function runTasks(iInput $input, iOutput $output): int
{
$run = [];
@@ -232,15 +274,35 @@ final class TasksCommand extends Command
}
if ($input->getOption('save-log') && count($this->logs) >= 1) {
- if (false !== ($fp = @fopen(Config::get('tasks.logfile'), 'a'))) {
- fwrite($fp, preg_replace('#\R+#', PHP_EOL, implode(PHP_EOL, $this->logs)) . PHP_EOL . PHP_EOL);
- fclose($fp);
+ try {
+ $stream = new Stream(Config::get('tasks.logfile'), 'a');
+ $stream->write(preg_replace('#\R+#', PHP_EOL, implode(PHP_EOL, $this->logs)) . PHP_EOL . PHP_EOL);
+ $stream->close();
+ } catch (Throwable $e) {
+ $this->write(r('Failed to open log file [{file}]. Error [{message}].', [
+ 'file' => Config::get('tasks.logfile'),
+ 'message' => $e->getMessage(),
+ ]), $input, $output);
+
+ return self::INVALID;
}
}
return self::SUCCESS;
}
+ /**
+ * Write method.
+ *
+ * Writes a given text to the output with the specified level.
+ * Optionally if the 'save-log' option is set to true, the output will be saved to the logs array.
+ * The logs array will be saved to the log file at the end of the command execution.
+ *
+ * @param string $text The text to write to output.
+ * @param iInput $input The input object.
+ * @param iOutput $output The output object.
+ * @param int $level The level of the output (default: iOutput::OUTPUT_NORMAL).
+ */
private function write(string $text, iInput $input, iOutput $output, int $level = iOutput::OUTPUT_NORMAL): void
{
assert($output instanceof ConsoleOutput);
@@ -252,28 +314,10 @@ final class TasksCommand extends Command
}
/**
- * @throws Exception
+ * Get the list of tasks.
+ *
+ * @return array The list of tasks.
*/
- private function listTasks(iInput $input, iOutput $output): void
- {
- $list = [];
-
- $mode = $input->getOption('output');
-
- foreach ($this->getTasks() as $task) {
- $list[] = [
- 'name' => $task['name'],
- 'command' => $task['command'],
- 'options' => $task['args'] ?? '',
- 'timer' => $task['timer']->getExpression(),
- 'description' => $task['description'] ?? '',
- 'NextRun' => $task['next'],
- ];
- }
-
- $this->displayContent($list, $output, $mode);
- }
-
private function getTasks(): array
{
$list = [];
@@ -302,6 +346,12 @@ final class TasksCommand extends Command
return $list;
}
+ /**
+ * Complete the input with suggestions if necessary.
+ *
+ * @param CompletionInput $input The completion input object.
+ * @param CompletionSuggestions $suggestions The completion suggestions object.
+ */
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
parent::complete($input, $suggestions);