commit ac95dbb1c8f6424872443b674806aeb7bfaff3cf
Author: zimoyin <2556608754@qq.com>
Date: Wed Apr 1 15:07:15 2026 +0800
init
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..516299c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+/runtime
+/.idea
+/.vscode
+/vendor
+*.log
+.env
+/tests/tmp
+/tests/.phpunit.result.cache
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..94ce154
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+FROM php:8.3.22-cli-alpine
+
+RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
+
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
+ && apk update --no-cache \
+ && docker-php-source extract
+
+# install extensions
+RUN docker-php-ext-install pdo pdo_mysql -j$(nproc) pcntl
+
+# enable opcache and pcntl
+RUN docker-php-ext-enable opcache pcntl
+RUN docker-php-source delete \
+ rm -rf /var/cache/apk/*
+
+RUN mkdir -p /app
+WORKDIR /app
\ No newline at end of file
diff --git a/PHPUnit.php b/PHPUnit.php
new file mode 100644
index 0000000..9b7abf5
--- /dev/null
+++ b/PHPUnit.php
@@ -0,0 +1,49 @@
+
+
webman
+
+基于workerman开发的超高性能PHP框架
+
+
+学习
+
+
+
+
+
+
赞助商
+
+
特别赞助
+
+
+
+
+
铂金赞助
+

+

+
+
+
+
+
+
+
+
请作者喝咖啡
+
+

+

+
+
如果您觉得webman对您有所帮助,欢迎捐赠。
+
+
+
+
+
+
+
LICENSE
+The webman is open-sourced software licensed under the MIT.
+
+
+
+
+
diff --git a/app/bootstrap/SqlDebug.php b/app/bootstrap/SqlDebug.php
new file mode 100644
index 0000000..b8de867
--- /dev/null
+++ b/app/bootstrap/SqlDebug.php
@@ -0,0 +1,82 @@
+dbDebug) Db::connection()->listen(function (QueryExecuted $queryExecuted) use ($appPath) {
+ // 过滤掉 "select 1" 这类心跳检测SQL
+ if (isset($queryExecuted->sql) && $queryExecuted->sql !== "select 1") {
+ $bindings = $queryExecuted->bindings;
+ // 替换SQL中的?为实际绑定参数
+ $sql = array_reduce(
+ $bindings,
+ function ($sql, $binding) {
+ // 处理参数类型:字符串加引号,数值/布尔直接使用,null显示为NULL
+ $value = match (true) {
+ is_string($binding) => "'{$binding}'",
+ is_null($binding) => 'NULL',
+ is_bool($binding) => $binding ? 1 : 0,
+ default => $binding
+ };
+ return preg_replace('/\?/', $value, $sql, 1);
+ },
+ $queryExecuted->sql
+ );
+
+ // 构造基础SQL日志信息
+ $sqlLog = sprintf(
+ "%s",
+ $sql
+ );
+
+ // 定位产生SQL的业务文件/行号/方法
+ $traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ foreach ($traces as $trace) {
+ if (isset($trace['file'], $trace['function']) && str_contains($trace['file'], $appPath)) {
+ // 格式化文件路径(去掉项目根目录,只保留相对路径)
+ $file = str_replace(base_path(), '', $trace['file']);
+ $file = ltrim($file, '/\\');
+ $file = str_replace(".php", '', $file);
+ $file = str_replace("\\", '.', $file);
+ $file = str_replace("/", '.', $file);
+// $file = basename($file);
+
+ // 使用Logger::debug输出日志(会同时输出到控制台和日志文件)
+ Log::debug(
+ '' . $file . "{$trace['line']}{$trace['function']}[$queryExecuted->time ms] " . $sqlLog
+ );
+ break; // 只打印第一个匹配的业务文件信息,避免重复输出
+ }
+ }
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/config/Config.php b/app/config/Config.php
new file mode 100644
index 0000000..be07db9
--- /dev/null
+++ b/app/config/Config.php
@@ -0,0 +1,126 @@
+ $this->logRotationTimeByDay;
+ }
+
+ /**
+ * @var int 错误日志轮转时间默认 30 天
+ */
+ public int $errorLogRotationTimeByDay {
+ get => $this->errorLogRotationTimeByDay;
+ }
+
+ public DatabaseConfig $database {
+ get => $this->database;
+ }
+
+ public bool $dbDebug {
+ get => $this->dbDebug;
+ }
+
+ /**
+ * 方式 类:方法
+ * 方式 类
+ * 方式 :方法
+ * 允许使用正则 * 作为通配符
+ * @var array 日志过滤器
+ */
+ public array $logFilter {
+ get => $this->logFilter;
+ }
+
+ public int $logLevel {
+ get => $this->logLevel;
+ }
+
+ /**
+ * 阻断模式
+ */
+ public bool $blockMode {
+ get => $this->blockMode;
+ }
+
+ /**
+ * TCP Server 进程数量
+ */
+ public int $tcpServerProcessNum = 1 {
+ get => $this->tcpServerProcessNum;
+ }
+
+ /**
+ * TCP_SERVER_PORT
+ */
+ public int $tcpServerPort = 50000 {
+ get => $this->tcpServerPort;
+ }
+
+ private function __construct()
+ {
+ $this->database = new DatabaseConfig();
+ $this->dbDebug = self::getBoolEnv('DB_DEBUG');
+ $this->logFilter = self::getStringArrayEnv('LOG_FILTER', []);
+ $this->logLevel = match (strtoupper(self::getStringEnv('LOG_LEVEL', 'DEBUG'))) {
+ 'INFO' => \Monolog\Logger::INFO,
+ 'WARNING' => \Monolog\Logger::WARNING,
+ 'ERROR' => \Monolog\Logger::ERROR,
+ 'ALERT' => \Monolog\Logger::ALERT,
+ 'EMERGENCY' => \Monolog\Logger::EMERGENCY,
+ 'CRITICAL' => \Monolog\Logger::CRITICAL,
+ 'NOTICE' => \Monolog\Logger::NOTICE,
+ default => \Monolog\Logger::DEBUG
+ };
+ $this->tcpServerPort = self::getIntEnv('TCP_SERVER_PORT', 50000);
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && $this->tcpServerProcessNum > 1) {
+ $this->tcpServerProcessNum = 1;
+ echo "Warning: TCP_SERVER_PROCESS_NUM set to 1 on Windows.\n";
+ }
+ }
+
+ private function __clone()
+ {
+ }
+
+ public static function getBoolEnv($name, $default = false)
+ {
+ $value = getenv($name);
+ return empty($value) ? $default : filter_var($value, FILTER_VALIDATE_BOOLEAN);
+ }
+
+ public static function getIntEnv($name, $default = 0)
+ {
+ $value = getenv($name);
+ return empty($value) ? $default : (int)$value;
+ }
+
+ public static function getStringEnv($name, $default = null)
+ {
+ $value = getenv($name);
+ return empty($value) ? $default : $value;
+ }
+
+ private static ?Config $instance = null;
+
+ public static function getInstance(): Config
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ private static function getStringArrayEnv(string $string, array $array): array
+ {
+ $value = getenv($string);
+ return empty($value) ? $array : array_map('trim', explode(';', $value));
+ }
+
+}
\ No newline at end of file
diff --git a/app/config/DatabaseConfig.php b/app/config/DatabaseConfig.php
new file mode 100644
index 0000000..21f771a
--- /dev/null
+++ b/app/config/DatabaseConfig.php
@@ -0,0 +1,48 @@
+host;
+ }
+ }
+ public string $username = 'root' {
+ get {
+ return $this->username;
+ }
+ }
+ public string $password = '' {
+ get {
+ return $this->password;
+ }
+ }
+ public string $database = 'opm_ectms' {
+ get {
+ return $this->database;
+ }
+ }
+
+ public function __construct()
+ {
+ $this->host = getenv('DB_HOST');
+ $this->username = getenv('DB_USER');
+ $this->password = getenv('DB_PASSWORD');
+ $this->database = getenv('DB_NAME');
+ }
+
+ /**
+ * @param $connection string|null
+ * @return Connection
+ */
+ public function getConnection(?string $connection = null): Connection
+ {
+ return Db::connection($connection);
+ }
+}
\ No newline at end of file
diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php
new file mode 100644
index 0000000..396ea80
--- /dev/null
+++ b/app/controller/IndexController.php
@@ -0,0 +1,42 @@
+
+ * {
+ padding: 0;
+ margin: 0;
+ }
+ iframe {
+ border: none;
+ overflow: scroll;
+ }
+
+
+EOF;
+ }
+
+ public function view(Request $request)
+ {
+ return view('index/view', ['name' => 'webman']);
+ }
+
+ public function json(Request $request)
+ {
+ return json(['code' => 0, 'msg' => 'ok']);
+ }
+
+}
diff --git a/app/functions.php b/app/functions.php
new file mode 100644
index 0000000..5c9c58d
--- /dev/null
+++ b/app/functions.php
@@ -0,0 +1,4 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace app\middleware;
+
+use Webman\MiddlewareInterface;
+use Webman\Http\Response;
+use Webman\Http\Request;
+
+/**
+ * Class StaticFile
+ * @package app\middleware
+ */
+class StaticFile implements MiddlewareInterface
+{
+ public function process(Request $request, callable $handler): Response
+ {
+ // Access to files beginning with. Is prohibited
+ if (strpos($request->path(), '/.') !== false) {
+ return response('403 forbidden
', 403);
+ }
+ /** @var Response $response */
+ $response = $handler($request);
+ // Add cross domain HTTP header
+ /*$response->withHeaders([
+ 'Access-Control-Allow-Origin' => '*',
+ 'Access-Control-Allow-Credentials' => 'true',
+ ]);*/
+ return $response;
+ }
+}
diff --git a/app/model/为保证数据隔离与方法污染请使用repo做数据查询与操作 b/app/model/为保证数据隔离与方法污染请使用repo做数据查询与操作
new file mode 100644
index 0000000..e69de29
diff --git a/app/process/Http.php b/app/process/Http.php
new file mode 100644
index 0000000..f462c3a
--- /dev/null
+++ b/app/process/Http.php
@@ -0,0 +1,10 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace app\process;
+
+use FilesystemIterator;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use SplFileInfo;
+use Workerman\Timer;
+use Workerman\Worker;
+
+/**
+ * Class FileMonitor
+ * @package process
+ */
+class Monitor
+{
+ /**
+ * @var array
+ */
+ protected array $paths = [];
+
+ /**
+ * @var array
+ */
+ protected array $extensions = [];
+
+ /**
+ * @var array
+ */
+ protected array $loadedFiles = [];
+
+ /**
+ * @var int
+ */
+ protected int $ppid = 0;
+
+ /**
+ * Pause monitor
+ * @return void
+ */
+ public static function pause(): void
+ {
+ file_put_contents(static::lockFile(), time());
+ }
+
+ /**
+ * Resume monitor
+ * @return void
+ */
+ public static function resume(): void
+ {
+ clearstatcache();
+ if (is_file(static::lockFile())) {
+ unlink(static::lockFile());
+ }
+ }
+
+ /**
+ * Whether monitor is paused
+ * @return bool
+ */
+ public static function isPaused(): bool
+ {
+ clearstatcache();
+ return file_exists(static::lockFile());
+ }
+
+ /**
+ * Lock file
+ * @return string
+ */
+ protected static function lockFile(): string
+ {
+ return runtime_path('monitor.lock');
+ }
+
+ /**
+ * FileMonitor constructor.
+ * @param $monitorDir
+ * @param $monitorExtensions
+ * @param array $options
+ */
+ public function __construct($monitorDir, $monitorExtensions, array $options = [])
+ {
+ $this->ppid = function_exists('posix_getppid') ? posix_getppid() : 0;
+ static::resume();
+ $this->paths = (array)$monitorDir;
+ $this->extensions = $monitorExtensions;
+ foreach (get_included_files() as $index => $file) {
+ $this->loadedFiles[$file] = $index;
+ if (strpos($file, 'webman-framework/src/support/App.php')) {
+ break;
+ }
+ }
+ if (!Worker::getAllWorkers()) {
+ return;
+ }
+ $disableFunctions = explode(',', ini_get('disable_functions'));
+ if (in_array('exec', $disableFunctions, true)) {
+ echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n";
+ } else {
+ if ($options['enable_file_monitor'] ?? true) {
+ Timer::add(1, function () {
+ $this->checkAllFilesChange();
+ });
+ }
+ }
+
+ $memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null);
+ if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) {
+ Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]);
+ }
+ }
+
+ /**
+ * @param $monitorDir
+ * @return bool
+ */
+ public function checkFilesChange($monitorDir): bool
+ {
+ static $lastMtime, $tooManyFilesCheck;
+ if (!$lastMtime) {
+ $lastMtime = time();
+ }
+ clearstatcache();
+ if (!is_dir($monitorDir)) {
+ if (!is_file($monitorDir)) {
+ return false;
+ }
+ $iterator = [new SplFileInfo($monitorDir)];
+ } else {
+ // recursive traversal directory
+ $dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS);
+ $iterator = new RecursiveIteratorIterator($dirIterator);
+ }
+ $count = 0;
+ foreach ($iterator as $file) {
+ $count ++;
+ /** @var SplFileInfo $file */
+ if (is_dir($file->getRealPath())) {
+ continue;
+ }
+ // check mtime
+ if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) {
+ $lastMtime = $file->getMTime();
+ if (DIRECTORY_SEPARATOR === '/' && isset($this->loadedFiles[$file->getRealPath()])) {
+ echo "$file updated but cannot be reloaded because only auto-loaded files support reload.\n";
+ continue;
+ }
+ $var = 0;
+ exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var);
+ if ($var) {
+ continue;
+ }
+ // send SIGUSR1 signal to master process for reload
+ if (DIRECTORY_SEPARATOR === '/') {
+ if ($masterPid = $this->getMasterPid()) {
+ echo $file . " updated and reload\n";
+ posix_kill($masterPid, SIGUSR1);
+ } else {
+ echo "Master process has gone away and can not reload\n";
+ }
+ return true;
+ }
+ echo $file . " updated and reload\n";
+ return true;
+ }
+ }
+ if (!$tooManyFilesCheck && $count > 1000) {
+ echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n";
+ $tooManyFilesCheck = 1;
+ }
+ return false;
+ }
+
+ /**
+ * @return int
+ */
+ public function getMasterPid(): int
+ {
+ if ($this->ppid === 0) {
+ return 0;
+ }
+ if (function_exists('posix_kill') && !posix_kill($this->ppid, 0)) {
+ echo "Master process has gone away\n";
+ return $this->ppid = 0;
+ }
+ if (PHP_OS_FAMILY !== 'Linux') {
+ return $this->ppid;
+ }
+ $cmdline = "/proc/$this->ppid/cmdline";
+ if (!is_readable($cmdline) || !($content = file_get_contents($cmdline)) || (!str_contains($content, 'WorkerMan') && !str_contains($content, 'php'))) {
+ // Process not exist
+ $this->ppid = 0;
+ }
+ return $this->ppid;
+ }
+
+ /**
+ * @return bool
+ */
+ public function checkAllFilesChange(): bool
+ {
+ if (static::isPaused()) {
+ return false;
+ }
+ foreach ($this->paths as $path) {
+ if ($this->checkFilesChange($path)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param $memoryLimit
+ * @return void
+ */
+ public function checkMemory($memoryLimit): void
+ {
+ if (static::isPaused() || $memoryLimit <= 0) {
+ return;
+ }
+ $masterPid = $this->getMasterPid();
+ if ($masterPid <= 0) {
+ echo "Master process has gone away\n";
+ return;
+ }
+
+ $childrenFile = "/proc/$masterPid/task/$masterPid/children";
+ if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) {
+ return;
+ }
+ foreach (explode(' ', $children) as $pid) {
+ $pid = (int)$pid;
+ $statusFile = "/proc/$pid/status";
+ if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) {
+ continue;
+ }
+ $mem = 0;
+ if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) {
+ $mem = $match[1];
+ }
+ $mem = (int)($mem / 1024);
+ if ($mem >= $memoryLimit) {
+ posix_kill($pid, SIGINT);
+ }
+ }
+ }
+
+ /**
+ * Get memory limit
+ * @param $memoryLimit
+ * @return int
+ */
+ protected function getMemoryLimit($memoryLimit): int
+ {
+ if ($memoryLimit === 0) {
+ return 0;
+ }
+ $usePhpIni = false;
+ if (!$memoryLimit) {
+ $memoryLimit = ini_get('memory_limit');
+ $usePhpIni = true;
+ }
+
+ if ($memoryLimit == -1) {
+ return 0;
+ }
+ $unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]);
+ $memoryLimit = (int)$memoryLimit;
+ if ($unit === 'g') {
+ $memoryLimit = 1024 * $memoryLimit;
+ } else if ($unit === 'k') {
+ $memoryLimit = ($memoryLimit / 1024);
+ } else if ($unit === 'm') {
+ $memoryLimit = (int)($memoryLimit);
+ } else if ($unit === 't') {
+ $memoryLimit = (1024 * 1024 * $memoryLimit);
+ } else {
+ $memoryLimit = ($memoryLimit / (1024 * 1024));
+ }
+ if ($memoryLimit < 50) {
+ $memoryLimit = 50;
+ }
+ if ($usePhpIni) {
+ $memoryLimit = (0.8 * $memoryLimit);
+ }
+ return (int)$memoryLimit;
+ }
+
+}
diff --git a/app/process/TcpServer.php b/app/process/TcpServer.php
new file mode 100644
index 0000000..817971c
--- /dev/null
+++ b/app/process/TcpServer.php
@@ -0,0 +1,134 @@
+ 连接池,key为客户端IP,value为连接对象
+ */
+ private static array $connections = [];
+
+ public function __construct()
+ {
+ // 初始化 FlowMain
+ FlowMain::getInstance();
+ }
+
+ /**
+ * 获取连接池
+ */
+ public static function getConnections(): array
+ {
+ return self::$connections;
+ }
+
+ /**
+ * 获取链接
+ */
+ public static function getConnection(string $ip): ?TcpConnection
+ {
+ return self::$connections[$ip] ?? null;
+ }
+
+ /**
+ * 获取所有IP
+ */
+ public static function getAllIp(): array
+ {
+ return array_keys(self::$connections);
+ }
+
+ /**
+ * 获取所有连接
+ */
+ public static function getAllConnections(): array
+ {
+ return self::$connections;
+ }
+
+ /**
+ * Worker启动时触发
+ */
+ public function onWorkerStart($worker): void
+ {
+ Logger::info("TcpServer started");
+ }
+
+
+ /**
+ * 连接时触发
+ */
+ public function onConnect(TcpConnection $connection): void
+ {
+ self::$connections[$connection->getRemoteIp()] = $connection;
+ Log::info("客户端链接到主机: {$connection->getRemoteIp()}");
+ }
+
+ /**
+ * 接收数据时触发
+ */
+ public function onMessage(TcpConnection $connection, $data): void
+ {
+ $ip = $connection->getRemoteIp();
+ self::$connections[$ip] = $connection;
+ $packet = PacketParserFactory::new($data);
+ $context = new PacketContext(self::getConnections(), $connection, $packet);
+ $this->logMessage($packet, $ip);
+ if ($packet->isMatched) {
+ $result = FlowMain::getInstance()->main($context);
+ $connection->send($result->getFullVoice());
+ } else {
+ $connection->send("Packet is not matched");
+ }
+ }
+
+ /**
+ * 连接关闭时触发
+ */
+ public function onClose(TcpConnection $connection): void
+ {
+ unset(self::$connections[$connection->getRemoteIp()]);
+ Logger::info("客户端已断开连接:{$connection->getRemoteIp()}");
+ }
+
+ /**
+ * @param \app\net\Packet $packet
+ * @param string $ip
+ * @return void
+ */
+ public function logMessage(\app\net\Packet $packet, string $ip): void
+ {
+ if ($packet->isMatched) {
+ Logger::debug(Logger::generateTextBox([
+ "---------------------------- PACKET --------------------------------",
+ "IP Address : {$ip}",
+ "Packet Length: {$packet->length}",
+ "Hex Packet : {$packet->hexString}",
+ "Packet Type : {$packet->hexType->name}",
+ "---------------------------- DATA --------------------------------",
+ "Reader Info : {$packet->reader}",
+ "Card Info : {$packet->card}",
+ "Gateway Info : {$packet->gateway}",
+ ]));
+ } else {
+ Logger::debug("Packet is not matched");
+ Logger::debug(Logger::generateTextBox([
+ "---------------------------- PACKET --------------------------------",
+ "IP Address : {$ip}",
+ "Packet Length: {$packet->length}",
+ "Packet Type : {$packet->hexType->name}",
+ "---------------------------- DATA --------------------------------",
+ "Hex Packet : {$packet->hexString}"
+ ]));
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/repository/BaseRepository.php b/app/repository/BaseRepository.php
new file mode 100644
index 0000000..3757859
--- /dev/null
+++ b/app/repository/BaseRepository.php
@@ -0,0 +1,12 @@
+ trim($row) !== '');
+ if (empty($validRows)) return '';
+
+ // 计算最长行的字符长度(核心:按实际字符数计算)
+ $maxContentLen = max(array_map('mb_strlen', $validRows));
+
+ // 格式化每行:左侧固定前缀,右侧补空格至最长长度
+ $formattedRows = array_map(function ($row) use ($maxContentLen) {
+ $currentLen = mb_strlen($row);
+ $padding = $maxContentLen - $currentLen; // 精准计算需要补充的空格数
+ return '| ' . $row . str_repeat(' ', max(0, $padding)) . ' |';
+ }, $validRows);
+
+ // 计算边框总宽度(格式化后最长行的长度)
+ $borderLen = max(array_map('strlen', $formattedRows));
+ $borderLine = str_repeat($borderChar, $borderLen);
+
+ // 空行(和内容行宽度一致)
+ $emptyLine = '| ' . str_repeat(' ', $maxContentLen) . ' |';
+
+ // 拼接文本框
+ return "\n{$borderLine}\n{$emptyLine}\n" . implode("\n", $formattedRows) . "\n{$emptyLine}\n{$borderLine}";
+ }
+
+ /**
+ * 静态信息级别日志
+ * @param string $message 日志消息
+ * @param array|Throwable $context 占位符参数或异常对象
+ * @param string $name 日志器名称
+ */
+ public static function info(string $message, array|Throwable $context = [], string $name = '__default'): void
+ {
+ self::new($name)->info($message, $context);
+ }
+
+ /**
+ * 静态调试级别日志
+ * @param string $message 日志消息
+ * @param array|Throwable $context 占位符参数或异常对象
+ * @param string $name 日志器名称
+ */
+ public static function debug(string $message, array|Throwable $context = [], string $name = '__default'): void
+ {
+ self::new($name)->debug($message, $context);
+ }
+
+ /**
+ * 静态warn方法(warning别名)
+ * @param string $message 日志消息
+ * @param array|Throwable $context 占位符参数或异常对象
+ * @param string $name 日志器名称
+ */
+ public static function warn(string $message, array|Throwable $context = [], string $name = '__default'): void
+ {
+ self::new($name)->warn($message, $context);
+ }
+
+ /**
+ * 静态warn方法(warning别名)
+ * @param string $message 日志消息
+ * @param array|Throwable $context 占位符参数或异常对象
+ * @param string $name 日志器名称
+ */
+ public static function warning(string $message, array|Throwable $context = [], string $name = '__default'): void
+ {
+ self::new($name)->warn($message, $context);
+ }
+
+ /**
+ * 静态错误级别日志
+ * @param string $message 日志消息
+ * @param array|Throwable $context 占位符参数或异常对象
+ * @param string $name 日志器名称
+ */
+ public static function error(string $message, array|Throwable $context = [], string $name = '__default'): void
+ {
+ self::new($name)->error($message, $context);
+ }
+
+ /**
+ * 静态通用日志方法
+ * @param string $level 日志级别
+ * @param string $message 日志消息
+ * @param array|Throwable $context 占位符参数或异常对象
+ * @param string $name 日志器名称
+ */
+ public static function log(string $level, string $message, array|Throwable $context = [], string $name = '__default'): void
+ {
+ self::new($name)->log($level, $message, $context);
+ }
+}
+
+/**
+ * 日志实例类(仅提供实例方法)
+ */
+class LoggerIns
+{
+ public $name = '';
+
+ /**
+ * 构造函数:初始化日志器名称
+ * @param string $name 日志器名称,默认 '__default'
+ */
+ public function __construct(string $name = '__default')
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * 信息级别日志
+ * @param string $message 日志消息(支持 {} 占位符)
+ * @param array|Throwable $context 占位符替换参数或异常对象
+ */
+ public function info(string $message, array|Throwable $context = []): void
+ {
+ $processedMessage = $this->buildMessage($message, $context);
+ if (empty($processedMessage)) return;
+ Log::info($processedMessage);
+ }
+
+ /**
+ * 调试级别日志
+ * @param string $message 日志消息(支持 {} 占位符)
+ * @param array|Throwable $context 占位符替换参数或异常对象
+ */
+ public function debug(string $message, array|Throwable $context = []): void
+ {
+ $processedMessage = $this->buildMessage($message, $context);
+ if (empty($processedMessage)) return;
+ Log::debug($processedMessage);
+ }
+
+ /**
+ * 警告级别日志
+ * @param string $message 日志消息(支持 {} 占位符)
+ * @param array|Throwable $context 占位符替换参数或异常对象
+ */
+ public function warning(string $message, array|Throwable $context = []): void
+ {
+ $processedMessage = $this->buildMessage($message, $context);
+ if (empty($processedMessage)) return;
+ Log::warning($processedMessage);
+ }
+
+ /**
+ * warn 方法 - warning 的别名
+ * @param string $message 日志消息(支持 {} 占位符)
+ * @param array|Throwable $context 占位符替换参数或异常对象
+ */
+ public function warn(string $message, array|Throwable $context = []): void
+ {
+ $this->warning($message, $context);
+ }
+
+ /**
+ * 错误级别日志
+ * @param string $message 日志消息(支持 {} 占位符)
+ * @param array|Throwable $context 占位符替换参数或异常对象
+ */
+ public function error(string $message, array|Throwable $context = []): void
+ {
+ $processedMessage = $this->buildMessage($message, $context);
+ if (empty($processedMessage)) return;
+ Log::error($processedMessage);
+ }
+
+ /**
+ * 通用日志方法
+ * @param string $level 日志级别 (info/debug/warning/warn/error/fatal)
+ * @param string $message 日志消息(支持 {} 占位符)
+ * @param array|Throwable $context 占位符替换参数或异常对象
+ */
+ public function log(string $level, string $message, array|Throwable $context = []): void
+ {
+ // 验证并处理日志级别(兼容warn别名)
+ $validLevels = ['info', 'debug', 'warning', 'warn', 'error', 'fatal'];
+ $level = strtolower($level);
+
+ // 将warn映射为warning
+ if ($level === 'warn') {
+ $level = 'warning';
+ }
+
+ if (!in_array($level, $validLevels)) {
+ $level = 'info'; // 默认使用info级别
+ }
+
+ $processedMessage = $this->buildMessage($message, $context);
+ if (empty($processedMessage)) return;
+ Log::{$level}($processedMessage);
+ }
+
+ /**
+ * 处理日志消息:替换占位符、处理异常、添加名称前缀
+ * @param string $message 原始日志消息
+ * @param array|Throwable $context 占位符参数或异常对象
+ * @return string 处理后的日志消息
+ */
+ private function buildMessage(string $message, array|Throwable $context = []): string
+ {
+ // 1. 先处理占位符替换(无论是否有异常,先处理消息本身)
+ $processedMessage = $this->replacePlaceholders($message, is_array($context) ? $context : []);
+
+ // 2. 处理异常对象:换行输出完整异常内容,优化堆栈缩进
+ if ($context instanceof Throwable) {
+ // 转换异常为字符串并优化堆栈缩进(制表符)
+ $exceptionStr = $this->formatExceptionStackTrace((string)$context);
+ // 使用 PHP_EOL 保证跨系统换行
+ $processedMessage .= PHP_EOL . $exceptionStr;
+ }
+
+ // 3. 添加名称前缀 [name]
+ $namePrefix = "name}>";
+ $result = $namePrefix . $processedMessage;
+// if ($this->isFilter()) return "";
+ return $result;
+ }
+
+
+ /**
+ * 解析调用堆栈,获取实际业务代码的类名和方法名
+ * @return array ['class' => 业务类名, 'method' => 业务方法名]
+ */
+ private function parseBusinessStackInfo(): array
+ {
+ $stackInfo = ['class' => '', 'method' => ''];
+ // 获取调用栈(忽略参数,避免性能损耗,取前10层足够)
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
+
+ // 跳过的日志相关类/方法(根据实际项目调整)
+ $skipPatterns = [
+ 'class' => [
+ 'Monolog\\', // 跳过Monolog核心类
+ __CLASS__, // 跳过当前类自身
+ 'app\\logger\\', // 跳过大日志封装类(根据你的项目调整)
+ ],
+ 'method' => [
+ 'buildMessage', // 跳过当前方法
+ 'isFilter', // 跳过过滤方法
+ 'parseBusinessStackInfo', // 跳过堆栈解析方法
+ 'replacePlaceholders',// 跳過占位符替换方法
+ 'formatExceptionStackTrace', // 跳过异常格式化方法
+ 'log', 'error', 'info', 'warning', 'debug' // 跳过日志级别方法
+ ]
+ ];
+
+ // 遍历调用栈,找到第一个业务代码层
+ foreach ($trace as $step) {
+ // 跳过无文件/无方法的栈帧
+ if (!isset($step['file']) || !isset($step['function'])) {
+ continue;
+ }
+
+ // 跳过日志相关类
+ $currentClass = $step['class'] ?? '';
+ $isSkipClass = false;
+ foreach ($skipPatterns['class'] as $pattern) {
+ if ($pattern && str_contains($currentClass, $pattern)) {
+ $isSkipClass = true;
+ break;
+ }
+ }
+ if ($isSkipClass) {
+ continue;
+ }
+
+ // 跳过日志相关方法
+ $currentMethod = $step['function'];
+ if (in_array($currentMethod, $skipPatterns['method'])) {
+ continue;
+ }
+
+ // 解析业务类名(简化为项目内相对路径,和原逻辑一致)
+ $projectRoot = defined('BASE_PATH') ? BASE_PATH : (function_exists('base_path') ? base_path() : '');
+ $file = $step['file'];
+ if ($projectRoot && str_starts_with($file, $projectRoot)) {
+ $file = substr($file, strlen($projectRoot) + 1);
+ // 转换为类名格式(app/controller/Index.php → app.controller.Index)
+ $class = str_replace(['.php', '\\', '/'], ['', '.', '.'], $file);
+ } else {
+ $class = $currentClass ?: basename($file, '.php');
+ }
+
+ // 赋值并终止遍历(找到第一个业务层即可)
+ $stackInfo['class'] = trim($class);
+ $stackInfo['method'] = $currentMethod;
+ break;
+ }
+
+ return $stackInfo;
+ }
+
+ /**
+ * 格式化异常堆栈:让Stack trace下的每一行添加制表符缩进
+ * @param string $exceptionStr 原始异常字符串
+ * @return string 格式化后的异常字符串
+ */
+ private function formatExceptionStackTrace(string $exceptionStr): string
+ {
+ // 正则匹配 Stack trace: 后的所有行,并在每行前添加制表符
+ $pattern = '/(Stack trace:\s*)(.*)$/s';
+ return preg_replace_callback($pattern, function ($matches) {
+ // $matches[1] 是 "Stack trace: "
+ // $matches[2] 是堆栈内容
+ $stackTrace = $matches[2];
+ // 将堆栈的每一行(#开头)前添加 制表符(\t)
+ $formattedStack = preg_replace('/(#\d+)/', "\t$1", $stackTrace);
+ return $matches[1] . $formattedStack;
+ }, $exceptionStr);
+ }
+
+ /**
+ * 内部方法:替换 {} 占位符
+ * @param string $message 原始消息
+ * @param array $context 替换参数
+ * @return string 替换后的消息
+ */
+ private function replacePlaceholders(string $message, array $context = []): string
+ {
+ if (empty($context)) {
+ return $message;
+ }
+
+ // 将上下文参数转换为安全的字符串
+ $replacements = array_map(function ($value) {
+ if ($value === null) {
+ return 'null';
+ }
+ if (is_bool($value)) {
+ return $value ? 'true' : 'false';
+ }
+ if (is_array($value) || is_object($value)) {
+ return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR);
+ }
+ return (string)$value;
+ }, $context);
+
+ // 按顺序替换 {} 占位符
+ $index = 0;
+ $result = preg_replace_callback('/\{\}/', function () use (&$index, $replacements) {
+ return isset($replacements[$index]) ? $replacements[$index++] : '{}';
+ }, $message);
+
+ // 替换失败时返回原始消息
+ return $result ?: $message;
+ }
+}
\ No newline at end of file
diff --git a/app/utils/ModelAutoGenerator.php b/app/utils/ModelAutoGenerator.php
new file mode 100644
index 0000000..7799d64
--- /dev/null
+++ b/app/utils/ModelAutoGenerator.php
@@ -0,0 +1,337 @@
+ bool, 'msg' => string]
+ */
+ public static function generate(string $tableName, string $modelName, bool $force = false): array
+ {
+ // 1. 定义模型文件路径(兼容webman的app_path)
+ $modelPath = app_path() . '/model/' . ucfirst($modelName) . '.php';
+ $modelClassName = ucfirst($modelName); // 确保类名首字母大写
+
+ // 2. 检查文件是否已存在,存在则返回不覆盖提示
+ if (file_exists($modelPath) && !$force) {
+ return [
+ 'status' => false,
+ 'msg' => "模型文件 {$modelClassName}.php 已存在,不执行覆盖操作"
+ ];
+ }
+
+ try {
+ // 3. 读取数据表基本信息(包含表注释)- 修复参数绑定问题
+ $tableComment = self::getTableComment($tableName);
+ // 4. 读取数据表结构(包含字段注释、类型等)
+ $tableStruct = self::getTableStruct($tableName);
+
+ if (empty($tableStruct)) {
+ return [
+ 'status' => false,
+ 'msg' => "数据表 {$tableName} 无字段信息,生成失败"
+ ];
+ }
+
+ // 5. 构建模型文件内容(传入表注释)
+ $modelContent = self::buildModelContent($tableName, $modelClassName, $tableStruct, $tableComment);
+
+ // 6. 确保model目录存在
+ if (!is_dir(dirname($modelPath))) {
+ mkdir(dirname($modelPath), 0755, true);
+ }
+
+ // 7. 写入模型文件
+ $writeResult = file_put_contents($modelPath, $modelContent);
+ if ($writeResult === false) {
+ throw new RuntimeException("模型文件写入失败,检查目录权限");
+ }
+
+ return [
+ 'status' => true,
+ 'msg' => "模型 {$modelClassName}.php 已成功生成至 app/model 目录"
+ ];
+
+ } catch (\Exception $e) {
+ return [
+ 'status' => false,
+ 'msg' => "生成失败:{$e->getMessage()}"
+ ];
+ }
+ }
+
+ /**
+ * 批量生成所有数据表的模型文件
+ *
+ * @param bool $force 是否强制覆盖已存在的模型文件
+ * @param array $excludeTables 排除的数据表(如:['migrations', 'logs'])
+ * @return array 批量生成结果 ['success' => 成功数量, 'fail' => 失败列表]
+ */
+ public static function generate_all(bool $force = false, array $excludeTables = []): array
+ {
+ $result = [
+ 'success' => 0,
+ 'fail' => []
+ ];
+
+ try {
+ // 1. 获取数据库中所有数据表
+ $tables = Db::select("SHOW TABLES");
+ $tableNameKey = 'Tables_in_' . config('database.connections.mysql.database');
+
+ foreach ($tables as $table) {
+ $tableName = $table->$tableNameKey;
+
+ // 跳过排除的表
+ if (in_array($tableName, $excludeTables)) {
+ continue;
+ }
+
+ // 跳过视图(以 v_ 开头的表,避免视图生成模型)
+ if (str_starts_with($tableName, 'v_')) {
+ continue;
+ }
+
+ // 2. 表名转模型名(下划线转驼峰,如 user_info → UserInfo)
+ $modelName = self::tableNameToModelName($tableName);
+
+ // 3. 调用单个生成方法
+ $generateResult = self::generate($tableName, $modelName, $force);
+ if ($generateResult['status']) {
+ $result['success']++;
+ } else {
+ $result['fail'][] = [
+ 'table' => $tableName,
+ 'model' => $modelName,
+ 'reason' => $generateResult['msg']
+ ];
+ }
+ }
+
+ return $result;
+
+ } catch (\Exception $e) {
+ $result['fail'][] = [
+ 'table' => 'all',
+ 'model' => 'all',
+ 'reason' => "批量生成异常:{$e->getMessage()}"
+ ];
+ return $result;
+ }
+ }
+
+ /**
+ * 获取数据表注释(修复参数绑定问题)
+ *
+ * @param string $tableName 数据表名
+ * @return string 表注释(无注释则返回空字符串)
+ */
+ private static function getTableComment(string $tableName): string
+ {
+ // 修复核心:不使用参数绑定,直接拼接表名(先过滤表名防止注入)
+ $safeTableName = preg_replace('/[^a-zA-Z0-9_]/', '', $tableName);
+ $sql = "SHOW TABLE STATUS LIKE '{$safeTableName}'";
+
+ try {
+ $tableInfo = Db::select($sql);
+ } catch (\Exception $e) {
+ // 读取表注释失败时返回空字符串,不影响模型生成
+ return '';
+ }
+
+ if (empty($tableInfo)) {
+ return '';
+ }
+
+ // 兼容不同数据库驱动的字段名(Comment/comment)
+ $comment = $tableInfo[0]->Comment ?? $tableInfo[0]->comment ?? '';
+ return trim($comment);
+ }
+
+ /**
+ * 获取数据表结构(修复关键字冲突+兼容字段注释读取)
+ *
+ * @param string $tableName 数据表名
+ * @return array 表结构数组
+ */
+ private static function getTableStruct(string $tableName): array
+ {
+ // 表名安全过滤
+ $safeTableName = preg_replace('/[^a-zA-Z0-9_]/', '', $tableName);
+ // 修复关键字冲突:给别名加反引号,避免与MySQL保留字冲突
+ $sql = "SELECT
+ COLUMN_NAME AS `Field`,
+ DATA_TYPE AS `TypeSimple`,
+ COLUMN_TYPE AS `Type`,
+ IS_NULLABLE AS `IsNull`,
+ COLUMN_KEY AS `ColumnKey`,
+ COLUMN_DEFAULT AS `ColumnDefault`,
+ COLUMN_COMMENT AS `Comment`
+ FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA = ?
+ AND TABLE_NAME = ?
+ ORDER BY ORDINAL_POSITION";
+
+ // 使用参数绑定读取字段信息(INFORMATION_SCHEMA 支持参数绑定)
+ $tableStruct = Db::select($sql, [
+ config('database.connections.mysql.database'),
+ $safeTableName
+ ]);
+
+ // 格式化字段信息,确保注释字段统一
+ return array_map(function ($field) {
+ return (object)[
+ 'Field' => $field->Field ?? '',
+ 'Type' => $field->Type ?? $field->TypeSimple ?? '',
+ 'Key' => $field->ColumnKey ?? '', // 对应修改后的别名
+ // 优先读取 COMMENT,兼容大小写,确保去空格
+ 'Comment' => trim($field->Comment ?? $field->comment ?? ''),
+ 'Null' => $field->IsNull ?? '', // 对应修改后的别名
+ 'Default' => $field->ColumnDefault ?? '' // 对应修改后的别名
+ ];
+ }, $tableStruct);
+ }
+
+ /**
+ * 构建模型文件内容(确保字段注释正确展示)
+ *
+ * @param string $tableName 数据表名
+ * @param string $modelClassName 模型类名
+ * @param array $tableStruct 表结构信息
+ * @param string $tableComment 表注释
+ * @return string 模型文件内容
+ */
+ private static function buildModelContent(string $tableName, string $modelClassName, array $tableStruct, string $tableComment): string
+ {
+ // 提取主键
+ $primaryKey = 'id';
+ foreach ($tableStruct as $field) {
+ if ($field->Key === 'PRI') {
+ $primaryKey = $field->Field;
+ break;
+ }
+ }
+
+ // 构建字段注释(格式:字段名 数据库注释)
+ $fieldComments = [];
+ foreach ($tableStruct as $field) {
+ $fieldName = $field->Field; // 原始字段名(如 dt)
+ $dbComment = $field->Comment; // 数据库注释(如 基准时间)
+
+ // 拼接注释:字段名 + (有数据库注释则加)数据库注释
+ $fullComment = $fieldName;
+ if (!empty($dbComment)) {
+ $fullComment .= " {$dbComment}";
+ }
+
+ // 补充字段属性说明(非空、主键、默认值)
+ $extComment = [];
+ if ($field->Null === 'NO') {
+ $extComment[] = '非空';
+ }
+ if ($field->Key === 'PRI') {
+ $extComment[] = '主键';
+ }
+ if ($field->Default !== '') {
+ $extComment[] = "默认值:{$field->Default}";
+ }
+
+ // 如有属性说明,追加到注释末尾
+ if (!empty($extComment)) {
+ $fullComment .= '(' . implode(',', $extComment) . ')';
+ }
+
+ $fieldType = self::dbTypeToPhpType($field->Type);
+ $fieldComments[] = " * @property {$fieldType} \${$fieldName} {$fullComment}";
+ }
+ $fieldCommentsStr = implode("\n", $fieldComments);
+
+ // 构建表注释(无注释则使用默认描述)
+ $tableCommentStr = !empty($tableComment) ? $tableComment : "{$tableName} 数据表模型";
+
+ // 模型模板(适配webman的think-orm)
+ return << 'int',
+ 'float', 'double', 'decimal' => 'float',
+ 'date', 'time', 'datetime', 'timestamp' => 'string',
+ 'bool', 'boolean' => 'bool',
+ default => 'string'
+ };
+ }
+}
\ No newline at end of file
diff --git a/app/view/index/view.html b/app/view/index/view.html
new file mode 100644
index 0000000..67ebb26
--- /dev/null
+++ b/app/view/index/view.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ webman
+
+
+
+hello =htmlspecialchars($name)?>
+
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..c848049
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,71 @@
+{
+ "name": "workerman/webman",
+ "type": "project",
+ "keywords": [
+ "high performance",
+ "http service"
+ ],
+ "homepage": "https://www.workerman.net",
+ "license": "MIT",
+ "description": "High performance HTTP Service Framework.",
+ "authors": [
+ {
+ "name": "walkor",
+ "email": "walkor@workerman.net",
+ "homepage": "https://www.workerman.net",
+ "role": "Developer"
+ }
+ ],
+ "support": {
+ "email": "walkor@workerman.net",
+ "issues": "https://github.com/walkor/webman/issues",
+ "forum": "https://wenda.workerman.net/",
+ "wiki": "https://workerman.net/doc/webman",
+ "source": "https://github.com/walkor/webman"
+ },
+ "require": {
+ "php": ">=8.4",
+ "workerman/webman-framework": "^2.1",
+ "monolog/monolog": "^2.0",
+ "webman/console": "^2.2",
+ "webman/database": "^2.1",
+ "webman/think-orm": "^2.1",
+ "illuminate/pagination": "^12.53",
+ "illuminate/events": "^12.53",
+ "symfony/var-dumper": "^8.0",
+ "vlucas/phpdotenv": "^5.6"
+ },
+ "suggest": {
+ "ext-event": "For better performance. "
+ },
+ "autoload": {
+ "psr-4": {
+ "": "./",
+ "app\\": "./app",
+ "App\\": "./app",
+ "app\\View\\Components\\": "./app/view/components"
+ }
+ },
+ "scripts": {
+ "post-package-install": [
+ "support\\Plugin::install"
+ ],
+ "post-package-update": [
+ "support\\Plugin::install"
+ ],
+ "pre-package-uninstall": [
+ "support\\Plugin::uninstall"
+ ],
+ "post-create-project-cmd": [
+ "support\\Setup::run"
+ ],
+ "setup-webman": [
+ "support\\Setup::run"
+ ]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..783b309
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,6981 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "ecd59dd718e1f410ae9c5a01fdd8c1be",
+ "packages": [
+ {
+ "name": "brick/math",
+ "version": "0.14.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/brick/math.git",
+ "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629",
+ "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.2"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpstan/phpstan": "2.1.22",
+ "phpunit/phpunit": "^11.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Brick\\Math\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Arbitrary-precision arithmetic library",
+ "keywords": [
+ "Arbitrary-precision",
+ "BigInteger",
+ "BigRational",
+ "arithmetic",
+ "bigdecimal",
+ "bignum",
+ "bignumber",
+ "brick",
+ "decimal",
+ "integer",
+ "math",
+ "mathematics",
+ "rational"
+ ],
+ "support": {
+ "issues": "https://github.com/brick/math/issues",
+ "source": "https://github.com/brick/math/tree/0.14.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/BenMorel",
+ "type": "github"
+ }
+ ],
+ "time": "2026-02-10T14:33:43+00:00"
+ },
+ {
+ "name": "carbonphp/carbon-doctrine-types",
+ "version": "3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
+ "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+ "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "conflict": {
+ "doctrine/dbal": "<4.0.0 || >=5.0.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^4.0.0",
+ "nesbot/carbon": "^2.71.0 || ^3.0.0",
+ "phpunit/phpunit": "^10.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "KyleKatarn",
+ "email": "kylekatarnls@gmail.com"
+ }
+ ],
+ "description": "Types to use Carbon in Doctrine",
+ "keywords": [
+ "carbon",
+ "date",
+ "datetime",
+ "doctrine",
+ "time"
+ ],
+ "support": {
+ "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
+ "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/kylekatarnls",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/Carbon",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-02-09T16:56:22+00:00"
+ },
+ {
+ "name": "doctrine/inflector",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/inflector.git",
+ "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b",
+ "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^12.0 || ^13.0",
+ "phpstan/phpstan": "^1.12 || ^2.0",
+ "phpstan/phpstan-phpunit": "^1.4 || ^2.0",
+ "phpstan/phpstan-strict-rules": "^1.6 || ^2.0",
+ "phpunit/phpunit": "^8.5 || ^12.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Inflector\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
+ "homepage": "https://www.doctrine-project.org/projects/inflector.html",
+ "keywords": [
+ "inflection",
+ "inflector",
+ "lowercase",
+ "manipulation",
+ "php",
+ "plural",
+ "singular",
+ "strings",
+ "uppercase",
+ "words"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/inflector/issues",
+ "source": "https://github.com/doctrine/inflector/tree/2.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-10T19:31:58+00:00"
+ },
+ {
+ "name": "fruitcake/php-cors",
+ "version": "v1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fruitcake/php-cors.git",
+ "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379",
+ "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1",
+ "symfony/http-foundation": "^5.4|^6.4|^7.3|^8"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^2",
+ "phpunit/phpunit": "^9",
+ "squizlabs/php_codesniffer": "^4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Fruitcake\\Cors\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fruitcake",
+ "homepage": "https://fruitcake.nl"
+ },
+ {
+ "name": "Barryvdh",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "Cross-origin resource sharing library for the Symfony HttpFoundation",
+ "homepage": "https://github.com/fruitcake/php-cors",
+ "keywords": [
+ "cors",
+ "laravel",
+ "symfony"
+ ],
+ "support": {
+ "issues": "https://github.com/fruitcake/php-cors/issues",
+ "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://fruitcake.nl",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/barryvdh",
+ "type": "github"
+ }
+ ],
+ "time": "2025-12-03T09:33:47+00:00"
+ },
+ {
+ "name": "graham-campbell/result-type",
+ "version": "v1.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/GrahamCampbell/Result-Type.git",
+ "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b",
+ "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "GrahamCampbell\\ResultType\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "An Implementation Of The Result Type",
+ "keywords": [
+ "Graham Campbell",
+ "GrahamCampbell",
+ "Result Type",
+ "Result-Type",
+ "result"
+ ],
+ "support": {
+ "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+ "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-12-27T19:43:20+00:00"
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "7.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
+ "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^2.3",
+ "guzzlehttp/psr7": "^2.8",
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-client": "^1.0",
+ "symfony/deprecation-contracts": "^2.2 || ^3.0"
+ },
+ "provide": {
+ "psr/http-client-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "ext-curl": "*",
+ "guzzle/client-integration-tests": "3.0.2",
+ "php-http/message-factory": "^1.1",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20",
+ "psr/log": "^1.1 || ^2.0 || ^3.0"
+ },
+ "suggest": {
+ "ext-curl": "Required for CURL handler support",
+ "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+ "psr/log": "Required for using the Log middleware"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Jeremy Lindblom",
+ "email": "jeremeamia@gmail.com",
+ "homepage": "https://github.com/jeremeamia"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "psr-18",
+ "psr-7",
+ "rest",
+ "web service"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/guzzle/issues",
+ "source": "https://github.com/guzzle/guzzle/tree/7.10.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-23T22:36:01+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "2.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "481557b130ef3790cf82b713667b43030dc9c957"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957",
+ "reference": "481557b130ef3790cf82b713667b43030dc9c957",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/promises/issues",
+ "source": "https://github.com/guzzle/promises/tree/2.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-22T14:34:08+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "2.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "21dc724a0583619cd1652f673303492272778051"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
+ "reference": "21dc724a0583619cd1652f673303492272778051",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-23T21:21:41+00:00"
+ },
+ {
+ "name": "guzzlehttp/uri-template",
+ "version": "v1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/uri-template.git",
+ "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1",
+ "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "symfony/polyfill-php80": "^1.24"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25",
+ "uri-template/tests": "1.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\UriTemplate\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ }
+ ],
+ "description": "A polyfill class for uri_template of PHP",
+ "keywords": [
+ "guzzlehttp",
+ "uri-template"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/uri-template/issues",
+ "source": "https://github.com/guzzle/uri-template/tree/v1.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-22T14:27:06+00:00"
+ },
+ {
+ "name": "illuminate/bus",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/bus.git",
+ "reference": "c2ceb60c70961815f88da24ab0b7a7f32d47cf4e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/bus/zipball/c2ceb60c70961815f88da24ab0b7a7f32d47cf4e",
+ "reference": "c2ceb60c70961815f88da24ab0b7a7f32d47cf4e",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/collections": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "illuminate/pipeline": "^12.0",
+ "illuminate/support": "^12.0",
+ "php": "^8.2"
+ },
+ "suggest": {
+ "illuminate/queue": "Required to use closures when chaining jobs (^12.0)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Bus\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Bus package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-23T15:43:34+00:00"
+ },
+ {
+ "name": "illuminate/collections",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/collections.git",
+ "reference": "f35c084f0d9bc57895515cb4d0665797c66285fd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/collections/zipball/f35c084f0d9bc57895515cb4d0665797c66285fd",
+ "reference": "f35c084f0d9bc57895515cb4d0665797c66285fd",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/conditionable": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "illuminate/macroable": "^12.0",
+ "php": "^8.2",
+ "symfony/polyfill-php83": "^1.33",
+ "symfony/polyfill-php84": "^1.33",
+ "symfony/polyfill-php85": "^1.33"
+ },
+ "suggest": {
+ "illuminate/http": "Required to convert collections to API resources (^12.0).",
+ "symfony/var-dumper": "Required to use the dump method (^7.2)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "functions.php",
+ "helpers.php"
+ ],
+ "psr-4": {
+ "Illuminate\\Support\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Collections package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-16T14:10:38+00:00"
+ },
+ {
+ "name": "illuminate/conditionable",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/conditionable.git",
+ "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/conditionable/zipball/ec677967c1f2faf90b8428919124d2184a4c9b49",
+ "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Support\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Conditionable package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2025-05-13T15:08:45+00:00"
+ },
+ {
+ "name": "illuminate/container",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/container.git",
+ "reference": "648307e8f54bcd9450c858f99abd11bc50c364a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/container/zipball/648307e8f54bcd9450c858f99abd11bc50c364a0",
+ "reference": "648307e8f54bcd9450c858f99abd11bc50c364a0",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/contracts": "^12.0",
+ "illuminate/reflection": "^12.0",
+ "php": "^8.2",
+ "psr/container": "^1.1.1|^2.0.1",
+ "symfony/polyfill-php84": "^1.33",
+ "symfony/polyfill-php85": "^1.33"
+ },
+ "provide": {
+ "psr/container-implementation": "1.1|2.0"
+ },
+ "suggest": {
+ "illuminate/auth": "Required to use the Auth attribute",
+ "illuminate/cache": "Required to use the Cache attribute",
+ "illuminate/config": "Required to use the Config attribute",
+ "illuminate/database": "Required to use the DB attribute",
+ "illuminate/filesystem": "Required to use the Storage attribute",
+ "illuminate/log": "Required to use the Log or Context attributes"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Container\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Container package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-12T16:13:27+00:00"
+ },
+ {
+ "name": "illuminate/contracts",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/contracts.git",
+ "reference": "099fd9b56ccaf776facaa27699b960a3f2451127"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/contracts/zipball/099fd9b56ccaf776facaa27699b960a3f2451127",
+ "reference": "099fd9b56ccaf776facaa27699b960a3f2451127",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.2",
+ "psr/container": "^1.1.1|^2.0.1",
+ "psr/simple-cache": "^1.0|^2.0|^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Contracts\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Contracts package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-20T14:37:40+00:00"
+ },
+ {
+ "name": "illuminate/database",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/database.git",
+ "reference": "c685208bdd8bfec3e13c7b52f6235b6f3bde9fea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/database/zipball/c685208bdd8bfec3e13c7b52f6235b6f3bde9fea",
+ "reference": "c685208bdd8bfec3e13c7b52f6235b6f3bde9fea",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.11|^0.12|^0.13|^0.14",
+ "ext-pdo": "*",
+ "illuminate/collections": "^12.0",
+ "illuminate/container": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "illuminate/macroable": "^12.0",
+ "illuminate/support": "^12.0",
+ "laravel/serializable-closure": "^1.3|^2.0",
+ "php": "^8.2",
+ "symfony/polyfill-php83": "^1.33",
+ "symfony/polyfill-php85": "^1.33"
+ },
+ "suggest": {
+ "ext-filter": "Required to use the Postgres database driver.",
+ "fakerphp/faker": "Required to use the eloquent factory builder (^1.24).",
+ "illuminate/console": "Required to use the database commands (^12.0).",
+ "illuminate/events": "Required to use the observers with Eloquent (^12.0).",
+ "illuminate/filesystem": "Required to use the migrations (^12.0).",
+ "illuminate/http": "Required to convert Eloquent models to API resources (^12.0).",
+ "illuminate/pagination": "Required to paginate the result set (^12.0).",
+ "symfony/finder": "Required to use Eloquent model factories (^7.2)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Database\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Database package.",
+ "homepage": "https://laravel.com",
+ "keywords": [
+ "database",
+ "laravel",
+ "orm",
+ "sql"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-23T00:20:44+00:00"
+ },
+ {
+ "name": "illuminate/events",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/events.git",
+ "reference": "b71e42451496175f8fd898cb6a67ad7fd613d00b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/events/zipball/b71e42451496175f8fd898cb6a67ad7fd613d00b",
+ "reference": "b71e42451496175f8fd898cb6a67ad7fd613d00b",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/bus": "^12.0",
+ "illuminate/collections": "^12.0",
+ "illuminate/container": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "illuminate/macroable": "^12.0",
+ "illuminate/support": "^12.0",
+ "php": "^8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "functions.php"
+ ],
+ "psr-4": {
+ "Illuminate\\Events\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Events package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-23T15:43:34+00:00"
+ },
+ {
+ "name": "illuminate/filesystem",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/filesystem.git",
+ "reference": "c4c3f8612f218afcf09f3c7f5c7dc9e282626800"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/filesystem/zipball/c4c3f8612f218afcf09f3c7f5c7dc9e282626800",
+ "reference": "c4c3f8612f218afcf09f3c7f5c7dc9e282626800",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/collections": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "illuminate/macroable": "^12.0",
+ "illuminate/support": "^12.0",
+ "php": "^8.2",
+ "symfony/finder": "^7.2.0"
+ },
+ "suggest": {
+ "ext-fileinfo": "Required to use the Filesystem class.",
+ "ext-ftp": "Required to use the Flysystem FTP driver.",
+ "ext-hash": "Required to use the Filesystem class.",
+ "illuminate/http": "Required for handling uploaded files (^12.0).",
+ "league/flysystem": "Required to use the Flysystem local driver (^3.25.1).",
+ "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).",
+ "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).",
+ "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).",
+ "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
+ "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).",
+ "symfony/mime": "Required to enable support for guessing extensions (^7.2)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "functions.php"
+ ],
+ "psr-4": {
+ "Illuminate\\Filesystem\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Filesystem package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-13T20:26:32+00:00"
+ },
+ {
+ "name": "illuminate/http",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/http.git",
+ "reference": "b6351a4b8d3b6b1aef4b08cb98892045b20aa0ad"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/http/zipball/b6351a4b8d3b6b1aef4b08cb98892045b20aa0ad",
+ "reference": "b6351a4b8d3b6b1aef4b08cb98892045b20aa0ad",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "fruitcake/php-cors": "^1.3",
+ "guzzlehttp/guzzle": "^7.8.2",
+ "guzzlehttp/uri-template": "^1.0",
+ "illuminate/collections": "^12.0",
+ "illuminate/macroable": "^12.0",
+ "illuminate/session": "^12.0",
+ "illuminate/support": "^12.0",
+ "php": "^8.2",
+ "symfony/http-foundation": "^7.2.0",
+ "symfony/http-kernel": "^7.2.0",
+ "symfony/mime": "^7.2.0",
+ "symfony/polyfill-php83": "^1.33",
+ "symfony/polyfill-php85": "^1.33"
+ },
+ "suggest": {
+ "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image()."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Http\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Http package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-23T15:41:33+00:00"
+ },
+ {
+ "name": "illuminate/macroable",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/macroable.git",
+ "reference": "e862e5648ee34004fa56046b746f490dfa86c613"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/macroable/zipball/e862e5648ee34004fa56046b746f490dfa86c613",
+ "reference": "e862e5648ee34004fa56046b746f490dfa86c613",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Support\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Macroable package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2024-07-23T16:31:01+00:00"
+ },
+ {
+ "name": "illuminate/pagination",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/pagination.git",
+ "reference": "87e7e3e7b02d6809b1bcd41782e1ca2c6d2a413b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/pagination/zipball/87e7e3e7b02d6809b1bcd41782e1ca2c6d2a413b",
+ "reference": "87e7e3e7b02d6809b1bcd41782e1ca2c6d2a413b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "illuminate/collections": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "illuminate/support": "^12.0",
+ "php": "^8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Pagination\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Pagination package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2025-11-16T14:36:17+00:00"
+ },
+ {
+ "name": "illuminate/pipeline",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/pipeline.git",
+ "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/pipeline/zipball/b6a14c20d69a44bf0a6fba664a00d23ca71770ee",
+ "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/contracts": "^12.0",
+ "illuminate/macroable": "^12.0",
+ "illuminate/support": "^12.0",
+ "php": "^8.2"
+ },
+ "suggest": {
+ "illuminate/database": "Required to use database transactions (^12.0)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Pipeline\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Pipeline package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2025-08-20T13:36:50+00:00"
+ },
+ {
+ "name": "illuminate/reflection",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/reflection.git",
+ "reference": "6188e97a587371b9951c2a7e337cd760308c17d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/reflection/zipball/6188e97a587371b9951c2a7e337cd760308c17d7",
+ "reference": "6188e97a587371b9951c2a7e337cd760308c17d7",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/collections": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "php": "^8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "helpers.php"
+ ],
+ "psr-4": {
+ "Illuminate\\Support\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Reflection package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-04T15:21:22+00:00"
+ },
+ {
+ "name": "illuminate/session",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/session.git",
+ "reference": "98802e67dd5e059c0b978b3fe8f5f0a3ac17ec4e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/session/zipball/98802e67dd5e059c0b978b3fe8f5f0a3ac17ec4e",
+ "reference": "98802e67dd5e059c0b978b3fe8f5f0a3ac17ec4e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-session": "*",
+ "illuminate/collections": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "illuminate/filesystem": "^12.0",
+ "illuminate/support": "^12.0",
+ "php": "^8.2",
+ "symfony/finder": "^7.2.0",
+ "symfony/http-foundation": "^7.2.0"
+ },
+ "suggest": {
+ "illuminate/console": "Required to use the session:table command (^12.0)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Session\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Session package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-14T23:03:41+00:00"
+ },
+ {
+ "name": "illuminate/support",
+ "version": "v12.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/support.git",
+ "reference": "18d7d75366ddb9eded3b7f05173f791da47faf34"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/support/zipball/18d7d75366ddb9eded3b7f05173f791da47faf34",
+ "reference": "18d7d75366ddb9eded3b7f05173f791da47faf34",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/inflector": "^2.0",
+ "ext-ctype": "*",
+ "ext-filter": "*",
+ "ext-mbstring": "*",
+ "illuminate/collections": "^12.0",
+ "illuminate/conditionable": "^12.0",
+ "illuminate/contracts": "^12.0",
+ "illuminate/macroable": "^12.0",
+ "illuminate/reflection": "^12.0",
+ "nesbot/carbon": "^3.8.4",
+ "php": "^8.2",
+ "symfony/polyfill-php83": "^1.33",
+ "symfony/polyfill-php85": "^1.33",
+ "voku/portable-ascii": "^2.0.2"
+ },
+ "conflict": {
+ "tightenco/collect": "<5.5.33"
+ },
+ "replace": {
+ "spatie/once": "*"
+ },
+ "suggest": {
+ "illuminate/filesystem": "Required to use the Composer class (^12.0).",
+ "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).",
+ "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).",
+ "league/uri": "Required to use the Uri class (^7.5.1).",
+ "ramsey/uuid": "Required to use Str::uuid() (^4.7).",
+ "symfony/process": "Required to use the Composer class (^7.2).",
+ "symfony/uid": "Required to use Str::ulid() (^7.2).",
+ "symfony/var-dumper": "Required to use the dd function (^7.2).",
+ "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.6.1)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "functions.php",
+ "helpers.php"
+ ],
+ "psr-4": {
+ "Illuminate\\Support\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Support package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-23T15:44:06+00:00"
+ },
+ {
+ "name": "laravel/serializable-closure",
+ "version": "v2.0.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/serializable-closure.git",
+ "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669",
+ "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "illuminate/support": "^10.0|^11.0|^12.0|^13.0",
+ "nesbot/carbon": "^2.67|^3.0",
+ "pestphp/pest": "^2.36|^3.0|^4.0",
+ "phpstan/phpstan": "^2.0",
+ "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\SerializableClosure\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Nuno Maduro",
+ "email": "nuno@laravel.com"
+ }
+ ],
+ "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
+ "keywords": [
+ "closure",
+ "laravel",
+ "serializable"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/serializable-closure/issues",
+ "source": "https://github.com/laravel/serializable-closure"
+ },
+ "time": "2026-02-20T19:59:49+00:00"
+ },
+ {
+ "name": "monolog/monolog",
+ "version": "2.11.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "37308608e599f34a1a4845b16440047ec98a172a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/37308608e599f34a1a4845b16440047ec98a172a",
+ "reference": "37308608e599f34a1a4845b16440047ec98a172a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2",
+ "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "elasticsearch/elasticsearch": "^7 || ^8",
+ "ext-json": "*",
+ "graylog2/gelf-php": "^1.4.2 || ^2@dev",
+ "guzzlehttp/guzzle": "^7.4",
+ "guzzlehttp/psr7": "^2.2",
+ "mongodb/mongodb": "^1.8 || ^2.0",
+ "php-amqplib/php-amqplib": "~2.4 || ^3",
+ "phpspec/prophecy": "^1.15",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^8.5.38 || ^9.6.19",
+ "predis/predis": "^1.1 || ^2.0",
+ "rollbar/rollbar": "^1.3 || ^2 || ^3",
+ "ruflin/elastica": "^7",
+ "swiftmailer/swiftmailer": "^5.3|^6.0",
+ "symfony/mailer": "^5.4 || ^6",
+ "symfony/mime": "^5.4 || ^6"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+ "ext-mbstring": "Allow to work properly with unicode symbols",
+ "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+ "ext-openssl": "Required to send log messages using SSL",
+ "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Monolog\\": "src/Monolog"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "https://seld.be"
+ }
+ ],
+ "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+ "homepage": "https://github.com/Seldaek/monolog",
+ "keywords": [
+ "log",
+ "logging",
+ "psr-3"
+ ],
+ "support": {
+ "issues": "https://github.com/Seldaek/monolog/issues",
+ "source": "https://github.com/Seldaek/monolog/tree/2.11.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Seldaek",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-01T13:05:00+00:00"
+ },
+ {
+ "name": "nesbot/carbon",
+ "version": "3.11.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CarbonPHP/carbon.git",
+ "reference": "f438fcc98f92babee98381d399c65336f3a3827f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f",
+ "reference": "f438fcc98f92babee98381d399c65336f3a3827f",
+ "shasum": ""
+ },
+ "require": {
+ "carbonphp/carbon-doctrine-types": "<100.0",
+ "ext-json": "*",
+ "php": "^8.1",
+ "psr/clock": "^1.0",
+ "symfony/clock": "^6.3.12 || ^7.0 || ^8.0",
+ "symfony/polyfill-mbstring": "^1.0",
+ "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^3.6.3 || ^4.0",
+ "doctrine/orm": "^2.15.2 || ^3.0",
+ "friendsofphp/php-cs-fixer": "^v3.87.1",
+ "kylekatarnls/multi-tester": "^2.5.3",
+ "phpmd/phpmd": "^2.15.0",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^2.1.22",
+ "phpunit/phpunit": "^10.5.53",
+ "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0"
+ },
+ "bin": [
+ "bin/carbon"
+ ],
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Carbon\\Laravel\\ServiceProvider"
+ ]
+ },
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
+ "branch-alias": {
+ "dev-2.x": "2.x-dev",
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Carbon\\": "src/Carbon/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Brian Nesbitt",
+ "email": "brian@nesbot.com",
+ "homepage": "https://markido.com"
+ },
+ {
+ "name": "kylekatarnls",
+ "homepage": "https://github.com/kylekatarnls"
+ }
+ ],
+ "description": "An API extension for DateTime that supports 281 different languages.",
+ "homepage": "https://carbonphp.github.io/carbon/",
+ "keywords": [
+ "date",
+ "datetime",
+ "time"
+ ],
+ "support": {
+ "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html",
+ "issues": "https://github.com/CarbonPHP/carbon/issues",
+ "source": "https://github.com/CarbonPHP/carbon"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/kylekatarnls",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/Carbon#sponsor",
+ "type": "opencollective"
+ },
+ {
+ "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-29T09:26:29+00:00"
+ },
+ {
+ "name": "nikic/fast-route",
+ "version": "v1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/FastRoute.git",
+ "reference": "181d480e08d9476e61381e04a71b34dc0432e812"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
+ "reference": "181d480e08d9476e61381e04a71b34dc0432e812",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35|~5.7"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "FastRoute\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov",
+ "email": "nikic@php.net"
+ }
+ ],
+ "description": "Fast request router for PHP",
+ "keywords": [
+ "router",
+ "routing"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/FastRoute/issues",
+ "source": "https://github.com/nikic/FastRoute/tree/master"
+ },
+ "time": "2018-02-13T20:26:39+00:00"
+ },
+ {
+ "name": "phpoption/phpoption",
+ "version": "1.9.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/schmittjoh/php-option.git",
+ "reference": "75365b91986c2405cf5e1e012c5595cd487a98be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be",
+ "reference": "75365b91986c2405cf5e1e012c5595cd487a98be",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ },
+ "branch-alias": {
+ "dev-master": "1.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpOption\\": "src/PhpOption/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Johannes M. Schmitt",
+ "email": "schmittjoh@gmail.com",
+ "homepage": "https://github.com/schmittjoh"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "Option Type for PHP",
+ "keywords": [
+ "language",
+ "option",
+ "php",
+ "type"
+ ],
+ "support": {
+ "issues": "https://github.com/schmittjoh/php-option/issues",
+ "source": "https://github.com/schmittjoh/php-option/tree/1.9.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-12-27T19:41:33+00:00"
+ },
+ {
+ "name": "psr/clock",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/clock.git",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Clock\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for reading the clock.",
+ "homepage": "https://github.com/php-fig/clock",
+ "keywords": [
+ "clock",
+ "now",
+ "psr",
+ "psr-20",
+ "time"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/clock/issues",
+ "source": "https://github.com/php-fig/clock/tree/1.0.0"
+ },
+ "time": "2022-11-25T14:36:26+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/event-dispatcher",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/event-dispatcher.git",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\EventDispatcher\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Standard interfaces for event handling.",
+ "keywords": [
+ "events",
+ "psr",
+ "psr-14"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/event-dispatcher/issues",
+ "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+ },
+ "time": "2019-01-08T18:20:26+00:00"
+ },
+ {
+ "name": "psr/http-client",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client"
+ },
+ "time": "2023-09-23T14:17:50+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "time": "2024-04-15T12:06:14+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/2.0"
+ },
+ "time": "2023-04-04T09:54:51+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "time": "2024-09-11T13:17:53+00:00"
+ },
+ {
+ "name": "psr/simple-cache",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/simple-cache.git",
+ "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
+ "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for simple caching",
+ "keywords": [
+ "cache",
+ "caching",
+ "psr",
+ "psr-16",
+ "simple-cache"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
+ },
+ "time": "2021-10-29T13:26:27+00:00"
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "time": "2019-03-08T08:55:37+00:00"
+ },
+ {
+ "name": "symfony/clock",
+ "version": "v8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/clock.git",
+ "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/clock/zipball/832119f9b8dbc6c8e6f65f30c5969eca1e88764f",
+ "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "psr/clock": "^1.0"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/now.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\Clock\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Decouples applications from the system clock",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "clock",
+ "psr20",
+ "time"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/clock/tree/v8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-11-12T15:46:48+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v7.4.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "6d643a93b47398599124022eb24d97c153c12f27"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/6d643a93b47398599124022eb24d97c153c12f27",
+ "reference": "6d643a93b47398599124022eb24d97c153c12f27",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^7.2|^8.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0|^8.0",
+ "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+ "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
+ "symfony/http-foundation": "^6.4|^7.0|^8.0",
+ "symfony/http-kernel": "^6.4|^7.0|^8.0",
+ "symfony/lock": "^6.4|^7.0|^8.0",
+ "symfony/messenger": "^6.4|^7.0|^8.0",
+ "symfony/process": "^6.4|^7.0|^8.0",
+ "symfony/stopwatch": "^6.4|^7.0|^8.0",
+ "symfony/var-dumper": "^6.4|^7.0|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/console/tree/v7.4.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-25T17:02:47+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:21:43+00:00"
+ },
+ {
+ "name": "symfony/error-handler",
+ "version": "v8.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/error-handler.git",
+ "reference": "7620b97ec0ab1d2d6c7fb737aa55da411bea776a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/7620b97ec0ab1d2d6c7fb737aa55da411bea776a",
+ "reference": "7620b97ec0ab1d2d6c7fb737aa55da411bea776a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "psr/log": "^1|^2|^3",
+ "symfony/polyfill-php85": "^1.32",
+ "symfony/var-dumper": "^7.4|^8.0"
+ },
+ "conflict": {
+ "symfony/deprecation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/console": "^7.4|^8.0",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/http-kernel": "^7.4|^8.0",
+ "symfony/serializer": "^7.4|^8.0",
+ "symfony/webpack-encore-bundle": "^1.0|^2.0"
+ },
+ "bin": [
+ "Resources/bin/patch-type-declarations"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ErrorHandler\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides tools to manage errors and ease debugging PHP code",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/error-handler/tree/v8.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-23T11:07:10+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v8.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "99301401da182b6cfaa4700dbe9987bb75474b47"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47",
+ "reference": "99301401da182b6cfaa4700dbe9987bb75474b47",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "symfony/event-dispatcher-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "symfony/security-http": "<7.4",
+ "symfony/service-contracts": "<2.5"
+ },
+ "provide": {
+ "psr/event-dispatcher-implementation": "1.0",
+ "symfony/event-dispatcher-implementation": "2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^7.4|^8.0",
+ "symfony/dependency-injection": "^7.4|^8.0",
+ "symfony/error-handler": "^7.4|^8.0",
+ "symfony/expression-language": "^7.4|^8.0",
+ "symfony/framework-bundle": "^7.4|^8.0",
+ "symfony/http-foundation": "^7.4|^8.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/stopwatch": "^7.4|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-05T11:45:55+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+ "reference": "59eb412e93815df44f05f342958efa9f46b1e586"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586",
+ "reference": "59eb412e93815df44f05f342958efa9f46b1e586",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/event-dispatcher": "^1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\EventDispatcher\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to dispatching event",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:21:43+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v7.4.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf",
+ "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "symfony/filesystem": "^6.4|^7.0|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/finder/tree/v7.4.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-29T09:40:50+00:00"
+ },
+ {
+ "name": "symfony/http-foundation",
+ "version": "v7.4.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-foundation.git",
+ "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/fd97d5e926e988a363cef56fbbf88c5c528e9065",
+ "reference": "fd97d5e926e988a363cef56fbbf88c5c528e9065",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "^1.1"
+ },
+ "conflict": {
+ "doctrine/dbal": "<3.6",
+ "symfony/cache": "<6.4.12|>=7.0,<7.1.5"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^3.6|^4",
+ "predis/predis": "^1.1|^2.0",
+ "symfony/cache": "^6.4.12|^7.1.5|^8.0",
+ "symfony/clock": "^6.4|^7.0|^8.0",
+ "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+ "symfony/expression-language": "^6.4|^7.0|^8.0",
+ "symfony/http-kernel": "^6.4|^7.0|^8.0",
+ "symfony/mime": "^6.4|^7.0|^8.0",
+ "symfony/rate-limiter": "^6.4|^7.0|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpFoundation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Defines an object-oriented layer for the HTTP specification",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/http-foundation/tree/v7.4.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-21T16:25:55+00:00"
+ },
+ {
+ "name": "symfony/http-kernel",
+ "version": "v7.4.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-kernel.git",
+ "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/002ac0cf4cd972a7fd0912dcd513a95e8a81ce83",
+ "reference": "002ac0cf4cd972a7fd0912dcd513a95e8a81ce83",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/log": "^1|^2|^3",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/error-handler": "^6.4|^7.0|^8.0",
+ "symfony/event-dispatcher": "^7.3|^8.0",
+ "symfony/http-foundation": "^7.4|^8.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "symfony/browser-kit": "<6.4",
+ "symfony/cache": "<6.4",
+ "symfony/config": "<6.4",
+ "symfony/console": "<6.4",
+ "symfony/dependency-injection": "<6.4",
+ "symfony/doctrine-bridge": "<6.4",
+ "symfony/flex": "<2.10",
+ "symfony/form": "<6.4",
+ "symfony/http-client": "<6.4",
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/mailer": "<6.4",
+ "symfony/messenger": "<6.4",
+ "symfony/translation": "<6.4",
+ "symfony/translation-contracts": "<2.5",
+ "symfony/twig-bridge": "<6.4",
+ "symfony/validator": "<6.4",
+ "symfony/var-dumper": "<6.4",
+ "twig/twig": "<3.12"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/cache": "^1.0|^2.0|^3.0",
+ "symfony/browser-kit": "^6.4|^7.0|^8.0",
+ "symfony/clock": "^6.4|^7.0|^8.0",
+ "symfony/config": "^6.4|^7.0|^8.0",
+ "symfony/console": "^6.4|^7.0|^8.0",
+ "symfony/css-selector": "^6.4|^7.0|^8.0",
+ "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0",
+ "symfony/dom-crawler": "^6.4|^7.0|^8.0",
+ "symfony/expression-language": "^6.4|^7.0|^8.0",
+ "symfony/finder": "^6.4|^7.0|^8.0",
+ "symfony/http-client-contracts": "^2.5|^3",
+ "symfony/process": "^6.4|^7.0|^8.0",
+ "symfony/property-access": "^7.1|^8.0",
+ "symfony/routing": "^6.4|^7.0|^8.0",
+ "symfony/serializer": "^7.1|^8.0",
+ "symfony/stopwatch": "^6.4|^7.0|^8.0",
+ "symfony/translation": "^6.4|^7.0|^8.0",
+ "symfony/translation-contracts": "^2.5|^3",
+ "symfony/uid": "^6.4|^7.0|^8.0",
+ "symfony/validator": "^6.4|^7.0|^8.0",
+ "symfony/var-dumper": "^6.4|^7.0|^8.0",
+ "symfony/var-exporter": "^6.4|^7.0|^8.0",
+ "twig/twig": "^3.12"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpKernel\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides a structured process for converting a Request into a Response",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/http-kernel/tree/v7.4.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-26T08:30:57+00:00"
+ },
+ {
+ "name": "symfony/mime",
+ "version": "v7.4.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mime.git",
+ "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/9fc881d95feae4c6c48678cb6372bd8a7ba04f5f",
+ "reference": "9fc881d95feae4c6c48678cb6372bd8a7ba04f5f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-intl-idn": "^1.10",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "conflict": {
+ "egulias/email-validator": "~3.0.0",
+ "phpdocumentor/reflection-docblock": "<5.2|>=7",
+ "phpdocumentor/type-resolver": "<1.5.1",
+ "symfony/mailer": "<6.4",
+ "symfony/serializer": "<6.4.3|>7.0,<7.0.3"
+ },
+ "require-dev": {
+ "egulias/email-validator": "^2.1.10|^3.1|^4",
+ "league/html-to-markdown": "^5.0",
+ "phpdocumentor/reflection-docblock": "^5.2|^6.0",
+ "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+ "symfony/process": "^6.4|^7.0|^8.0",
+ "symfony/property-access": "^6.4|^7.0|^8.0",
+ "symfony/property-info": "^6.4|^7.0|^8.0",
+ "symfony/serializer": "^6.4.3|^7.0.3|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mime\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows manipulating MIME messages",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "mime",
+ "mime-type"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/mime/tree/v7.4.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-05T15:57:06+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-06-27T09:58:17+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-idn",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3",
+ "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2",
+ "symfony/polyfill-intl-normalizer": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Trevor Rowbotham",
+ "email": "trevor.rowbotham@pm.me"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "idn",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-10T14:38:51+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "shasum": ""
+ },
+ "require": {
+ "ext-iconv": "*",
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-23T08:48:59+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-01-02T08:10:11+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php83",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php83.git",
+ "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
+ "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php83\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-07-08T02:45:35+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php84",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php84.git",
+ "reference": "d8ced4d875142b6a7426000426b8abc631d6b191"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191",
+ "reference": "d8ced4d875142b6a7426000426b8abc631d6b191",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php84\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-06-24T13:30:11+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php85",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php85.git",
+ "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
+ "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php85\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-06-23T16:12:55+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
+ "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-07-15T11:30:57+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v8.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
+ "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-intl-grapheme": "^1.33",
+ "symfony/polyfill-intl-normalizer": "^1.0",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/emoji": "^7.4|^8.0",
+ "symfony/http-client": "^7.4|^8.0",
+ "symfony/intl": "^7.4|^8.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^7.4|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v8.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-09T10:14:57+00:00"
+ },
+ {
+ "name": "symfony/translation",
+ "version": "v8.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation.git",
+ "reference": "13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b",
+ "reference": "13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "symfony/polyfill-mbstring": "^1.0",
+ "symfony/translation-contracts": "^3.6.1"
+ },
+ "conflict": {
+ "nikic/php-parser": "<5.0",
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/service-contracts": "<2.5"
+ },
+ "provide": {
+ "symfony/translation-implementation": "2.3|3.0"
+ },
+ "require-dev": {
+ "nikic/php-parser": "^5.0",
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^7.4|^8.0",
+ "symfony/console": "^7.4|^8.0",
+ "symfony/dependency-injection": "^7.4|^8.0",
+ "symfony/finder": "^7.4|^8.0",
+ "symfony/http-client-contracts": "^2.5|^3.0",
+ "symfony/http-kernel": "^7.4|^8.0",
+ "symfony/intl": "^7.4|^8.0",
+ "symfony/polyfill-intl-icu": "^1.21",
+ "symfony/routing": "^7.4|^8.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/yaml": "^7.4|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides tools to internationalize your application",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/translation/tree/v8.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-17T13:07:04+00:00"
+ },
+ {
+ "name": "symfony/translation-contracts",
+ "version": "v3.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation-contracts.git",
+ "reference": "65a8bc82080447fae78373aa10f8d13b38338977"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977",
+ "reference": "65a8bc82080447fae78373aa10f8d13b38338977",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to translation",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-07-15T13:41:35+00:00"
+ },
+ {
+ "name": "symfony/var-dumper",
+ "version": "v8.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-dumper.git",
+ "reference": "2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209",
+ "reference": "2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "conflict": {
+ "symfony/console": "<7.4",
+ "symfony/error-handler": "<7.4"
+ },
+ "require-dev": {
+ "symfony/console": "^7.4|^8.0",
+ "symfony/http-kernel": "^7.4|^8.0",
+ "symfony/process": "^7.4|^8.0",
+ "symfony/uid": "^7.4|^8.0",
+ "twig/twig": "^3.12"
+ },
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions/dump.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\VarDumper\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides mechanisms for walking through any arbitrary PHP variable",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "debug",
+ "dump"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/var-dumper/tree/v8.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-15T10:53:29+00:00"
+ },
+ {
+ "name": "topthink/think-container",
+ "version": "v3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/top-think/think-container.git",
+ "reference": "b2df244be1e7399ad4c8be1ccc40ed57868f730a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/top-think/think-container/zipball/b2df244be1e7399ad4c8be1ccc40ed57868f730a",
+ "reference": "b2df244be1e7399ad4c8be1ccc40ed57868f730a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0",
+ "psr/container": "^2.0",
+ "topthink/think-helper": "^3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [],
+ "psr-4": {
+ "think\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "description": "PHP Container & Facade Manager",
+ "support": {
+ "issues": "https://github.com/top-think/think-container/issues",
+ "source": "https://github.com/top-think/think-container/tree/v3.0.2"
+ },
+ "time": "2025-04-07T03:21:51+00:00"
+ },
+ {
+ "name": "topthink/think-helper",
+ "version": "v3.1.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/top-think/think-helper.git",
+ "reference": "fe277121112a8f1c872e169a733ca80bb11c4acb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/top-think/think-helper/zipball/fe277121112a8f1c872e169a733ca80bb11c4acb",
+ "reference": "fe277121112a8f1c872e169a733ca80bb11c4acb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/helper.php"
+ ],
+ "psr-4": {
+ "think\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "description": "The ThinkPHP6 Helper Package",
+ "support": {
+ "issues": "https://github.com/top-think/think-helper/issues",
+ "source": "https://github.com/top-think/think-helper/tree/v3.1.12"
+ },
+ "time": "2025-12-26T09:58:29+00:00"
+ },
+ {
+ "name": "topthink/think-orm",
+ "version": "v4.0.51",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/top-think/think-orm.git",
+ "reference": "46abe2f824eb3bcb117d4c0ce93b203b592b79f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/top-think/think-orm/zipball/46abe2f824eb3bcb117d4c0ce93b203b592b79f7",
+ "reference": "46abe2f824eb3bcb117d4c0ce93b203b592b79f7",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-pdo": "*",
+ "php": ">=8.0.0",
+ "psr/log": ">=1.0",
+ "psr/simple-cache": "^3.0",
+ "topthink/think-helper": "^3.1",
+ "topthink/think-validate": "^3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6|^10"
+ },
+ "suggest": {
+ "ext-mongodb": "provide mongodb support"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/helper.php",
+ "stubs/load_stubs.php"
+ ],
+ "psr-4": {
+ "think\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "description": "the PHP Database&ORM Framework",
+ "keywords": [
+ "database",
+ "orm"
+ ],
+ "support": {
+ "issues": "https://github.com/top-think/think-orm/issues",
+ "source": "https://github.com/top-think/think-orm/tree/v4.0.51"
+ },
+ "time": "2025-12-18T13:11:52+00:00"
+ },
+ {
+ "name": "topthink/think-validate",
+ "version": "v3.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/top-think/think-validate.git",
+ "reference": "85063f6d4ef8ed122f17a36179dc3e0949b30988"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/top-think/think-validate/zipball/85063f6d4ef8ed122f17a36179dc3e0949b30988",
+ "reference": "85063f6d4ef8ed122f17a36179dc3e0949b30988",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0",
+ "topthink/think-container": ">=3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/helper.php"
+ ],
+ "psr-4": {
+ "think\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "description": "think validate",
+ "support": {
+ "issues": "https://github.com/top-think/think-validate/issues",
+ "source": "https://github.com/top-think/think-validate/tree/v3.0.7"
+ },
+ "time": "2025-06-11T05:51:40+00:00"
+ },
+ {
+ "name": "vlucas/phpdotenv",
+ "version": "v5.6.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/vlucas/phpdotenv.git",
+ "reference": "955e7815d677a3eaa7075231212f2110983adecc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc",
+ "reference": "955e7815d677a3eaa7075231212f2110983adecc",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "graham-campbell/result-type": "^1.1.4",
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.5",
+ "symfony/polyfill-ctype": "^1.26",
+ "symfony/polyfill-mbstring": "^1.26",
+ "symfony/polyfill-php80": "^1.26"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "ext-filter": "*",
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+ },
+ "suggest": {
+ "ext-filter": "Required to use the boolean validator."
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ },
+ "branch-alias": {
+ "dev-master": "5.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dotenv\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Vance Lucas",
+ "email": "vance@vancelucas.com",
+ "homepage": "https://github.com/vlucas"
+ }
+ ],
+ "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+ "keywords": [
+ "dotenv",
+ "env",
+ "environment"
+ ],
+ "support": {
+ "issues": "https://github.com/vlucas/phpdotenv/issues",
+ "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-12-27T19:49:13+00:00"
+ },
+ {
+ "name": "voku/portable-ascii",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/voku/portable-ascii.git",
+ "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
+ "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0"
+ },
+ "suggest": {
+ "ext-intl": "Use Intl for transliterator_transliterate() support"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "voku\\": "src/voku/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Lars Moelleken",
+ "homepage": "https://www.moelleken.org/"
+ }
+ ],
+ "description": "Portable ASCII library - performance optimized (ascii) string functions for php.",
+ "homepage": "https://github.com/voku/portable-ascii",
+ "keywords": [
+ "ascii",
+ "clean",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/voku/portable-ascii/issues",
+ "source": "https://github.com/voku/portable-ascii/tree/2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.me/moelleken",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/voku",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/portable-ascii",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://www.patreon.com/voku",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-21T01:49:47+00:00"
+ },
+ {
+ "name": "webman/console",
+ "version": "v2.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webman-php/console.git",
+ "reference": "e66e21c3db1685ac76841df3316d488b25e23d81"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webman-php/console/zipball/e66e21c3db1685ac76841df3316d488b25e23d81",
+ "reference": "e66e21c3db1685ac76841df3316d488b25e23d81",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/inflector": "^2.0",
+ "php": ">=8.1",
+ "symfony/console": "^6.1 || ^7.0"
+ },
+ "require-dev": {
+ "workerman/webman": "^2.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Webman\\Console\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "walkor",
+ "email": "walkor@workerman.net",
+ "homepage": "http://www.workerman.net",
+ "role": "Developer"
+ }
+ ],
+ "description": "Webman console",
+ "homepage": "http://www.workerman.net",
+ "keywords": [
+ "webman console"
+ ],
+ "support": {
+ "email": "walkor@workerman.net",
+ "forum": "http://www.workerman.net/questions",
+ "issues": "https://github.com/webman-php/console/issues",
+ "source": "https://github.com/webman-php/console",
+ "wiki": "http://www.workerman.net/doc/webman"
+ },
+ "time": "2026-02-26T02:45:43+00:00"
+ },
+ {
+ "name": "webman/database",
+ "version": "v2.1.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webman-php/database.git",
+ "reference": "5a1463c96c79b35225f1cbd2f3e65830a7b4da0e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webman-php/database/zipball/5a1463c96c79b35225f1cbd2f3e65830a7b4da0e",
+ "reference": "5a1463c96c79b35225f1cbd2f3e65830a7b4da0e",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/database": "^10.0 || ^11.0 || ^12.0",
+ "illuminate/http": "^10.0 || ^11.0 || ^12.0",
+ "laravel/serializable-closure": "^1.0 || ^2.0",
+ "workerman/webman-framework": "^2.1 || dev-master"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "support\\": "src/support",
+ "Webman\\Database\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Webman database",
+ "support": {
+ "issues": "https://github.com/webman-php/database/issues",
+ "source": "https://github.com/webman-php/database/tree/v2.1.9"
+ },
+ "time": "2026-02-02T10:45:05+00:00"
+ },
+ {
+ "name": "webman/think-orm",
+ "version": "v2.1.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webman-php/think-orm.git",
+ "reference": "b779fce0de4d66e08eb8925eb5841107d1ca9ec4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webman-php/think-orm/zipball/b779fce0de4d66e08eb8925eb5841107d1ca9ec4",
+ "reference": "b779fce0de4d66e08eb8925eb5841107d1ca9ec4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "topthink/think-container": "^2.0|^3.0",
+ "topthink/think-orm": "^2.0.53 || ^3.0.0 || ^4.0.30 || dev-master",
+ "workerman/webman-framework": "^2.1 || dev-master"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "support\\": "src/support",
+ "Webman\\ThinkOrm\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "support": {
+ "issues": "https://github.com/webman-php/think-orm/issues",
+ "source": "https://github.com/webman-php/think-orm/tree/v2.1.10"
+ },
+ "time": "2026-02-04T09:07:18+00:00"
+ },
+ {
+ "name": "workerman/coroutine",
+ "version": "v1.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/workerman-php/coroutine.git",
+ "reference": "b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44",
+ "reference": "b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0",
+ "psr/log": "*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Workerman\\": "src",
+ "Workerman\\Coroutine\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Workerman coroutine",
+ "support": {
+ "issues": "https://github.com/workerman-php/coroutine/issues",
+ "source": "https://github.com/workerman-php/coroutine/tree/v1.1.4"
+ },
+ "time": "2025-10-11T15:09:08+00:00"
+ },
+ {
+ "name": "workerman/webman-framework",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/walkor/webman-framework.git",
+ "reference": "2da4e49259d41925f1732f95f6fb052a3f42ceee"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/walkor/webman-framework/zipball/2da4e49259d41925f1732f95f6fb052a3f42ceee",
+ "reference": "2da4e49259d41925f1732f95f6fb052a3f42ceee",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "nikic/fast-route": "^1.3",
+ "php": ">=8.1",
+ "psr/container": ">=1.0",
+ "psr/log": "^2.0 || ^3.0",
+ "workerman/workerman": "^5.1 || dev-master"
+ },
+ "suggest": {
+ "ext-event": "For better performance. "
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "./src/support/helpers.php"
+ ],
+ "psr-4": {
+ "Webman\\": "./src",
+ "Support\\": "./src/support",
+ "support\\": "./src/support",
+ "Support\\View\\": "./src/support/view",
+ "Support\\Bootstrap\\": "./src/support/bootstrap",
+ "Support\\Exception\\": "./src/support/exception"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "walkor",
+ "email": "walkor@workerman.net",
+ "homepage": "https://www.workerman.net",
+ "role": "Developer"
+ }
+ ],
+ "description": "High performance HTTP Service Framework.",
+ "homepage": "https://www.workerman.net",
+ "keywords": [
+ "High Performance",
+ "http service"
+ ],
+ "support": {
+ "email": "walkor@workerman.net",
+ "forum": "https://wenda.workerman.net/",
+ "issues": "https://github.com/walkor/webman/issues",
+ "source": "https://github.com/walkor/webman-framework",
+ "wiki": "https://doc.workerman.net/"
+ },
+ "time": "2026-02-20T02:37:25+00:00"
+ },
+ {
+ "name": "workerman/workerman",
+ "version": "v5.1.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/walkor/workerman.git",
+ "reference": "fff0954628f8ceeccfe29d3e817f0fad87cfdbf2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/walkor/workerman/zipball/fff0954628f8ceeccfe29d3e817f0fad87cfdbf2",
+ "reference": "fff0954628f8ceeccfe29d3e817f0fad87cfdbf2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": ">=8.1",
+ "workerman/coroutine": "^1.1 || dev-main"
+ },
+ "conflict": {
+ "ext-swow": "=3 <3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-01T08:46:24+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
+ },
+ "time": "2025-12-06T11:56:16+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "13.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a8b58fde2f4fbc69a064e1f80ff917607cf7737c",
+ "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.7.0",
+ "php": ">=8.4",
+ "phpunit/php-file-iterator": "^7.0",
+ "phpunit/php-text-template": "^6.0",
+ "sebastian/complexity": "^6.0",
+ "sebastian/environment": "^9.0",
+ "sebastian/lines-of-code": "^5.0",
+ "sebastian/version": "^7.0",
+ "theseer/tokenizer": "^2.0.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "13.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/13.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T06:05:15+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50",
+ "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:33:26+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88",
+ "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^13.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:34:47+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4",
+ "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:36:37+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "9.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6",
+ "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "9.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:37:53+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "13.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "d57826e8921a534680c613924bfd921ded8047f4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d57826e8921a534680c613924bfd921ded8047f4",
+ "reference": "d57826e8921a534680c613924bfd921ded8047f4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.13.4",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.4.1",
+ "phpunit/php-code-coverage": "^13.0.1",
+ "phpunit/php-file-iterator": "^7.0.0",
+ "phpunit/php-invoker": "^7.0.0",
+ "phpunit/php-text-template": "^6.0.0",
+ "phpunit/php-timer": "^9.0.0",
+ "sebastian/cli-parser": "^5.0.0",
+ "sebastian/comparator": "^8.0.0",
+ "sebastian/diff": "^8.0.0",
+ "sebastian/environment": "^9.0.0",
+ "sebastian/exporter": "^8.0.0",
+ "sebastian/global-state": "^9.0.0",
+ "sebastian/object-enumerator": "^8.0.0",
+ "sebastian/recursion-context": "^8.0.0",
+ "sebastian/type": "^7.0.0",
+ "sebastian/version": "^7.0.0",
+ "staabm/side-effects-detector": "^1.0.5"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "13.0-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/13.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-18T12:40:03+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca",
+ "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:39:44+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/29b232ddc29c2b114c0358c69b3084e7c3da0d58",
+ "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.4",
+ "sebastian/diff": "^8.0",
+ "sebastian/exporter": "^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "suggest": {
+ "ext-bcmath": "For comparing BcMath\\Number objects"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:40:39+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "c5651c795c98093480df79350cb050813fc7a2f3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3",
+ "reference": "c5651c795c98093480df79350cb050813fc7a2f3",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:41:32+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2b6d09d7729ee87d605a439469f9dcc39be5ea3",
+ "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0",
+ "symfony/process": "^7.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/diff",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:42:27+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "9.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "bb64d08145b021b67d5f253308a498b73ab0461e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/bb64d08145b021b67d5f253308a498b73ab0461e",
+ "reference": "bb64d08145b021b67d5f253308a498b73ab0461e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "9.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/9.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/environment",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:43:29+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea",
+ "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.4",
+ "sebastian/recursion-context": "^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:44:28+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "9.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7",
+ "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "sebastian/object-reflector": "^6.0",
+ "sebastian/recursion-context": "^8.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "9.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:45:13+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/4f21bb7768e1c997722ccc7efb1d6b5c11bfd471",
+ "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:45:54+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5",
+ "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "sebastian/object-reflector": "^6.0",
+ "sebastian/recursion-context": "^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:46:36+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200",
+ "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:47:13+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e",
+ "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:51:28+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "42412224607bd3931241bbd17f38e0f972f5a916"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/42412224607bd3931241bbd17f38e0f972f5a916",
+ "reference": "42412224607bd3931241bbd17f38e0f972f5a916",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^13.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:52:09+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b",
+ "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/version",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-02-06T04:52:52+00:00"
+ },
+ {
+ "name": "staabm/side-effects-detector",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/staabm/side-effects-detector.git",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.6",
+ "phpunit/phpunit": "^9.6.21",
+ "symfony/var-dumper": "^5.4.43",
+ "tomasvotruba/type-coverage": "1.0.0",
+ "tomasvotruba/unused-public": "1.0.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "lib/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A static analysis tool to detect side effects in PHP code",
+ "keywords": [
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/staabm/side-effects-detector/issues",
+ "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/staabm",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-20T05:08:20+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4",
+ "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^8.1"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/2.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2025-12-08T11:19:18+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": {},
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=8.1"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/config/app.php b/config/app.php
new file mode 100644
index 0000000..f26e358
--- /dev/null
+++ b/config/app.php
@@ -0,0 +1,26 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\Request;
+
+return [
+ 'debug' => true,
+ 'error_reporting' => E_ALL,
+ 'default_timezone' => 'Asia/Shanghai',
+ 'request_class' => Request::class,
+ 'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public',
+ 'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime',
+ 'controller_suffix' => 'Controller',
+ 'controller_reuse' => false,
+];
diff --git a/config/autoload.php b/config/autoload.php
new file mode 100644
index 0000000..69a8135
--- /dev/null
+++ b/config/autoload.php
@@ -0,0 +1,21 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+ 'files' => [
+ base_path() . '/app/functions.php',
+ base_path() . '/support/Request.php',
+ base_path() . '/support/Response.php',
+ ]
+];
diff --git a/config/bootstrap.php b/config/bootstrap.php
new file mode 100644
index 0000000..5e7047a
--- /dev/null
+++ b/config/bootstrap.php
@@ -0,0 +1,19 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+ support\bootstrap\Session::class,
+ Webman\ThinkOrm\ThinkOrm::class,
+ app\bootstrap\SqlDebug::class,
+];
diff --git a/config/container.php b/config/container.php
new file mode 100644
index 0000000..106b7b4
--- /dev/null
+++ b/config/container.php
@@ -0,0 +1,15 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return new Webman\Container;
\ No newline at end of file
diff --git a/config/database.php b/config/database.php
new file mode 100644
index 0000000..07c0fb5
--- /dev/null
+++ b/config/database.php
@@ -0,0 +1,32 @@
+ 'mysql',
+ 'connections' => [
+ 'mysql' => [
+ 'driver' => 'mysql',
+ 'host' => Config::getStringEnv('DB_HOST', 'localhost'),
+ 'port' => Config::getIntEnv('DB_PORT',3306),
+ 'database' => Config::getStringEnv('DB_NAME', 'opm_ectms'),
+ 'username' => Config::getStringEnv('DB_USER', 'root'),
+ 'password' => Config::getStringEnv('DB_PASSWORD'),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_general_ci',
+ 'prefix' => '',
+ 'strict' => true,
+ 'engine' => null,
+ 'options' => [
+ PDO::ATTR_EMULATE_PREPARES => false, // Must be false for Swoole and Swow drivers.
+ ],
+ 'pool' => [
+ 'max_connections' => 5,
+ 'min_connections' => 1,
+ 'wait_timeout' => 3,
+ 'idle_timeout' => 60,
+ 'heartbeat_interval' => 50,
+ ],
+ ],
+ ],
+];
\ No newline at end of file
diff --git a/config/dependence.php b/config/dependence.php
new file mode 100644
index 0000000..8e964ed
--- /dev/null
+++ b/config/dependence.php
@@ -0,0 +1,15 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [];
\ No newline at end of file
diff --git a/config/exception.php b/config/exception.php
new file mode 100644
index 0000000..f2aede3
--- /dev/null
+++ b/config/exception.php
@@ -0,0 +1,17 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+ '' => support\exception\Handler::class,
+];
\ No newline at end of file
diff --git a/config/log.php b/config/log.php
new file mode 100644
index 0000000..dbc11e2
--- /dev/null
+++ b/config/log.php
@@ -0,0 +1,184 @@
+ [
+ 'handlers' => [
+ // 控制台输出处理器
+ [
+ 'class' => StreamHandler::class,
+ 'constructor' => [
+ 'php://stdout',
+ Config::getInstance()->logLevel,
+ ],
+ 'formatter' => [
+ 'class' => Monolog\Formatter\LineFormatter::class,
+ /**
+ * 格式化日志输出
+ * %start%: 标记日志颜色
+ * %end%: 结束标记颜色
+ * %logger%: 日志记录器名称
+ * %L: 行号
+ * %M: 方法名
+ * %P: 进程ID
+ * %thread%: 线程ID
+ * %Level%: 日志级别(带有占位符)
+ * %level_name%: 日志级别名称
+ * %level%: 日志等级数字
+ * %message%: 日志内容
+ * %datetime%: 时间
+ */
+ 'constructor' => [
+ "%start%%datetime% [%thread%] %Level% %logger%:%L% - %message%%end%\n",
+ 'Y-m-d H:i:s',
+ true
+ ],
+ ],
+ ],
+ // 默认文件输出处理器
+ [
+ 'class' => RotatingFileHandler::class,
+ 'constructor' => [
+ runtime_path() . '/logs/webman.log',
+ Config::getInstance()->logRotationTimeByDay,
+ Config::getInstance()->logLevel,
+ ],
+ 'formatter' => [
+ 'class' => Monolog\Formatter\LineFormatter::class,
+ 'constructor' => [
+ "%datetime% [%thread%] %Level% %logger%:%L% - %message%\n",
+ 'Y-m-d H:i:s',
+ true
+ ],
+ ],
+ ],
+ // Error级别单独文件处理器
+ [
+ 'class' => FilterHandler::class,
+ 'constructor' => [
+ new RotatingFileHandler(
+ runtime_path() . '/logs/error.log',
+ Config::getInstance()->errorLogRotationTimeByDay,
+ Logger::DEBUG
+ ),
+ Logger::ERROR,
+ Logger::EMERGENCY
+ ],
+ 'formatter' => [
+ 'class' => Monolog\Formatter\LineFormatter::class,
+ 'constructor' => [
+ "%datetime% [%thread%] %Level% %logger%:%L% - %message%\n",
+ 'Y-m-d H:i:s',
+ true
+ ],
+ ],
+ ],
+ ],
+ // 全局处理器:手动解析调用栈,精准定位业务代码
+ 'processors' => [
+ // 1. 注入进程ID(生成线程名)
+ new ProcessIdProcessor(),
+ // 2. 手动解析调用栈,获取实际业务代码位置 + 日志过滤
+ function ($record) {
+ $message = $record['message'];
+ // 线程名
+ $processId = $record['extra']['process'] ?? 0;
+ $record['thread'] = "thread-{$processId}";
+
+ // 手动解析调用栈,定位实际业务代码
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); // 获取调用栈(前10层)
+ $file = $record['channel'];
+ $function = '';
+ $line = '0';
+
+ // 遍历调用栈,跳过日志类相关的层,找到业务代码层
+ foreach ($trace as $step) {
+ // 跳过 webman 日志类、monolog 相关的调用层
+ if (isset($step['file'])
+ && str_contains($step['file'], 'support') === false
+ && str_contains($step['file'], 'monolog') === false
+ && str_contains($step['file'], 'Logger') === false
+ ) {
+ // 获取文件路径(简化为项目内相对路径)
+ $file = $step['file'];
+ $projectRoot = base_path();
+ if (str_starts_with($file, $projectRoot)) {
+ $file = substr($file, strlen($projectRoot) + 1);
+ }
+ $file = str_replace('.php', '', $file);
+ $file = str_replace('\\', '.', $file);
+
+ // 获取方法名
+ $function = $step['function'];
+ // 获取行
+ $line = $step['line'] ?? '0';
+ break; // 找到第一个业务代码层就停止
+ }
+ }
+
+ $record['M'] = $function;
+ $record['L'] = $line;
+ $record['P'] = $record['extra']['process_id'];
+
+ // 解析 标签
+ $pattern = '//si';
+ if (preg_match_all($pattern, $message, $matches) && !empty($matches[1])) {
+ $record['logger'] = $matches[1][0];
+ $record['message'] = preg_replace($pattern, '', $message);
+ if ($matches[1][0] == '__default') $record['logger'] = $file;
+ } else {
+ $record['logger'] = $file;
+ $record['message'] = $message;
+ }
+
+ // 解析 SQL_LOG 相关标签
+ $tagPatterns = [
+ 'logger' => '/(.*?)<\/SQL_LOG_F>/s', // 文件路径
+ 'L' => '/(.*?)<\/SQL_LOG_L>/s', // 行号
+ 'M' => '/(.*?)<\/SQL_LOG_M>/s' // 方法名
+ ];
+ $tempMessage = $record['message']; // 基于处理后的message继续解析
+ foreach ($tagPatterns as $key => $pattern) {
+ if (preg_match($pattern, $tempMessage, $matches)) {
+ $record[$key] = $matches[1];
+ $tempMessage = preg_replace($pattern, '', $tempMessage); // 移除当前标签
+ $record['message'] = trim($tempMessage);
+ }
+ }
+
+ // 日志级别颜色映射
+ $levelColorMap = [
+ Logger::EMERGENCY => "\033[41m\033[37m", // 最高级别:红色底色+白色字体
+ Logger::ALERT => "\033[41m\033[37m", // 警报:红色底色+白色字体
+ Logger::CRITICAL => "\033[41m\033[37m", // 严重:红色底色+白色字体
+ Logger::ERROR => "\033[31m", // 错误:红色字体(无底色)
+ Logger::WARNING => "\033[33m", // 警告:深黄色字体
+ Logger::INFO => "\033[32m", // 信息:绿色字体
+ Logger::DEBUG => "\033[34m", // 调试:蓝色字体
+ ];
+ $record['start'] = "\033[0m";
+ $record['end'] = "\033[0m";
+ foreach ($levelColorMap as $level => $color) {
+ if ($record['level'] >= $level) {
+ $record['start'] = $color;
+ $record['end'] = "\033[0m";
+ break;
+ }
+ }
+
+ $record['Level'] = str_pad(strtoupper($record['level_name']), 5);
+ return $record;
+ }
+ ],
+ 'ignore' => [
+ app_path() . '/flow/config/AbstractConfig.php'
+ ],
+ ],
+];
\ No newline at end of file
diff --git a/config/middleware.php b/config/middleware.php
new file mode 100644
index 0000000..8e964ed
--- /dev/null
+++ b/config/middleware.php
@@ -0,0 +1,15 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [];
\ No newline at end of file
diff --git a/config/plugin/webman/console/app.php b/config/plugin/webman/console/app.php
new file mode 100644
index 0000000..d7bf968
--- /dev/null
+++ b/config/plugin/webman/console/app.php
@@ -0,0 +1,28 @@
+ true,
+
+ 'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build',
+
+ 'phar_filename' => 'webman.phar',
+
+ 'phar_format' => Phar::PHAR, // Phar archive format: Phar::PHAR, Phar::TAR, Phar::ZIP
+
+ 'phar_compression' => Phar::NONE, // Compression method for Phar archive: Phar::NONE, Phar::GZ, Phar::BZ2
+
+ 'bin_filename' => 'webman.bin',
+
+ 'signature_algorithm'=> Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL.
+
+ 'private_key_file' => '', // The file path for certificate or OpenSSL private key file.
+
+ 'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#',
+
+ 'exclude_files' => [
+ '.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin'
+ ],
+
+ 'custom_ini' => '
+memory_limit = 256M
+ ',
+];
diff --git a/config/process.php b/config/process.php
new file mode 100644
index 0000000..3b253aa
--- /dev/null
+++ b/config/process.php
@@ -0,0 +1,70 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use app\config\Config;
+use support\Log;
+use support\Request;
+use app\process\Http;
+
+global $argv;
+
+return [
+ 'webman' => [
+ 'handler' => Http::class,
+ 'listen' => 'http://0.0.0.0:8787',
+// 'count' => cpu_count() * 4,
+ 'count' => 1,
+ 'user' => '',
+ 'group' => '',
+ 'reusePort' => false,
+ 'eventLoop' => '',
+ 'context' => [],
+ 'constructor' => [
+ 'requestClass' => Request::class,
+ 'logger' => Log::channel('default'),
+ 'appPath' => app_path(),
+ 'publicPath' => public_path()
+ ]
+ ],
+ // File update detection and automatic reload
+ 'monitor' => [
+ 'handler' => app\process\Monitor::class,
+ 'reloadable' => false,
+ 'constructor' => [
+ // Monitor these directories
+ 'monitorDir' => array_merge([
+ app_path(),
+ config_path(),
+ base_path() . '/process',
+ base_path() . '/support',
+ base_path() . '/resource',
+ base_path() . '/.env',
+ ], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')),
+ // Files with these suffixes will be monitored
+ 'monitorExtensions' => [
+ 'php', 'html', 'htm', 'env'
+ ],
+ 'options' => [
+ 'enable_file_monitor' => !in_array('-d', $argv) && DIRECTORY_SEPARATOR === '/',
+ 'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
+ ]
+ ]
+ ],
+ 'TcpServer' => [
+ 'handler' => app\process\TcpServer::class,
+ 'listen' => 'tcp://0.0.0.0:'. Config::getInstance()->tcpServerPort,
+ 'count' => Config::getInstance()->tcpServerProcessNum,
+ 'reusePort' => true,
+ ],
+];
diff --git a/config/route.php b/config/route.php
new file mode 100644
index 0000000..a5064fc
--- /dev/null
+++ b/config/route.php
@@ -0,0 +1,21 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Webman\Route;
+
+
+
+
+
+
diff --git a/config/server.php b/config/server.php
new file mode 100644
index 0000000..054d01f
--- /dev/null
+++ b/config/server.php
@@ -0,0 +1,23 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+return [
+ 'event_loop' => '',
+ 'stop_timeout' => 2,
+ 'pid_file' => runtime_path() . '/webman.pid',
+ 'status_file' => runtime_path() . '/webman.status',
+ 'stdout_file' => runtime_path() . '/logs/stdout.log',
+ 'log_file' => runtime_path() . '/logs/workerman.log',
+ 'max_package_size' => 10 * 1024 * 1024
+];
diff --git a/config/session.php b/config/session.php
new file mode 100644
index 0000000..043f8c4
--- /dev/null
+++ b/config/session.php
@@ -0,0 +1,65 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Webman\Session\FileSessionHandler;
+use Webman\Session\RedisSessionHandler;
+use Webman\Session\RedisClusterSessionHandler;
+
+return [
+
+ 'type' => 'file', // or redis or redis_cluster
+
+ 'handler' => FileSessionHandler::class,
+
+ 'config' => [
+ 'file' => [
+ 'save_path' => runtime_path() . '/sessions',
+ ],
+ 'redis' => [
+ 'host' => '127.0.0.1',
+ 'port' => 6379,
+ 'auth' => '',
+ 'timeout' => 2,
+ 'database' => '',
+ 'prefix' => 'redis_session_',
+ ],
+ 'redis_cluster' => [
+ 'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'],
+ 'timeout' => 2,
+ 'auth' => '',
+ 'prefix' => 'redis_session_',
+ ]
+ ],
+
+ 'session_name' => 'PHPSID',
+
+ 'auto_update_timestamp' => false,
+
+ 'lifetime' => 7*24*60*60,
+
+ 'cookie_lifetime' => 365*24*60*60,
+
+ 'cookie_path' => '/',
+
+ 'domain' => '',
+
+ 'http_only' => true,
+
+ 'secure' => false,
+
+ 'same_site' => '',
+
+ 'gc_probability' => [1, 1000],
+
+];
diff --git a/config/static.php b/config/static.php
new file mode 100644
index 0000000..6313679
--- /dev/null
+++ b/config/static.php
@@ -0,0 +1,23 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+/**
+ * Static file settings
+ */
+return [
+ 'enable' => true,
+ 'middleware' => [ // Static file Middleware
+ //app\middleware\StaticFile::class,
+ ],
+];
\ No newline at end of file
diff --git a/config/think-orm.php b/config/think-orm.php
new file mode 100644
index 0000000..479ff90
--- /dev/null
+++ b/config/think-orm.php
@@ -0,0 +1,42 @@
+ 'mysql',
+ 'connections' => [
+ 'mysql' => [
+ // 数据库类型
+ 'type' => 'mysql',
+ // 服务器地址
+ 'hostname' => '127.0.0.1',
+ // 数据库名
+ 'database' => 'test',
+ // 数据库用户名
+ 'username' => 'root',
+ // 数据库密码
+ 'password' => '123456',
+ // 数据库连接端口
+ 'hostport' => '3306',
+ // 数据库连接参数
+ 'params' => [
+ // 连接超时3秒
+ \PDO::ATTR_TIMEOUT => 3,
+ ],
+ // 数据库编码默认采用utf8
+ 'charset' => 'utf8',
+ // 数据库表前缀
+ 'prefix' => '',
+ // 断线重连
+ 'break_reconnect' => true,
+ // 连接池配置
+ 'pool' => [
+ 'max_connections' => 5, // 最大连接数
+ 'min_connections' => 1, // 最小连接数
+ 'wait_timeout' => 3, // 从连接池获取连接等待超时时间
+ 'idle_timeout' => 60, // 连接最大空闲时间,超过该时间会被回收
+ 'heartbeat_interval' => 50, // 心跳检测间隔,需要小于60秒
+ ],
+ ],
+ ],
+ // 自定义分页类
+ 'paginator' => '',
+];
diff --git a/config/translation.php b/config/translation.php
new file mode 100644
index 0000000..96589b2
--- /dev/null
+++ b/config/translation.php
@@ -0,0 +1,25 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+/**
+ * Multilingual configuration
+ */
+return [
+ // Default language
+ 'locale' => 'zh_CN',
+ // Fallback language
+ 'fallback_locale' => ['zh_CN', 'en'],
+ // Folder where language files are stored
+ 'path' => base_path() . '/resource/translations',
+];
\ No newline at end of file
diff --git a/config/view.php b/config/view.php
new file mode 100644
index 0000000..e3a7b85
--- /dev/null
+++ b/config/view.php
@@ -0,0 +1,22 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use support\view\Raw;
+use support\view\Twig;
+use support\view\Blade;
+use support\view\ThinkPHP;
+
+return [
+ 'handler' => Raw::class
+];
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..352871e
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,11 @@
+version: "3"
+services:
+ webman:
+ build: .
+ container_name: docker-webman
+ restart: unless-stopped
+ volumes:
+ - "./:/app"
+ ports:
+ - "8787:8787"
+ command: ["php", "start.php", "start" ]
\ No newline at end of file
diff --git a/phpunit-ide.php b/phpunit-ide.php
new file mode 100644
index 0000000..d6ac7fa
--- /dev/null
+++ b/phpunit-ide.php
@@ -0,0 +1,37 @@
+
+
+
+
+ ./tests
+
+
+
+
+ ./app
+
+
+
\ No newline at end of file
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..7e47604
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+ tests/flow
+
+
+
+
+ app/flow
+
+
+
+
+
+
+
+
+
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..b9f722e
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/start.php b/start.php
new file mode 100644
index 0000000..41ad7ef
--- /dev/null
+++ b/start.php
@@ -0,0 +1,5 @@
+#!/usr/bin/env php
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace support;
+
+/**
+ * Class Request
+ * @package support
+ */
+class Request extends \Webman\Http\Request
+{
+
+}
\ No newline at end of file
diff --git a/support/Response.php b/support/Response.php
new file mode 100644
index 0000000..9bc4e1e
--- /dev/null
+++ b/support/Response.php
@@ -0,0 +1,24 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+namespace support;
+
+/**
+ * Class Response
+ * @package support
+ */
+class Response extends \Webman\Http\Response
+{
+
+}
\ No newline at end of file
diff --git a/support/Setup.php b/support/Setup.php
new file mode 100644
index 0000000..99320e8
--- /dev/null
+++ b/support/Setup.php
@@ -0,0 +1,1558 @@
+ \DateTimeZone::ASIA,
+ 'Europe' => \DateTimeZone::EUROPE,
+ 'America' => \DateTimeZone::AMERICA,
+ 'Africa' => \DateTimeZone::AFRICA,
+ 'Australia' => \DateTimeZone::AUSTRALIA,
+ 'Pacific' => \DateTimeZone::PACIFIC,
+ 'Atlantic' => \DateTimeZone::ATLANTIC,
+ 'Indian' => \DateTimeZone::INDIAN,
+ 'Antarctica' => \DateTimeZone::ANTARCTICA,
+ 'Arctic' => \DateTimeZone::ARCTIC,
+ 'UTC' => \DateTimeZone::UTC,
+ ];
+
+ // --- Locale => default timezone ---
+
+ private const LOCALE_DEFAULT_TIMEZONES = [
+ 'zh_CN' => 'Asia/Shanghai',
+ 'zh_TW' => 'Asia/Taipei',
+ 'en' => 'UTC',
+ 'ja' => 'Asia/Tokyo',
+ 'ko' => 'Asia/Seoul',
+ 'fr' => 'Europe/Paris',
+ 'de' => 'Europe/Berlin',
+ 'es' => 'Europe/Madrid',
+ 'pt_BR' => 'America/Sao_Paulo',
+ 'ru' => 'Europe/Moscow',
+ 'vi' => 'Asia/Ho_Chi_Minh',
+ 'tr' => 'Europe/Istanbul',
+ 'id' => 'Asia/Jakarta',
+ 'th' => 'Asia/Bangkok',
+ ];
+
+ // --- Locale options (localized display names) ---
+
+ private const LOCALE_LABELS = [
+ 'zh_CN' => '简体中文',
+ 'zh_TW' => '繁體中文',
+ 'en' => 'English',
+ 'ja' => '日本語',
+ 'ko' => '한국어',
+ 'fr' => 'Français',
+ 'de' => 'Deutsch',
+ 'es' => 'Español',
+ 'pt_BR' => 'Português (Brasil)',
+ 'ru' => 'Русский',
+ 'vi' => 'Tiếng Việt',
+ 'tr' => 'Türkçe',
+ 'id' => 'Bahasa Indonesia',
+ 'th' => 'ไทย',
+ ];
+
+ // --- Multilingual messages (%s = placeholder) ---
+
+ private const MESSAGES = [
+ 'zh_CN' => [
+ 'remove_package_question' => '发现以下已安装组件本次未选择,是否将其卸载 ?%s',
+ 'removing_package' => '- 准备移除组件 %s',
+ 'removing' => '卸载:',
+ 'error_remove' => '卸载组件出错,请手动执行:composer remove %s',
+ 'done_remove' => '已卸载组件。',
+ 'skip' => '非交互模式,跳过安装向导。',
+ 'default_choice' => ' (默认 %s)',
+ 'timezone_prompt' => '时区 (默认 %s,输入可联想补全): ',
+ 'timezone_title' => '时区设置 (默认 %s)',
+ 'timezone_help' => '输入关键字Tab自动补全,可↑↓下选择:',
+ 'timezone_region' => '选择时区区域',
+ 'timezone_city' => '选择时区',
+ 'timezone_invalid' => '无效的时区,已使用默认值 %s',
+ 'timezone_input_prompt' => '输入时区或关键字:',
+ 'timezone_pick_prompt' => '请输入数字编号或关键字:',
+ 'timezone_no_match' => '未找到匹配的时区,请重试。',
+ 'timezone_invalid_index' => '无效的编号,请重新输入。',
+ 'yes' => '是',
+ 'no' => '否',
+ 'adding_package' => '- 添加依赖 %s',
+ 'console_question' => '安装命令行组件 webman/console',
+ 'db_question' => '数据库组件',
+ 'db_none' => '不安装',
+ 'db_invalid' => '请输入有效选项',
+ 'redis_question' => '安装 Redis 组件 webman/redis',
+ 'events_note' => ' (Redis 依赖 illuminate/events,已自动包含)',
+ 'validation_question' => '安装验证器组件 webman/validation',
+ 'template_question' => '模板引擎',
+ 'template_none' => '不安装',
+ 'no_components' => '未选择额外组件。',
+ 'installing' => '即将安装:',
+ 'running' => '执行:',
+ 'error_install' => '安装可选组件时出错,请手动执行:composer require %s',
+ 'done' => '可选组件安装完成。',
+ 'summary_locale' => '语言:%s',
+ 'summary_timezone' => '时区:%s',
+ ],
+ 'zh_TW' => [
+ 'skip' => '非交互模式,跳過安裝嚮導。',
+ 'default_choice' => ' (預設 %s)',
+ 'timezone_prompt' => '時區 (預設 %s,輸入可聯想補全): ',
+ 'timezone_title' => '時區設定 (預設 %s)',
+ 'timezone_help' => '輸入關鍵字Tab自動補全,可↑↓上下選擇:',
+ 'timezone_region' => '選擇時區區域',
+ 'timezone_city' => '選擇時區',
+ 'timezone_invalid' => '無效的時區,已使用預設值 %s',
+ 'timezone_input_prompt' => '輸入時區或關鍵字:',
+ 'timezone_pick_prompt' => '請輸入數字編號或關鍵字:',
+ 'timezone_no_match' => '未找到匹配的時區,請重試。',
+ 'timezone_invalid_index' => '無效的編號,請重新輸入。',
+ 'yes' => '是',
+ 'no' => '否',
+ 'adding_package' => '- 新增依賴 %s',
+ 'console_question' => '安裝命令列組件 webman/console',
+ 'db_question' => '資料庫組件',
+ 'db_none' => '不安裝',
+ 'db_invalid' => '請輸入有效選項',
+ 'redis_question' => '安裝 Redis 組件 webman/redis',
+ 'events_note' => ' (Redis 依賴 illuminate/events,已自動包含)',
+ 'validation_question' => '安裝驗證器組件 webman/validation',
+ 'template_question' => '模板引擎',
+ 'template_none' => '不安裝',
+ 'no_components' => '未選擇額外組件。',
+ 'installing' => '即將安裝:',
+ 'running' => '執行:',
+ 'error_install' => '安裝可選組件時出錯,請手動執行:composer require %s',
+ 'done' => '可選組件安裝完成。',
+ 'summary_locale' => '語言:%s',
+ 'summary_timezone' => '時區:%s',
+ ],
+ 'en' => [
+ 'skip' => 'Non-interactive mode, skipping setup wizard.',
+ 'default_choice' => ' (default %s)',
+ 'timezone_prompt' => 'Timezone (default=%s, type to autocomplete): ',
+ 'timezone_title' => 'Timezone (default=%s)',
+ 'timezone_help' => 'Type keyword then press Tab to autocomplete, use ↑↓ to choose:',
+ 'timezone_region' => 'Select timezone region',
+ 'timezone_city' => 'Select timezone',
+ 'timezone_invalid' => 'Invalid timezone, using default %s',
+ 'timezone_input_prompt' => 'Enter timezone or keyword:',
+ 'timezone_pick_prompt' => 'Enter number or keyword:',
+ 'timezone_no_match' => 'No matching timezone found, please try again.',
+ 'timezone_invalid_index' => 'Invalid number, please try again.',
+ 'yes' => 'yes',
+ 'no' => 'no',
+ 'adding_package' => '- Adding package %s',
+ 'console_question' => 'Install console component webman/console',
+ 'db_question' => 'Database component',
+ 'db_none' => 'None',
+ 'db_invalid' => 'Please enter a valid option',
+ 'redis_question' => 'Install Redis component webman/redis',
+ 'events_note' => ' (Redis requires illuminate/events, automatically included)',
+ 'validation_question' => 'Install validator component webman/validation',
+ 'template_question' => 'Template engine',
+ 'template_none' => 'None',
+ 'no_components' => 'No optional components selected.',
+ 'installing' => 'Installing:',
+ 'running' => 'Running:',
+ 'error_install' => 'Failed to install. Try manually: composer require %s',
+ 'done' => 'Optional components installed.',
+ 'summary_locale' => 'Language: %s',
+ 'summary_timezone' => 'Timezone: %s',
+ ],
+ 'ja' => [
+ 'skip' => '非対話モードのため、セットアップウィザードをスキップします。',
+ 'default_choice' => ' (デフォルト %s)',
+ 'timezone_prompt' => 'タイムゾーン (デフォルト=%s、入力で補完): ',
+ 'timezone_title' => 'タイムゾーン (デフォルト=%s)',
+ 'timezone_help' => 'キーワード入力→Tabで補完、↑↓で選択:',
+ 'timezone_region' => 'タイムゾーンの地域を選択',
+ 'timezone_city' => 'タイムゾーンを選択',
+ 'timezone_invalid' => '無効なタイムゾーンです。デフォルト %s を使用します',
+ 'timezone_input_prompt' => 'タイムゾーンまたはキーワードを入力:',
+ 'timezone_pick_prompt' => '番号またはキーワードを入力:',
+ 'timezone_no_match' => '一致するタイムゾーンが見つかりません。再試行してください。',
+ 'timezone_invalid_index' => '無効な番号です。もう一度入力してください。',
+ 'yes' => 'はい',
+ 'no' => 'いいえ',
+ 'adding_package' => '- パッケージを追加 %s',
+ 'console_question' => 'コンソールコンポーネント webman/console をインストール',
+ 'db_question' => 'データベースコンポーネント',
+ 'db_none' => 'インストールしない',
+ 'db_invalid' => '有効なオプションを入力してください',
+ 'redis_question' => 'Redis コンポーネント webman/redis をインストール',
+ 'events_note' => ' (Redis は illuminate/events が必要です。自動的に含まれます)',
+ 'validation_question' => 'バリデーションコンポーネント webman/validation をインストール',
+ 'template_question' => 'テンプレートエンジン',
+ 'template_none' => 'インストールしない',
+ 'no_components' => 'オプションコンポーネントが選択されていません。',
+ 'installing' => 'インストール中:',
+ 'running' => '実行中:',
+ 'error_install' => 'インストールに失敗しました。手動で実行してください:composer require %s',
+ 'done' => 'オプションコンポーネントのインストールが完了しました。',
+ 'summary_locale' => '言語:%s',
+ 'summary_timezone' => 'タイムゾーン:%s',
+ ],
+ 'ko' => [
+ 'skip' => '비대화형 모드입니다. 설치 마법사를 건너뜁니다.',
+ 'default_choice' => ' (기본값 %s)',
+ 'timezone_prompt' => '시간대 (기본값=%s, 입력하여 자동완성): ',
+ 'timezone_title' => '시간대 (기본값=%s)',
+ 'timezone_help' => '키워드 입력 후 Tab 자동완성, ↑↓로 선택:',
+ 'timezone_region' => '시간대 지역 선택',
+ 'timezone_city' => '시간대 선택',
+ 'timezone_invalid' => '잘못된 시간대입니다. 기본값 %s 을(를) 사용합니다',
+ 'timezone_input_prompt' => '시간대 또는 키워드 입력:',
+ 'timezone_pick_prompt' => '번호 또는 키워드 입력:',
+ 'timezone_no_match' => '일치하는 시간대를 찾을 수 없습니다. 다시 시도하세요.',
+ 'timezone_invalid_index' => '잘못된 번호입니다. 다시 입력하세요.',
+ 'yes' => '예',
+ 'no' => '아니오',
+ 'adding_package' => '- 패키지 추가 %s',
+ 'console_question' => '콘솔 컴포넌트 webman/console 설치',
+ 'db_question' => '데이터베이스 컴포넌트',
+ 'db_none' => '설치 안 함',
+ 'db_invalid' => '유효한 옵션을 입력하세요',
+ 'redis_question' => 'Redis 컴포넌트 webman/redis 설치',
+ 'events_note' => ' (Redis는 illuminate/events가 필요합니다. 자동으로 포함됩니다)',
+ 'validation_question' => '검증 컴포넌트 webman/validation 설치',
+ 'template_question' => '템플릿 엔진',
+ 'template_none' => '설치 안 함',
+ 'no_components' => '선택된 추가 컴포넌트가 없습니다.',
+ 'installing' => '설치 예정:',
+ 'running' => '실행 중:',
+ 'error_install' => '설치에 실패했습니다. 수동으로 실행하세요: composer require %s',
+ 'done' => '선택 컴포넌트 설치가 완료되었습니다.',
+ 'summary_locale' => '언어: %s',
+ 'summary_timezone' => '시간대: %s',
+ ],
+ 'fr' => [
+ 'skip' => 'Mode non interactif, assistant d\'installation ignoré.',
+ 'default_choice' => ' (défaut %s)',
+ 'timezone_prompt' => 'Fuseau horaire (défaut=%s, tapez pour compléter) : ',
+ 'timezone_title' => 'Fuseau horaire (défaut=%s)',
+ 'timezone_help' => 'Tapez un mot-clé, Tab pour compléter, ↑↓ pour choisir :',
+ 'timezone_region' => 'Sélectionnez la région du fuseau horaire',
+ 'timezone_city' => 'Sélectionnez le fuseau horaire',
+ 'timezone_invalid' => 'Fuseau horaire invalide, utilisation de %s par défaut',
+ 'timezone_input_prompt' => 'Entrez un fuseau horaire ou un mot-clé :',
+ 'timezone_pick_prompt' => 'Entrez un numéro ou un mot-clé :',
+ 'timezone_no_match' => 'Aucun fuseau horaire correspondant, veuillez réessayer.',
+ 'timezone_invalid_index' => 'Numéro invalide, veuillez réessayer.',
+ 'yes' => 'oui',
+ 'no' => 'non',
+ 'adding_package' => '- Ajout du paquet %s',
+ 'console_question' => 'Installer le composant console webman/console',
+ 'db_question' => 'Composant base de données',
+ 'db_none' => 'Aucun',
+ 'db_invalid' => 'Veuillez entrer une option valide',
+ 'redis_question' => 'Installer le composant Redis webman/redis',
+ 'events_note' => ' (Redis nécessite illuminate/events, inclus automatiquement)',
+ 'validation_question' => 'Installer le composant de validation webman/validation',
+ 'template_question' => 'Moteur de templates',
+ 'template_none' => 'Aucun',
+ 'no_components' => 'Aucun composant optionnel sélectionné.',
+ 'installing' => 'Installation en cours :',
+ 'running' => 'Exécution :',
+ 'error_install' => 'Échec de l\'installation. Essayez manuellement : composer require %s',
+ 'done' => 'Composants optionnels installés.',
+ 'summary_locale' => 'Langue : %s',
+ 'summary_timezone' => 'Fuseau horaire : %s',
+ ],
+ 'de' => [
+ 'skip' => 'Nicht-interaktiver Modus, Einrichtungsassistent übersprungen.',
+ 'default_choice' => ' (Standard %s)',
+ 'timezone_prompt' => 'Zeitzone (Standard=%s, Eingabe zur Vervollständigung): ',
+ 'timezone_title' => 'Zeitzone (Standard=%s)',
+ 'timezone_help' => 'Stichwort tippen, Tab ergänzt, ↑↓ auswählen:',
+ 'timezone_region' => 'Zeitzone Region auswählen',
+ 'timezone_city' => 'Zeitzone auswählen',
+ 'timezone_invalid' => 'Ungültige Zeitzone, Standardwert %s wird verwendet',
+ 'timezone_input_prompt' => 'Zeitzone oder Stichwort eingeben:',
+ 'timezone_pick_prompt' => 'Nummer oder Stichwort eingeben:',
+ 'timezone_no_match' => 'Keine passende Zeitzone gefunden, bitte erneut versuchen.',
+ 'timezone_invalid_index' => 'Ungültige Nummer, bitte erneut eingeben.',
+ 'yes' => 'ja',
+ 'no' => 'nein',
+ 'adding_package' => '- Paket hinzufügen %s',
+ 'console_question' => 'Konsolen-Komponente webman/console installieren',
+ 'db_question' => 'Datenbank-Komponente',
+ 'db_none' => 'Keine',
+ 'db_invalid' => 'Bitte geben Sie eine gültige Option ein',
+ 'redis_question' => 'Redis-Komponente webman/redis installieren',
+ 'events_note' => ' (Redis benötigt illuminate/events, automatisch eingeschlossen)',
+ 'validation_question' => 'Validierungs-Komponente webman/validation installieren',
+ 'template_question' => 'Template-Engine',
+ 'template_none' => 'Keine',
+ 'no_components' => 'Keine optionalen Komponenten ausgewählt.',
+ 'installing' => 'Installation:',
+ 'running' => 'Ausführung:',
+ 'error_install' => 'Installation fehlgeschlagen. Manuell ausführen: composer require %s',
+ 'done' => 'Optionale Komponenten installiert.',
+ 'summary_locale' => 'Sprache: %s',
+ 'summary_timezone' => 'Zeitzone: %s',
+ ],
+ 'es' => [
+ 'skip' => 'Modo no interactivo, asistente de instalación omitido.',
+ 'default_choice' => ' (predeterminado %s)',
+ 'timezone_prompt' => 'Zona horaria (predeterminado=%s, escriba para autocompletar): ',
+ 'timezone_title' => 'Zona horaria (predeterminado=%s)',
+ 'timezone_help' => 'Escriba una palabra clave, Tab autocompleta, use ↑↓ para elegir:',
+ 'timezone_region' => 'Seleccione la región de zona horaria',
+ 'timezone_city' => 'Seleccione la zona horaria',
+ 'timezone_invalid' => 'Zona horaria inválida, usando valor predeterminado %s',
+ 'timezone_input_prompt' => 'Ingrese zona horaria o palabra clave:',
+ 'timezone_pick_prompt' => 'Ingrese número o palabra clave:',
+ 'timezone_no_match' => 'No se encontró zona horaria coincidente, intente de nuevo.',
+ 'timezone_invalid_index' => 'Número inválido, intente de nuevo.',
+ 'yes' => 'sí',
+ 'no' => 'no',
+ 'adding_package' => '- Agregando paquete %s',
+ 'console_question' => 'Instalar componente de consola webman/console',
+ 'db_question' => 'Componente de base de datos',
+ 'db_none' => 'Ninguno',
+ 'db_invalid' => 'Por favor ingrese una opción válida',
+ 'redis_question' => 'Instalar componente Redis webman/redis',
+ 'events_note' => ' (Redis requiere illuminate/events, incluido automáticamente)',
+ 'validation_question' => 'Instalar componente de validación webman/validation',
+ 'template_question' => 'Motor de plantillas',
+ 'template_none' => 'Ninguno',
+ 'no_components' => 'No se seleccionaron componentes opcionales.',
+ 'installing' => 'Instalando:',
+ 'running' => 'Ejecutando:',
+ 'error_install' => 'Error en la instalación. Intente manualmente: composer require %s',
+ 'done' => 'Componentes opcionales instalados.',
+ 'summary_locale' => 'Idioma: %s',
+ 'summary_timezone' => 'Zona horaria: %s',
+ ],
+ 'pt_BR' => [
+ 'skip' => 'Modo não interativo, assistente de instalação ignorado.',
+ 'default_choice' => ' (padrão %s)',
+ 'timezone_prompt' => 'Fuso horário (padrão=%s, digite para autocompletar): ',
+ 'timezone_title' => 'Fuso horário (padrão=%s)',
+ 'timezone_help' => 'Digite uma palavra-chave, Tab autocompleta, use ↑↓ para escolher:',
+ 'timezone_region' => 'Selecione a região do fuso horário',
+ 'timezone_city' => 'Selecione o fuso horário',
+ 'timezone_invalid' => 'Fuso horário inválido, usando padrão %s',
+ 'timezone_input_prompt' => 'Digite fuso horário ou palavra-chave:',
+ 'timezone_pick_prompt' => 'Digite número ou palavra-chave:',
+ 'timezone_no_match' => 'Nenhum fuso horário encontrado, tente novamente.',
+ 'timezone_invalid_index' => 'Número inválido, tente novamente.',
+ 'yes' => 'sim',
+ 'no' => 'não',
+ 'adding_package' => '- Adicionando pacote %s',
+ 'console_question' => 'Instalar componente de console webman/console',
+ 'db_question' => 'Componente de banco de dados',
+ 'db_none' => 'Nenhum',
+ 'db_invalid' => 'Por favor, digite uma opção válida',
+ 'redis_question' => 'Instalar componente Redis webman/redis',
+ 'events_note' => ' (Redis requer illuminate/events, incluído automaticamente)',
+ 'validation_question' => 'Instalar componente de validação webman/validation',
+ 'template_question' => 'Motor de templates',
+ 'template_none' => 'Nenhum',
+ 'no_components' => 'Nenhum componente opcional selecionado.',
+ 'installing' => 'Instalando:',
+ 'running' => 'Executando:',
+ 'error_install' => 'Falha na instalação. Tente manualmente: composer require %s',
+ 'done' => 'Componentes opcionais instalados.',
+ 'summary_locale' => 'Idioma: %s',
+ 'summary_timezone' => 'Fuso horário: %s',
+ ],
+ 'ru' => [
+ 'skip' => 'Неинтерактивный режим, мастер установки пропущен.',
+ 'default_choice' => ' (по умолчанию %s)',
+ 'timezone_prompt' => 'Часовой пояс (по умолчанию=%s, введите для автодополнения): ',
+ 'timezone_title' => 'Часовой пояс (по умолчанию=%s)',
+ 'timezone_help' => 'Введите ключевое слово, Tab для автодополнения, ↑↓ для выбора:',
+ 'timezone_region' => 'Выберите регион часового пояса',
+ 'timezone_city' => 'Выберите часовой пояс',
+ 'timezone_invalid' => 'Неверный часовой пояс, используется значение по умолчанию %s',
+ 'timezone_input_prompt' => 'Введите часовой пояс или ключевое слово:',
+ 'timezone_pick_prompt' => 'Введите номер или ключевое слово:',
+ 'timezone_no_match' => 'Совпадающий часовой пояс не найден, попробуйте снова.',
+ 'timezone_invalid_index' => 'Неверный номер, попробуйте снова.',
+ 'yes' => 'да',
+ 'no' => 'нет',
+ 'adding_package' => '- Добавление пакета %s',
+ 'console_question' => 'Установить консольный компонент webman/console',
+ 'db_question' => 'Компонент базы данных',
+ 'db_none' => 'Не устанавливать',
+ 'db_invalid' => 'Пожалуйста, введите допустимый вариант',
+ 'redis_question' => 'Установить компонент Redis webman/redis',
+ 'events_note' => ' (Redis требует illuminate/events, автоматически включён)',
+ 'validation_question' => 'Установить компонент валидации webman/validation',
+ 'template_question' => 'Шаблонизатор',
+ 'template_none' => 'Не устанавливать',
+ 'no_components' => 'Дополнительные компоненты не выбраны.',
+ 'installing' => 'Установка:',
+ 'running' => 'Выполнение:',
+ 'error_install' => 'Ошибка установки. Выполните вручную: composer require %s',
+ 'done' => 'Дополнительные компоненты установлены.',
+ 'summary_locale' => 'Язык: %s',
+ 'summary_timezone' => 'Часовой пояс: %s',
+ ],
+ 'vi' => [
+ 'skip' => 'Chế độ không tương tác, bỏ qua trình hướng dẫn cài đặt.',
+ 'default_choice' => ' (mặc định %s)',
+ 'timezone_prompt' => 'Múi giờ (mặc định=%s, nhập để tự động hoàn thành): ',
+ 'timezone_title' => 'Múi giờ (mặc định=%s)',
+ 'timezone_help' => 'Nhập từ khóa, Tab để tự hoàn thành, dùng ↑↓ để chọn:',
+ 'timezone_region' => 'Chọn khu vực múi giờ',
+ 'timezone_city' => 'Chọn múi giờ',
+ 'timezone_invalid' => 'Múi giờ không hợp lệ, sử dụng mặc định %s',
+ 'timezone_input_prompt' => 'Nhập múi giờ hoặc từ khóa:',
+ 'timezone_pick_prompt' => 'Nhập số thứ tự hoặc từ khóa:',
+ 'timezone_no_match' => 'Không tìm thấy múi giờ phù hợp, vui lòng thử lại.',
+ 'timezone_invalid_index' => 'Số không hợp lệ, vui lòng thử lại.',
+ 'yes' => 'có',
+ 'no' => 'không',
+ 'adding_package' => '- Thêm gói %s',
+ 'console_question' => 'Cài đặt thành phần console webman/console',
+ 'db_question' => 'Thành phần cơ sở dữ liệu',
+ 'db_none' => 'Không cài đặt',
+ 'db_invalid' => 'Vui lòng nhập tùy chọn hợp lệ',
+ 'redis_question' => 'Cài đặt thành phần Redis webman/redis',
+ 'events_note' => ' (Redis cần illuminate/events, đã tự động bao gồm)',
+ 'validation_question' => 'Cài đặt thành phần xác thực webman/validation',
+ 'template_question' => 'Công cụ mẫu',
+ 'template_none' => 'Không cài đặt',
+ 'no_components' => 'Không có thành phần tùy chọn nào được chọn.',
+ 'installing' => 'Đang cài đặt:',
+ 'running' => 'Đang thực thi:',
+ 'error_install' => 'Cài đặt thất bại. Thử thủ công: composer require %s',
+ 'done' => 'Các thành phần tùy chọn đã được cài đặt.',
+ 'summary_locale' => 'Ngôn ngữ: %s',
+ 'summary_timezone' => 'Múi giờ: %s',
+ ],
+ 'tr' => [
+ 'skip' => 'Etkileşimsiz mod, kurulum sihirbazı atlanıyor.',
+ 'default_choice' => ' (varsayılan %s)',
+ 'timezone_prompt' => 'Saat dilimi (varsayılan=%s, otomatik tamamlama için yazın): ',
+ 'timezone_title' => 'Saat dilimi (varsayılan=%s)',
+ 'timezone_help' => 'Anahtar kelime yazın, Tab tamamlar, ↑↓ ile seçin:',
+ 'timezone_region' => 'Saat dilimi bölgesini seçin',
+ 'timezone_city' => 'Saat dilimini seçin',
+ 'timezone_invalid' => 'Geçersiz saat dilimi, varsayılan %s kullanılıyor',
+ 'timezone_input_prompt' => 'Saat dilimi veya anahtar kelime girin:',
+ 'timezone_pick_prompt' => 'Numara veya anahtar kelime girin:',
+ 'timezone_no_match' => 'Eşleşen saat dilimi bulunamadı, tekrar deneyin.',
+ 'timezone_invalid_index' => 'Geçersiz numara, tekrar deneyin.',
+ 'yes' => 'evet',
+ 'no' => 'hayır',
+ 'adding_package' => '- Paket ekleniyor %s',
+ 'console_question' => 'Konsol bileşeni webman/console yüklensin mi',
+ 'db_question' => 'Veritabanı bileşeni',
+ 'db_none' => 'Yok',
+ 'db_invalid' => 'Lütfen geçerli bir seçenek girin',
+ 'redis_question' => 'Redis bileşeni webman/redis yüklensin mi',
+ 'events_note' => ' (Redis, illuminate/events gerektirir, otomatik olarak dahil edildi)',
+ 'validation_question' => 'Doğrulama bileşeni webman/validation yüklensin mi',
+ 'template_question' => 'Şablon motoru',
+ 'template_none' => 'Yok',
+ 'no_components' => 'İsteğe bağlı bileşen seçilmedi.',
+ 'installing' => 'Yükleniyor:',
+ 'running' => 'Çalıştırılıyor:',
+ 'error_install' => 'Yükleme başarısız. Manuel olarak deneyin: composer require %s',
+ 'done' => 'İsteğe bağlı bileşenler yüklendi.',
+ 'summary_locale' => 'Dil: %s',
+ 'summary_timezone' => 'Saat dilimi: %s',
+ ],
+ 'id' => [
+ 'skip' => 'Mode non-interaktif, melewati wizard instalasi.',
+ 'default_choice' => ' (default %s)',
+ 'timezone_prompt' => 'Zona waktu (default=%s, ketik untuk melengkapi): ',
+ 'timezone_title' => 'Zona waktu (default=%s)',
+ 'timezone_help' => 'Ketik kata kunci, Tab untuk melengkapi, gunakan ↑↓ untuk memilih:',
+ 'timezone_region' => 'Pilih wilayah zona waktu',
+ 'timezone_city' => 'Pilih zona waktu',
+ 'timezone_invalid' => 'Zona waktu tidak valid, menggunakan default %s',
+ 'timezone_input_prompt' => 'Masukkan zona waktu atau kata kunci:',
+ 'timezone_pick_prompt' => 'Masukkan nomor atau kata kunci:',
+ 'timezone_no_match' => 'Zona waktu tidak ditemukan, silakan coba lagi.',
+ 'timezone_invalid_index' => 'Nomor tidak valid, silakan coba lagi.',
+ 'yes' => 'ya',
+ 'no' => 'tidak',
+ 'adding_package' => '- Menambahkan paket %s',
+ 'console_question' => 'Instal komponen konsol webman/console',
+ 'db_question' => 'Komponen database',
+ 'db_none' => 'Tidak ada',
+ 'db_invalid' => 'Silakan masukkan opsi yang valid',
+ 'redis_question' => 'Instal komponen Redis webman/redis',
+ 'events_note' => ' (Redis memerlukan illuminate/events, otomatis disertakan)',
+ 'validation_question' => 'Instal komponen validasi webman/validation',
+ 'template_question' => 'Mesin template',
+ 'template_none' => 'Tidak ada',
+ 'no_components' => 'Tidak ada komponen opsional yang dipilih.',
+ 'installing' => 'Menginstal:',
+ 'running' => 'Menjalankan:',
+ 'error_install' => 'Instalasi gagal. Coba manual: composer require %s',
+ 'done' => 'Komponen opsional terinstal.',
+ 'summary_locale' => 'Bahasa: %s',
+ 'summary_timezone' => 'Zona waktu: %s',
+ ],
+ 'th' => [
+ 'skip' => 'โหมดไม่โต้ตอบ ข้ามตัวช่วยติดตั้ง',
+ 'default_choice' => ' (ค่าเริ่มต้น %s)',
+ 'timezone_prompt' => 'เขตเวลา (ค่าเริ่มต้น=%s พิมพ์เพื่อเติมอัตโนมัติ): ',
+ 'timezone_title' => 'เขตเวลา (ค่าเริ่มต้น=%s)',
+ 'timezone_help' => 'พิมพ์คีย์เวิร์ดแล้วกด Tab เพื่อเติมอัตโนมัติ ใช้ ↑↓ เพื่อเลือก:',
+ 'timezone_region' => 'เลือกภูมิภาคเขตเวลา',
+ 'timezone_city' => 'เลือกเขตเวลา',
+ 'timezone_invalid' => 'เขตเวลาไม่ถูกต้อง ใช้ค่าเริ่มต้น %s',
+ 'timezone_input_prompt' => 'ป้อนเขตเวลาหรือคำค้น:',
+ 'timezone_pick_prompt' => 'ป้อนหมายเลขหรือคำค้น:',
+ 'timezone_no_match' => 'ไม่พบเขตเวลาที่ตรงกัน กรุณาลองอีกครั้ง',
+ 'timezone_invalid_index' => 'หมายเลขไม่ถูกต้อง กรุณาลองอีกครั้ง',
+ 'yes' => 'ใช่',
+ 'no' => 'ไม่',
+ 'adding_package' => '- เพิ่มแพ็กเกจ %s',
+ 'console_question' => 'ติดตั้งคอมโพเนนต์คอนโซล webman/console',
+ 'db_question' => 'คอมโพเนนต์ฐานข้อมูล',
+ 'db_none' => 'ไม่ติดตั้ง',
+ 'db_invalid' => 'กรุณาป้อนตัวเลือกที่ถูกต้อง',
+ 'redis_question' => 'ติดตั้งคอมโพเนนต์ Redis webman/redis',
+ 'events_note' => ' (Redis ต้องการ illuminate/events รวมไว้โดยอัตโนมัติ)',
+ 'validation_question' => 'ติดตั้งคอมโพเนนต์ตรวจสอบ webman/validation',
+ 'template_question' => 'เทมเพลตเอนจิน',
+ 'template_none' => 'ไม่ติดตั้ง',
+ 'no_components' => 'ไม่ได้เลือกคอมโพเนนต์เสริม',
+ 'installing' => 'กำลังติดตั้ง:',
+ 'running' => 'กำลังดำเนินการ:',
+ 'error_install' => 'ติดตั้งล้มเหลว ลองด้วยตนเอง: composer require %s',
+ 'done' => 'คอมโพเนนต์เสริมติดตั้งเรียบร้อยแล้ว',
+ 'summary_locale' => 'ภาษา: %s',
+ 'summary_timezone' => 'เขตเวลา: %s',
+ ],
+ ];
+
+ // --- Interrupt message (Ctrl+C) ---
+
+ private const INTERRUPTED_MESSAGES = [
+ 'zh_CN' => '安装中断,可运行 composer setup-webman 可重新设置。',
+ 'zh_TW' => '安裝中斷,可運行 composer setup-webman 重新設置。',
+ 'en' => 'Setup interrupted. Run "composer setup-webman" to restart setup.',
+ 'ja' => 'セットアップが中断されました。composer setup-webman を実行して再設定できます。',
+ 'ko' => '설치가 중단되었습니다. composer setup-webman 을 실행하여 다시 설정할 수 있습니다.',
+ 'fr' => 'Installation interrompue. Exécutez « composer setup-webman » pour recommencer.',
+ 'de' => 'Einrichtung abgebrochen. Führen Sie "composer setup-webman" aus, um neu zu starten.',
+ 'es' => 'Instalación interrumpida. Ejecute "composer setup-webman" para reiniciar.',
+ 'pt_BR' => 'Instalação interrompida. Execute "composer setup-webman" para reiniciar.',
+ 'ru' => 'Установка прервана. Выполните «composer setup-webman» для повторной настройки.',
+ 'vi' => 'Cài đặt bị gián đoạn. Chạy "composer setup-webman" để cài đặt lại.',
+ 'tr' => 'Kurulum kesildi. Yeniden kurmak için "composer setup-webman" komutunu çalıştırın.',
+ 'id' => 'Instalasi terganggu. Jalankan "composer setup-webman" untuk mengatur ulang.',
+ 'th' => 'การติดตั้งถูกขัดจังหวะ เรียกใช้ "composer setup-webman" เพื่อตั้งค่าใหม่',
+ ];
+
+ // --- Signal handling state ---
+
+ /** @var string|null Saved stty mode for terminal restoration on interrupt */
+ private static ?string $sttyMode = null;
+
+ /** @var string Current locale for interrupt message */
+ private static string $interruptLocale = 'en';
+
+ // ═══════════════════════════════════════════════════════════════
+ // Entry
+ // ═══════════════════════════════════════════════════════════════
+
+ public static function run(Event $event): void
+ {
+ $io = $event->getIO();
+
+ // Non-interactive mode: use English for skip message
+ if (!$io->isInteractive()) {
+ $io->write('' . self::MESSAGES['en']['skip'] . '');
+ return;
+ }
+
+ try {
+ self::doRun($event, $io);
+ } catch (\Throwable $e) {
+ $io->writeError('');
+ $io->writeError('Setup wizard error: ' . $e->getMessage() . '');
+ $io->writeError('Run "composer setup-webman" to retry.');
+ }
+ }
+
+ private static function doRun(Event $event, IOInterface $io): void
+ {
+ $io->write('');
+
+ // Register Ctrl+C handler
+ self::registerInterruptHandler();
+
+ // Banner title (must be before locale selection)
+ self::renderTitle();
+
+ // 1. Locale selection
+ $locale = self::askLocale($io);
+ self::$interruptLocale = $locale;
+ $defaultTimezone = self::LOCALE_DEFAULT_TIMEZONES[$locale] ?? 'UTC';
+ $msg = fn(string $key, string ...$args): string =>
+ empty($args) ? self::MESSAGES[$locale][$key] : sprintf(self::MESSAGES[$locale][$key], ...$args);
+
+ // Write locale config (update when not default)
+ if ($locale !== 'zh_CN') {
+ self::updateConfig($event, 'config/translation.php', "'locale'", $locale);
+ }
+
+ $io->write('');
+ $io->write('');
+
+ // 2. Timezone selection (default by locale)
+ $timezone = self::askTimezone($io, $msg, $defaultTimezone);
+ if ($timezone !== 'Asia/Shanghai') {
+ self::updateConfig($event, 'config/app.php', "'default_timezone'", $timezone);
+ }
+
+ // 3. Optional components
+ $packages = self::askComponents($io, $msg);
+
+ // 4. Remove unselected components
+ $removePackages = self::askRemoveComponents($event, $packages, $io, $msg);
+
+ // 5. Summary
+ $io->write('');
+ $io->write('─────────────────────────────────────');
+ $io->write('' . $msg('summary_locale', self::LOCALE_LABELS[$locale]) . '');
+ $io->write('' . $msg('summary_timezone', $timezone) . '');
+
+ // Remove unselected packages first to avoid dependency conflicts
+ if ($removePackages !== []) {
+ $io->write('');
+ $io->write('' . $msg('removing') . '');
+
+ $secondaryPackages = [
+ self::PACKAGE_ILLUMINATE_EVENTS,
+ self::PACKAGE_ILLUMINATE_PAGINATION,
+ self::PACKAGE_SYMFONY_VAR_DUMPER,
+ ];
+ $displayRemovePackages = array_diff($removePackages, $secondaryPackages);
+ foreach ($displayRemovePackages as $pkg) {
+ $io->write(' - ' . $pkg);
+ }
+ $io->write('');
+ self::runComposerRemove($removePackages, $io, $msg);
+ }
+
+ // Then install selected packages
+ if ($packages !== []) {
+ $io->write('');
+ $io->write('' . $msg('installing') . ' ' . implode(', ', $packages));
+ $io->write('');
+ self::runComposerRequire($packages, $io, $msg);
+ } elseif ($removePackages === []) {
+ $io->write('' . $msg('no_components') . '');
+ }
+ }
+
+ private static function renderTitle(): void
+ {
+ $output = new ConsoleOutput();
+ $terminalWidth = (new Terminal())->getWidth();
+ if ($terminalWidth <= 0) {
+ $terminalWidth = 80;
+ }
+
+ $text = ' ' . self::SETUP_TITLE . ' ';
+ $minBoxWidth = 44;
+ $maxBoxWidth = min($terminalWidth, 96);
+ $boxWidth = min($maxBoxWidth, max($minBoxWidth, mb_strwidth($text) + 10));
+
+ $innerWidth = $boxWidth - 2;
+ $textWidth = mb_strwidth($text);
+ $pad = max(0, $innerWidth - $textWidth);
+ $left = intdiv($pad, 2);
+ $right = $pad - $left;
+ $line2 = '│' . str_repeat(' ', $left) . $text . str_repeat(' ', $right) . '│';
+ $line1 = '┌' . str_repeat('─', $innerWidth) . '┐';
+ $line3 = '└' . str_repeat('─', $innerWidth) . '┘';
+
+ $output->writeln('');
+ $output->writeln('' . $line1 . '>');
+ $output->writeln('' . $line2 . '>');
+ $output->writeln('' . $line3 . '>');
+ $output->writeln('');
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // Signal handling (Ctrl+C)
+ // ═══════════════════════════════════════════════════════════════
+
+ /**
+ * Register Ctrl+C (SIGINT) handler to show a friendly message on interrupt.
+ * Gracefully skipped when the required extensions are unavailable.
+ */
+ private static function registerInterruptHandler(): void
+ {
+ // Unix/Linux/Mac: pcntl extension with async signals for immediate delivery
+ /*if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
+ pcntl_async_signals(true);
+ pcntl_signal(\SIGINT, [self::class, 'handleInterrupt']);
+ return;
+ }*/
+
+ // Windows: sapi ctrl handler (PHP >= 7.4)
+ if (function_exists('sapi_windows_set_ctrl_handler')) {
+ sapi_windows_set_ctrl_handler(static function (int $event) {
+ if ($event === \PHP_WINDOWS_EVENT_CTRL_C) {
+ self::handleInterrupt();
+ }
+ });
+ }
+ }
+
+ /**
+ * Handle Ctrl+C: restore terminal, show tip, then exit.
+ */
+ private static function handleInterrupt(): void
+ {
+ // Restore terminal if in raw mode
+ if (self::$sttyMode !== null && function_exists('shell_exec')) {
+ @shell_exec('stty ' . self::$sttyMode);
+ self::$sttyMode = null;
+ }
+
+ $output = new ConsoleOutput();
+ $output->writeln('');
+ $output->writeln('' . (self::INTERRUPTED_MESSAGES[self::$interruptLocale] ?? self::INTERRUPTED_MESSAGES['en']) . '');
+ exit(1);
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // Interactive Menu System
+ // ═══════════════════════════════════════════════════════════════
+
+ /**
+ * Check if terminal supports interactive features (arrow keys, ANSI colors).
+ */
+ private static function supportsInteractive(): bool
+ {
+ return function_exists('shell_exec') && Terminal::hasSttyAvailable();
+ }
+
+ /**
+ * Display a selection menu with arrow key navigation (if supported) or text input fallback.
+ *
+ * @param IOInterface $io Composer IO
+ * @param string $title Menu title
+ * @param array $items Indexed array of ['tag' => string, 'label' => string]
+ * @param int $default Default selected index (0-based)
+ * @return int Selected index
+ */
+ private static function selectMenu(IOInterface $io, string $title, array $items, int $default = 0): int
+ {
+ // Append localized "default" hint to avoid ambiguity
+ // (Template should contain a single %s placeholder for the default tag.)
+ $defaultHintTemplate = null;
+ if (isset(self::MESSAGES[self::$interruptLocale]['default_choice'])) {
+ $defaultHintTemplate = self::MESSAGES[self::$interruptLocale]['default_choice'];
+ }
+
+ $defaultTag = $items[$default]['tag'] ?? '';
+ if ($defaultHintTemplate && $defaultTag !== '') {
+ $title .= sprintf($defaultHintTemplate, $defaultTag);
+ } elseif ($defaultTag !== '') {
+ // Fallback for early menus (e.g. locale selection) before locale is chosen.
+ $title .= sprintf(' (default %s)', $defaultTag);
+ }
+
+ if (self::supportsInteractive()) {
+ return self::arrowKeySelect($title, $items, $default);
+ }
+
+ return self::fallbackSelect($io, $title, $items, $default);
+ }
+
+ /**
+ * Display a yes/no confirmation as a selection menu.
+ *
+ * @param IOInterface $io Composer IO
+ * @param string $title Menu title
+ * @param bool $default Default value (true = yes)
+ * @return bool User's choice
+ */
+ private static function confirmMenu(IOInterface $io, string $title, bool $default = true): bool
+ {
+ $locale = self::$interruptLocale;
+ $yes = self::MESSAGES[$locale]['yes'] ?? self::MESSAGES['en']['yes'] ?? 'yes';
+ $no = self::MESSAGES[$locale]['no'] ?? self::MESSAGES['en']['no'] ?? 'no';
+ $items = $default
+ ? [['tag' => 'Y', 'label' => $yes], ['tag' => 'n', 'label' => $no]]
+ : [['tag' => 'y', 'label' => $yes], ['tag' => 'N', 'label' => $no]];
+ $defaultIndex = $default ? 0 : 1;
+
+ return self::selectMenu($io, $title, $items, $defaultIndex) === 0;
+ }
+
+ /**
+ * Interactive select with arrow key navigation, manual input and ANSI reverse-video highlighting.
+ * Input area and option list highlighting are bidirectionally linked.
+ * Requires stty (Unix-like terminals).
+ */
+ private static function arrowKeySelect(string $title, array $items, int $default): int
+ {
+ $output = new ConsoleOutput();
+ $count = count($items);
+ $selected = $default;
+
+ $maxTagWidth = max(array_map(fn(array $item) => mb_strlen($item['tag']), $items));
+ $defaultTag = $items[$default]['tag'];
+ $input = $defaultTag;
+
+ // Print title and initial options
+ $output->writeln('');
+ $output->writeln('' . $title . '>');
+ self::drawMenuItems($output, $items, $selected, $maxTagWidth);
+ $output->write('> ' . $input);
+
+ // Enter raw mode
+ self::$sttyMode = shell_exec('stty -g');
+ shell_exec('stty -icanon -echo');
+
+ try {
+ while (!feof(STDIN)) {
+ $c = fread(STDIN, 1);
+
+ if (false === $c || '' === $c) {
+ break;
+ }
+
+ // ── Backspace ──
+ if ("\177" === $c || "\010" === $c) {
+ if ('' !== $input) {
+ $input = mb_substr($input, 0, -1);
+ }
+ $selected = self::findItemByTag($items, $input);
+ $output->write("\033[{$count}A");
+ self::drawMenuItems($output, $items, $selected, $maxTagWidth);
+ $output->write("\033[2K\r> " . $input);
+ continue;
+ }
+
+ // ── Escape sequences (arrow keys) ──
+ if ("\033" === $c) {
+ $seq = fread(STDIN, 2);
+ if (isset($seq[1])) {
+ $changed = false;
+ if ('A' === $seq[1]) { // Up
+ $selected = ($selected <= 0 ? $count : $selected) - 1;
+ $changed = true;
+ } elseif ('B' === $seq[1]) { // Down
+ $selected = ($selected + 1) % $count;
+ $changed = true;
+ }
+ if ($changed) {
+ // Sync input with selected item's tag
+ $input = $items[$selected]['tag'];
+ $output->write("\033[{$count}A");
+ self::drawMenuItems($output, $items, $selected, $maxTagWidth);
+ $output->write("\033[2K\r> " . $input);
+ }
+ }
+ continue;
+ }
+
+ // ── Enter: confirm selection ──
+ if ("\n" === $c || "\r" === $c) {
+ if ($selected < 0) {
+ $selected = $default;
+ }
+ $output->write("\033[2K\r> " . $items[$selected]['tag'] . ' ' . $items[$selected]['label'] . '');
+ $output->writeln('');
+ break;
+ }
+
+ // ── Ignore other control characters ──
+ if (ord($c) < 32) {
+ continue;
+ }
+
+ // ── Printable character (with UTF-8 multi-byte support) ──
+ if ("\x80" <= $c) {
+ $extra = ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3];
+ $c .= fread(STDIN, $extra[$c & "\xF0"] ?? 0);
+ }
+ $input .= $c;
+ $selected = self::findItemByTag($items, $input);
+ $output->write("\033[{$count}A");
+ self::drawMenuItems($output, $items, $selected, $maxTagWidth);
+ $output->write("\033[2K\r> " . $input);
+ }
+ } finally {
+ if (self::$sttyMode !== null) {
+ shell_exec('stty ' . self::$sttyMode);
+ self::$sttyMode = null;
+ }
+ }
+
+ return $selected < 0 ? $default : $selected;
+ }
+
+ /**
+ * Fallback select for terminals without stty support. Uses plain text input.
+ */
+ private static function fallbackSelect(IOInterface $io, string $title, array $items, int $default): int
+ {
+ $maxTagWidth = max(array_map(fn(array $item) => mb_strlen($item['tag']), $items));
+ $defaultTag = $items[$default]['tag'];
+
+ $io->write('');
+ $io->write('' . $title . '>');
+ foreach ($items as $item) {
+ $tag = str_pad($item['tag'], $maxTagWidth);
+ $io->write(" [$tag] " . $item['label']);
+ }
+
+ while (true) {
+ $io->write('> ', false);
+ $line = fgets(STDIN);
+ if ($line === false) {
+ return $default;
+ }
+ $answer = trim($line);
+
+ if ($answer === '') {
+ $io->write('> ' . $items[$default]['tag'] . ' ' . $items[$default]['label'] . '');
+ return $default;
+ }
+
+ // Match by tag (case-insensitive)
+ foreach ($items as $i => $item) {
+ if (strcasecmp($item['tag'], $answer) === 0) {
+ $io->write('> ' . $items[$i]['tag'] . ' ' . $items[$i]['label'] . '');
+ return $i;
+ }
+ }
+ }
+ }
+
+ /**
+ * Render menu items with optional ANSI reverse-video highlighting for the selected item.
+ * When $selected is -1, no item is highlighted.
+ */
+ private static function drawMenuItems(ConsoleOutput $output, array $items, int $selected, int $maxTagWidth): void
+ {
+ foreach ($items as $i => $item) {
+ $tag = str_pad($item['tag'], $maxTagWidth);
+ $line = " [$tag] " . $item['label'];
+ if ($i === $selected) {
+ $output->writeln("\033[2K\r\033[7m" . $line . "\033[0m");
+ } else {
+ $output->writeln("\033[2K\r" . $line);
+ }
+ }
+ }
+
+ /**
+ * Find item index by tag (case-insensitive exact match).
+ * Returns -1 if no match found or input is empty.
+ */
+ private static function findItemByTag(array $items, string $input): int
+ {
+ if ($input === '') {
+ return -1;
+ }
+ foreach ($items as $i => $item) {
+ if (strcasecmp($item['tag'], $input) === 0) {
+ return $i;
+ }
+ }
+ return -1;
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // Locale selection
+ // ═══════════════════════════════════════════════════════════════
+
+ private static function askLocale(IOInterface $io): string
+ {
+ $locales = array_keys(self::LOCALE_LABELS);
+ $items = [];
+ foreach ($locales as $i => $code) {
+ $items[] = ['tag' => (string) $i, 'label' => self::LOCALE_LABELS[$code] . " ($code)"];
+ }
+
+ $selected = self::selectMenu(
+ $io,
+ '语言 / Language / 言語 / 언어',
+ $items,
+ 0
+ );
+
+ return $locales[$selected];
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // Timezone selection
+ // ═══════════════════════════════════════════════════════════════
+
+ private static function askTimezone(IOInterface $io, callable $msg, string $default): string
+ {
+ if (self::supportsInteractive()) {
+ return self::askTimezoneAutocomplete($msg, $default);
+ }
+
+ return self::askTimezoneSelect($io, $msg, $default);
+ }
+
+ /**
+ * Option A: when stty is available, custom character-by-character autocomplete
+ * (case-insensitive, substring match). Interaction: type to filter, hint on right;
+ * ↑↓ change candidate, Tab accept, Enter confirm; empty input = use default.
+ */
+ private static function askTimezoneAutocomplete(callable $msg, string $default): string
+ {
+ $allTimezones = \DateTimeZone::listIdentifiers();
+ $output = new ConsoleOutput();
+ $cursor = new Cursor($output);
+
+ $output->writeln('');
+ $output->writeln('' . $msg('timezone_title', $default) . '>');
+ $output->writeln($msg('timezone_help'));
+ $output->write('> ');
+
+ self::$sttyMode = shell_exec('stty -g');
+ shell_exec('stty -icanon -echo');
+
+ // Auto-fill default timezone in the input area; user can edit it directly.
+ $input = $default;
+ $output->write($input);
+
+ $ofs = 0;
+ $matches = self::filterTimezones($allTimezones, $input);
+ if (!empty($matches)) {
+ $hint = $matches[$ofs % count($matches)];
+ // Avoid duplicating hint when input already fully matches the only candidate.
+ if (!(count($matches) === 1 && $hint === $input)) {
+ $cursor->clearLineAfter();
+ $cursor->savePosition();
+ $output->write(' ' . $hint . '>');
+ if (count($matches) > 1) {
+ $output->write(' (' . count($matches) . ' matches, ↑↓)');
+ }
+ $cursor->restorePosition();
+ }
+ }
+
+ try {
+ while (!feof(STDIN)) {
+ $c = fread(STDIN, 1);
+
+ if (false === $c || '' === $c) {
+ break;
+ }
+
+ // ── Backspace ──
+ if ("\177" === $c || "\010" === $c) {
+ if ('' !== $input) {
+ $lastChar = mb_substr($input, -1);
+ $input = mb_substr($input, 0, -1);
+ $cursor->moveLeft(max(1, mb_strwidth($lastChar)));
+ }
+ $ofs = 0;
+
+ // ── Escape sequences (arrows) ──
+ } elseif ("\033" === $c) {
+ $seq = fread(STDIN, 2);
+ if (isset($seq[1]) && !empty($matches)) {
+ if ('A' === $seq[1]) {
+ $ofs = ($ofs - 1 + count($matches)) % count($matches);
+ } elseif ('B' === $seq[1]) {
+ $ofs = ($ofs + 1) % count($matches);
+ }
+ }
+
+ // ── Tab: accept current match ──
+ } elseif ("\t" === $c) {
+ if (isset($matches[$ofs])) {
+ self::replaceInput($output, $cursor, $input, $matches[$ofs]);
+ $input = $matches[$ofs];
+ $matches = [];
+ }
+ $cursor->clearLineAfter();
+ continue;
+
+ // ── Enter: confirm ──
+ } elseif ("\n" === $c || "\r" === $c) {
+ if (isset($matches[$ofs])) {
+ self::replaceInput($output, $cursor, $input, $matches[$ofs]);
+ $input = $matches[$ofs];
+ }
+ if ($input === '') {
+ $input = $default;
+ }
+ // Re-render user input with style
+ $cursor->moveToColumn(1);
+ $cursor->clearLine();
+ $output->write('> ' . $input . '');
+ $output->writeln('');
+ break;
+
+ // ── Other control chars: ignore ──
+ } elseif (ord($c) < 32) {
+ continue;
+
+ // ── Printable character ──
+ } else {
+ if ("\x80" <= $c) {
+ $extra = ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3];
+ $c .= fread(STDIN, $extra[$c & "\xF0"] ?? 0);
+ }
+ $output->write($c);
+ $input .= $c;
+ $ofs = 0;
+ }
+
+ // Update match list
+ $matches = self::filterTimezones($allTimezones, $input);
+
+ // Show autocomplete hint
+ $cursor->clearLineAfter();
+ if (!empty($matches)) {
+ $hint = $matches[$ofs % count($matches)];
+ $cursor->savePosition();
+ $output->write(' ' . $hint . '>');
+ if (count($matches) > 1) {
+ $output->write(' (' . count($matches) . ' matches, ↑↓)');
+ }
+ $cursor->restorePosition();
+ }
+ }
+ } finally {
+ if (self::$sttyMode !== null) {
+ shell_exec('stty ' . self::$sttyMode);
+ self::$sttyMode = null;
+ }
+ }
+
+ $result = '' === $input ? $default : $input;
+
+ if (!in_array($result, $allTimezones, true)) {
+ $output->writeln('' . $msg('timezone_invalid', $default) . '');
+ return $default;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Clear current input and replace with new text.
+ */
+ private static function replaceInput(ConsoleOutput $output, Cursor $cursor, string $oldInput, string $newInput): void
+ {
+ if ('' !== $oldInput) {
+ $cursor->moveLeft(mb_strwidth($oldInput));
+ }
+ $cursor->clearLineAfter();
+ $output->write($newInput);
+ }
+
+ /**
+ * Case-insensitive substring match for timezones.
+ */
+ private static function filterTimezones(array $timezones, string $input): array
+ {
+ if ('' === $input) {
+ return [];
+ }
+ $lower = mb_strtolower($input);
+ return array_values(array_filter(
+ $timezones,
+ fn(string $tz) => str_contains(mb_strtolower($tz), $lower)
+ ));
+ }
+
+ /**
+ * Find an exact timezone match (case-insensitive).
+ * Returns the correctly-cased system timezone name, or null if not found.
+ */
+ private static function findExactTimezone(array $allTimezones, string $input): ?string
+ {
+ $lower = mb_strtolower($input);
+ foreach ($allTimezones as $tz) {
+ if (mb_strtolower($tz) === $lower) {
+ return $tz;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Search timezones by keyword (substring) and similarity.
+ * Returns combined results: substring matches first, then similarity matches (>=50%).
+ *
+ * @param string[] $allTimezones All valid timezone identifiers
+ * @param string $keyword User input to search for
+ * @param int $limit Maximum number of results
+ * @return string[] Matched timezone identifiers
+ */
+ private static function searchTimezones(array $allTimezones, string $keyword, int $limit = 15): array
+ {
+ // 1. Substring matches (higher priority)
+ $substringMatches = self::filterTimezones($allTimezones, $keyword);
+ if (count($substringMatches) >= $limit) {
+ return array_slice($substringMatches, 0, $limit);
+ }
+
+ // 2. Similarity matches for remaining slots (normalized: strip _ and /)
+ $substringSet = array_flip($substringMatches);
+ $normalizedKeyword = str_replace(['_', '/'], ' ', mb_strtolower($keyword));
+ $similarityMatches = [];
+
+ foreach ($allTimezones as $tz) {
+ if (isset($substringSet[$tz])) {
+ continue;
+ }
+ $parts = explode('/', $tz);
+ $city = str_replace('_', ' ', mb_strtolower(end($parts)));
+ $normalizedTz = str_replace(['_', '/'], ' ', mb_strtolower($tz));
+
+ similar_text($normalizedKeyword, $city, $cityPercent);
+ similar_text($normalizedKeyword, $normalizedTz, $fullPercent);
+
+ $bestPercent = max($cityPercent, $fullPercent);
+ if ($bestPercent >= 50.0) {
+ $similarityMatches[] = ['tz' => $tz, 'score' => $bestPercent];
+ }
+ }
+
+ usort($similarityMatches, fn(array $a, array $b) => $b['score'] <=> $a['score']);
+
+ $results = $substringMatches;
+ foreach ($similarityMatches as $item) {
+ $results[] = $item['tz'];
+ if (count($results) >= $limit) {
+ break;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Option B: when stty is not available (e.g. Windows), keyword search with numbered list.
+ * Flow: enter timezone/keyword → exact match uses it directly; otherwise show
+ * numbered results (substring + similarity) → pick by number or refine keyword.
+ */
+ private static function askTimezoneSelect(IOInterface $io, callable $msg, string $default): string
+ {
+ $allTimezones = \DateTimeZone::listIdentifiers();
+
+ $io->write('');
+ $io->write('' . $msg('timezone_title', $default) . '>');
+ $io->write($msg('timezone_input_prompt'));
+
+ /** @var string[]|null Currently displayed search result list */
+ $currentList = null;
+
+ while (true) {
+ $io->write('> ', false);
+ $line = fgets(STDIN);
+ if ($line === false) {
+ return $default;
+ }
+ $answer = trim($line);
+
+ // Empty input → use default
+ if ($answer === '') {
+ $io->write('> ' . $default . '');
+ return $default;
+ }
+
+ // If a numbered list is displayed and input is a pure number
+ if ($currentList !== null && ctype_digit($answer)) {
+ $idx = (int) $answer;
+ if (isset($currentList[$idx])) {
+ $io->write('> ' . $currentList[$idx] . '');
+ return $currentList[$idx];
+ }
+ $io->write('' . $msg('timezone_invalid_index') . '');
+ continue;
+ }
+
+ // Exact case-insensitive match → return the correctly-cased system value
+ $exact = self::findExactTimezone($allTimezones, $answer);
+ if ($exact !== null) {
+ $io->write('> ' . $exact . '');
+ return $exact;
+ }
+
+ // Keyword + similarity search
+ $results = self::searchTimezones($allTimezones, $answer);
+
+ if (empty($results)) {
+ $io->write('' . $msg('timezone_no_match') . '');
+ $currentList = null;
+ continue;
+ }
+
+ // Single result → use it directly
+ if (count($results) === 1) {
+ $io->write('> ' . $results[0] . '');
+ return $results[0];
+ }
+
+ // Display numbered list
+ $currentList = $results;
+ $padWidth = strlen((string) (count($results) - 1));
+ foreach ($results as $i => $tz) {
+ $io->write(' [' . str_pad((string) $i, $padWidth) . '] ' . $tz);
+ }
+ $io->write($msg('timezone_pick_prompt'));
+ }
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // Optional component selection
+ // ═══════════════════════════════════════════════════════════════
+
+ private static function askComponents(IOInterface $io, callable $msg): array
+ {
+ $packages = [];
+ $addPackage = static function (string $package) use (&$packages, $io, $msg): void {
+ if (in_array($package, $packages, true)) {
+ return;
+ }
+ $packages[] = $package;
+ $io->write($msg('adding_package', '' . $package . ''));
+ };
+
+ // Console (default: yes)
+ if (self::confirmMenu($io, $msg('console_question'), true)) {
+ $addPackage(self::PACKAGE_CONSOLE);
+ }
+
+ // Database
+ $dbItems = [
+ ['tag' => '0', 'label' => $msg('db_none')],
+ ['tag' => '1', 'label' => 'webman/database'],
+ ['tag' => '2', 'label' => 'webman/think-orm'],
+ ['tag' => '3', 'label' => 'webman/database && webman/think-orm'],
+ ];
+ $dbChoice = self::selectMenu($io, $msg('db_question'), $dbItems, 0);
+ if ($dbChoice === 1) {
+ $addPackage(self::PACKAGE_DATABASE);
+ } elseif ($dbChoice === 2) {
+ $addPackage(self::PACKAGE_THINK_ORM);
+ } elseif ($dbChoice === 3) {
+ $addPackage(self::PACKAGE_DATABASE);
+ $addPackage(self::PACKAGE_THINK_ORM);
+ }
+
+ // If webman/database is selected, add required dependencies automatically
+ if (in_array(self::PACKAGE_DATABASE, $packages, true)) {
+ $addPackage(self::PACKAGE_ILLUMINATE_PAGINATION);
+ $addPackage(self::PACKAGE_ILLUMINATE_EVENTS);
+ $addPackage(self::PACKAGE_SYMFONY_VAR_DUMPER);
+ }
+
+ // Redis (default: no)
+ if (self::confirmMenu($io, $msg('redis_question'), false)) {
+ $addPackage(self::PACKAGE_REDIS);
+ $addPackage(self::PACKAGE_ILLUMINATE_EVENTS);
+ }
+
+ // Validation (default: no)
+ if (self::confirmMenu($io, $msg('validation_question'), false)) {
+ $addPackage(self::PACKAGE_VALIDATION);
+ }
+
+ // Template engine
+ $tplItems = [
+ ['tag' => '0', 'label' => $msg('template_none')],
+ ['tag' => '1', 'label' => 'webman/blade'],
+ ['tag' => '2', 'label' => 'twig/twig'],
+ ['tag' => '3', 'label' => 'topthink/think-template'],
+ ];
+ $tplChoice = self::selectMenu($io, $msg('template_question'), $tplItems, 0);
+ if ($tplChoice === 1) {
+ $addPackage(self::PACKAGE_BLADE);
+ } elseif ($tplChoice === 2) {
+ $addPackage(self::PACKAGE_TWIG);
+ } elseif ($tplChoice === 3) {
+ $addPackage(self::PACKAGE_THINK_TEMPLATE);
+ }
+
+ return $packages;
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // Config file update
+ // ═══════════════════════════════════════════════════════════════
+
+ /**
+ * Update a config value like 'key' => 'old_value' in the given file.
+ */
+ private static function updateConfig(Event $event, string $relativePath, string $key, string $newValue): void
+ {
+ $root = dirname($event->getComposer()->getConfig()->get('vendor-dir'));
+ $file = $root . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relativePath);
+ if (!is_readable($file)) {
+ return;
+ }
+ $content = file_get_contents($file);
+ if ($content === false) {
+ return;
+ }
+ $pattern = '/' . preg_quote($key, '/') . "\s*=>\s*'[^']*'/";
+ $replacement = $key . " => '" . $newValue . "'";
+ $newContent = preg_replace($pattern, $replacement, $content);
+ if ($newContent !== null && $newContent !== $content) {
+ file_put_contents($file, $newContent);
+ }
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // Composer require
+ // ═══════════════════════════════════════════════════════════════
+
+ private static function runComposerRequire(array $packages, IOInterface $io, callable $msg): void
+ {
+ $io->write('' . $msg('running') . ' composer require ' . implode(' ', $packages));
+ $io->write('');
+
+ $code = self::runComposerCommand('require', $packages);
+
+ if ($code !== 0) {
+ $io->writeError('' . $msg('error_install', implode(' ', $packages)) . '');
+ } else {
+ $io->write('' . $msg('done') . '');
+ }
+ }
+
+ private static function askRemoveComponents(Event $event, array $selectedPackages, IOInterface $io, callable $msg): array
+ {
+ $requires = $event->getComposer()->getPackage()->getRequires();
+ $allOptionalPackages = [
+ self::PACKAGE_CONSOLE,
+ self::PACKAGE_DATABASE,
+ self::PACKAGE_THINK_ORM,
+ self::PACKAGE_REDIS,
+ self::PACKAGE_ILLUMINATE_EVENTS,
+ self::PACKAGE_ILLUMINATE_PAGINATION,
+ self::PACKAGE_SYMFONY_VAR_DUMPER,
+ self::PACKAGE_VALIDATION,
+ self::PACKAGE_BLADE,
+ self::PACKAGE_TWIG,
+ self::PACKAGE_THINK_TEMPLATE,
+ ];
+
+ $secondaryPackages = [
+ self::PACKAGE_ILLUMINATE_EVENTS,
+ self::PACKAGE_ILLUMINATE_PAGINATION,
+ self::PACKAGE_SYMFONY_VAR_DUMPER,
+ ];
+
+ $installedOptionalPackages = [];
+ foreach ($allOptionalPackages as $pkg) {
+ if (isset($requires[$pkg])) {
+ $installedOptionalPackages[] = $pkg;
+ }
+ }
+
+ $allPackagesToRemove = array_diff($installedOptionalPackages, $selectedPackages);
+
+ if (count($allPackagesToRemove) === 0) {
+ return [];
+ }
+
+ $displayPackagesToRemove = array_diff($allPackagesToRemove, $secondaryPackages);
+
+ if (count($displayPackagesToRemove) === 0) {
+ return $allPackagesToRemove;
+ }
+
+ $pkgListStr = "";
+ foreach ($displayPackagesToRemove as $pkg) {
+ $pkgListStr .= "\n - {$pkg}";
+ }
+ $pkgListStr .= "\n";
+
+ $title = '' . $msg('remove_package_question', '') . '' . $pkgListStr;
+ if (self::confirmMenu($io, $title, false)) {
+ return $allPackagesToRemove;
+ }
+
+ return [];
+ }
+
+ private static function runComposerRemove(array $packages, IOInterface $io, callable $msg): void
+ {
+ $io->write('' . $msg('running') . ' composer remove ' . implode(' ', $packages));
+ $io->write('');
+
+ $code = self::runComposerCommand('remove', $packages);
+
+ if ($code !== 0) {
+ $io->writeError('' . $msg('error_remove', implode(' ', $packages)) . '');
+ } else {
+ $io->write('' . $msg('done_remove') . '');
+ }
+ }
+
+ /**
+ * Run a Composer command (require/remove) in-process via Composer's Application API.
+ * No shell execution functions needed — works even when passthru/exec/shell_exec are disabled.
+ */
+ private static function runComposerCommand(string $command, array $packages): int
+ {
+ try {
+ // Already inside a user-initiated Composer session — suppress duplicate root/superuser warnings
+ $_SERVER['COMPOSER_ALLOW_SUPERUSER'] = '1';
+ if (function_exists('putenv')) {
+ putenv('COMPOSER_ALLOW_SUPERUSER=1');
+ }
+
+ $application = new ComposerApplication();
+ $application->setAutoExit(false);
+
+ return $application->run(
+ new ArrayInput([
+ 'command' => $command,
+ 'packages' => $packages,
+ '--no-interaction' => true,
+ '--update-with-all-dependencies' => true,
+ ]),
+ new ConsoleOutput()
+ );
+ } catch (\Throwable) {
+ return 1;
+ }
+ }
+}
diff --git a/support/bootstrap.php b/support/bootstrap.php
new file mode 100644
index 0000000..d913def
--- /dev/null
+++ b/support/bootstrap.php
@@ -0,0 +1,139 @@
+
+ * @copyright walkor
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+use Dotenv\Dotenv;
+use support\Log;
+use Webman\Bootstrap;
+use Webman\Config;
+use Webman\Middleware;
+use Webman\Route;
+use Webman\Util;
+use Workerman\Events\Select;
+use Workerman\Worker;
+
+$worker = $worker ?? null;
+
+if (empty(Worker::$eventLoopClass)) {
+ Worker::$eventLoopClass = Select::class;
+}
+
+set_error_handler(function ($level, $message, $file = '', $line = 0) {
+ if (error_reporting() & $level) {
+ throw new ErrorException($message, 0, $level, $file, $line);
+ }
+});
+
+if ($worker) {
+ register_shutdown_function(function ($startTime) {
+ if (time() - $startTime <= 0.1) {
+ sleep(1);
+ }
+ }, time());
+}
+
+if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) {
+ if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) {
+ Dotenv::createUnsafeMutable(base_path(false))->load();
+ } else {
+ Dotenv::createMutable(base_path(false))->load();
+ }
+}
+
+Config::clear();
+support\App::loadAllConfig(['route']);
+if ($timezone = config('app.default_timezone')) {
+ date_default_timezone_set($timezone);
+}
+
+foreach (config('autoload.files', []) as $file) {
+ include_once $file;
+}
+foreach (config('plugin', []) as $firm => $projects) {
+ foreach ($projects as $name => $project) {
+ if (!is_array($project)) {
+ continue;
+ }
+ foreach ($project['autoload']['files'] ?? [] as $file) {
+ include_once $file;
+ }
+ }
+ foreach ($projects['autoload']['files'] ?? [] as $file) {
+ include_once $file;
+ }
+}
+
+Middleware::load(config('middleware', []));
+foreach (config('plugin', []) as $firm => $projects) {
+ foreach ($projects as $name => $project) {
+ if (!is_array($project) || $name === 'static') {
+ continue;
+ }
+ Middleware::load($project['middleware'] ?? []);
+ }
+ Middleware::load($projects['middleware'] ?? [], $firm);
+ if ($staticMiddlewares = config("plugin.$firm.static.middleware")) {
+ Middleware::load(['__static__' => $staticMiddlewares], $firm);
+ }
+}
+Middleware::load(['__static__' => config('static.middleware', [])]);
+
+foreach (config('bootstrap', []) as $className) {
+ if (!class_exists($className)) {
+ $log = "Warning: Class $className setting in config/bootstrap.php not found\r\n";
+ echo $log;
+ Log::error($log);
+ continue;
+ }
+ /** @var Bootstrap $className */
+ $className::start($worker);
+}
+
+foreach (config('plugin', []) as $firm => $projects) {
+ foreach ($projects as $name => $project) {
+ if (!is_array($project)) {
+ continue;
+ }
+ foreach ($project['bootstrap'] ?? [] as $className) {
+ if (!class_exists($className)) {
+ $log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n";
+ echo $log;
+ Log::error($log);
+ continue;
+ }
+ /** @var Bootstrap $className */
+ $className::start($worker);
+ }
+ }
+ foreach ($projects['bootstrap'] ?? [] as $className) {
+ /** @var string $className */
+ if (!class_exists($className)) {
+ $log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n";
+ echo $log;
+ Log::error($log);
+ continue;
+ }
+ /** @var Bootstrap $className */
+ $className::start($worker);
+ }
+}
+
+$directory = base_path() . '/plugin';
+$paths = [config_path()];
+foreach (Util::scanDir($directory) as $path) {
+ if (is_dir($path = "$path/config")) {
+ $paths[] = $path;
+ }
+}
+Route::load($paths);
+
diff --git a/tests/db/DBC.php b/tests/db/DBC.php
new file mode 100644
index 0000000..566162a
--- /dev/null
+++ b/tests/db/DBC.php
@@ -0,0 +1,51 @@
+getConnection();
+ $this->assertNotNull($db);
+ }
+
+ /**
+ * 测试生成所有表
+ */
+// public function testGenTables()
+// {
+// $result = ModelAutoGenerator::generate_all(true);
+//
+// // 2. 断言返回结果结构正确
+// $this->assertArrayHasKey('success', $result, '批量生成结果缺少 success 字段');
+// $this->assertArrayHasKey('fail', $result, '批量生成结果缺少 fail 字段');
+// // 3. 断言 success 是整数类型
+// $this->assertIsInt($result['success'], 'success 字段不是整数');
+// // 4. 断言 fail 是数组类型
+// $this->assertIsArray($result['fail'], 'fail 字段不是数组');
+// // 5. 断言 fail 数组是空的
+// if (!empty($result['fail'])){
+// foreach ($result['fail'] as $key => $value){
+// Logger::error($value['table']."表生成失败:".$value['reason']);
+// }
+// }
+// $this->assertEmpty($result['fail'], 'fail 数组不是空的,说明有表生成失败');
+// }
+
+ public function testModel()
+ {
+ $users = EctUser::all();
+ $this->assertNotEmpty($users, '用户列表不能为空');
+ }
+}
\ No newline at end of file
diff --git a/tests/resources/default_environment.json b/tests/resources/default_environment.json
new file mode 100644
index 0000000..ccc1055
--- /dev/null
+++ b/tests/resources/default_environment.json
@@ -0,0 +1,95 @@
+{
+ "description": "虚拟测试环境默认配置",
+ "version": "1.0",
+
+ "readers": {
+ "清洗": {
+ "no": "R001",
+ "id": "1",
+ "type": "清洗"
+ },
+ "漂洗": {
+ "no": "R002",
+ "id": "2",
+ "type": "漂洗"
+ },
+ "消毒": {
+ "no": "R003",
+ "id": "3",
+ "type": "消毒"
+ },
+ "终末漂洗": {
+ "no": "R004",
+ "id": "4",
+ "type": "终末漂洗"
+ },
+ "干燥": {
+ "no": "R005",
+ "id": "5",
+ "type": "干燥"
+ },
+ "机洗": {
+ "no": "R006",
+ "id": "6",
+ "type": "机洗"
+ },
+ "存储": {
+ "no": "R007",
+ "id": "7",
+ "type": "存储"
+ },
+ "未绑定": {
+ "no": "R999",
+ "id": "",
+ "type": ""
+ }
+ },
+
+ "endoscopes": {
+ "胃镜1": {
+ "id": "E001",
+ "name": "胃镜1号",
+ "cardNo": "CARD_E001",
+ "type": "胃镜"
+ },
+ "肠镜1": {
+ "id": "E002",
+ "name": "肠镜1号",
+ "cardNo": "CARD_E002",
+ "type": "肠镜"
+ },
+ "未绑定卡": {
+ "id": "",
+ "name": "",
+ "cardNo": "CARD_UNKNOWN",
+ "type": ""
+ }
+ },
+
+ "operators": {
+ "操作员1": {
+ "id": "U001",
+ "name": "张三",
+ "rfid": "RFID_U001"
+ },
+ "操作员2": {
+ "id": "U002",
+ "name": "李四",
+ "rfid": "RFID_U002"
+ }
+ },
+
+ "config": {
+ "machineId": "01",
+ "morningWashStartTime": "06:00:00",
+ "morningWashMode": "standard"
+ },
+
+ "stepDurations": {
+ "清洗": 60,
+ "漂洗": 60,
+ "消毒": 180,
+ "终末漂洗": 60,
+ "干燥": 60
+ }
+}
diff --git a/webman b/webman
new file mode 100644
index 0000000..3f55062
--- /dev/null
+++ b/webman
@@ -0,0 +1,71 @@
+#!/usr/bin/env php
+load();
+ } else {
+ Dotenv::createMutable(run_path())->load();
+ }
+}
+
+$appConfig = require $appConfigFile;
+if ($timezone = $appConfig['default_timezone'] ?? '') {
+ date_default_timezone_set($timezone);
+}
+
+if ($errorReporting = $appConfig['error_reporting'] ?? '') {
+ error_reporting($errorReporting);
+}
+
+if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) {
+ require_once __DIR__ . '/support/bootstrap.php';
+} else {
+ if (class_exists('Support\App')) {
+ Support\App::loadAllConfig(['route']);
+ } else {
+ Config::reload(config_path(), ['route', 'container']);
+ }
+}
+
+$cli = new Command();
+$cli->setName('webman cli');
+$cli->installInternalCommands();
+if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) {
+ $cli->installCommands($command_path);
+}
+
+foreach (config('plugin', []) as $firm => $projects) {
+ if (isset($projects['app'])) {
+ foreach (['', '/app'] as $app) {
+ if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) {
+ $command_path = base_path() . "/plugin/$firm{$app}/$command_str";
+ $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str");
+ }
+ }
+ }
+ foreach ($projects as $name => $project) {
+ if (!is_array($project)) {
+ continue;
+ }
+ $project['command'] ??= [];
+ array_walk($project['command'], [$cli, 'createCommandInstance']);
+ }
+}
+
+$cli->run();
diff --git a/windows.bat b/windows.bat
new file mode 100644
index 0000000..f07ce53
--- /dev/null
+++ b/windows.bat
@@ -0,0 +1,3 @@
+CHCP 65001
+php windows.php
+pause
\ No newline at end of file
diff --git a/windows.php b/windows.php
new file mode 100644
index 0000000..f37a72c
--- /dev/null
+++ b/windows.php
@@ -0,0 +1,136 @@
+load();
+ } else {
+ Dotenv::createMutable(base_path())->load();
+ }
+}
+
+App::loadAllConfig(['route']);
+
+$errorReporting = config('app.error_reporting');
+if (isset($errorReporting)) {
+ error_reporting($errorReporting);
+}
+
+$runtimeProcessPath = runtime_path() . DIRECTORY_SEPARATOR . '/windows';
+$paths = [
+ $runtimeProcessPath,
+ runtime_path('logs'),
+ runtime_path('views')
+];
+foreach ($paths as $path) {
+ if (!is_dir($path)) {
+ mkdir($path, 0777, true);
+ }
+}
+
+$processFiles = [];
+if (config('server.listen')) {
+ $processFiles[] = __DIR__ . DIRECTORY_SEPARATOR . 'start.php';
+}
+foreach (config('process', []) as $processName => $config) {
+ $processFiles[] = write_process_file($runtimeProcessPath, $processName, '');
+}
+
+foreach (config('plugin', []) as $firm => $projects) {
+ foreach ($projects as $name => $project) {
+ if (!is_array($project)) {
+ continue;
+ }
+ foreach ($project['process'] ?? [] as $processName => $config) {
+ $processFiles[] = write_process_file($runtimeProcessPath, $processName, "$firm.$name");
+ }
+ }
+ foreach ($projects['process'] ?? [] as $processName => $config) {
+ $processFiles[] = write_process_file($runtimeProcessPath, $processName, $firm);
+ }
+}
+
+function write_process_file($runtimeProcessPath, $processName, $firm): string
+{
+ $processParam = $firm ? "plugin.$firm.$processName" : $processName;
+ $configParam = $firm ? "config('plugin.$firm.process')['$processName']" : "config('process')['$processName']";
+ $fileContent = << true]);
+ if (!$resource) {
+ exit("Can not execute $cmd\r\n");
+ }
+ return $resource;
+}
+
+$resource = popen_processes($processFiles);
+echo "\r\n";
+while (1) {
+ sleep(1);
+ if (!empty($monitor) && $monitor->checkAllFilesChange()) {
+ $status = proc_get_status($resource);
+ $pid = $status['pid'];
+ shell_exec("taskkill /F /T /PID $pid");
+ proc_close($resource);
+ $resource = popen_processes($processFiles);
+ }
+}