From 6f178e30c51607697721e9a26d61d36cc4482860 Mon Sep 17 00:00:00 2001 From: zimoyin <2556608754@qq.com> Date: Sun, 2 Aug 2026 02:23:18 +0800 Subject: [PATCH] Initial commit: webman project --- .gitignore | 6 + webman/.gitignore | 8 + webman/Dockerfile | 18 + webman/LICENSE | 21 + webman/README.md | 70 + webman/app/controller/IndexController.php | 42 + webman/app/functions.php | 4 + webman/app/http/play.http | 22 + webman/app/http/play_next.http | 9 + webman/app/http/play_previous.http | 9 + webman/app/http/play_text_speak.http | 23 + webman/app/http/playground.http | 5 + webman/app/http/queue_playmode.http | 12 + webman/app/http/queue_vol.http | 12 + webman/app/http/stop.http | 9 + webman/app/middleware/StaticFile.php | 42 + webman/app/model/Test.php | 29 + webman/app/process/Http.php | 10 + webman/app/process/Monitor.php | 305 ++ webman/app/process/Timer.php | 148 + webman/app/service/AudioService.php | 96 + webman/app/view/index/view.html | 14 + webman/composer.json | 68 + webman/composer.lock | 5206 +++++++++++++++++++ webman/config/app.php | 26 + webman/config/autoload.php | 21 + webman/config/bootstrap.php | 18 + webman/config/container.php | 15 + webman/config/database.php | 29 + webman/config/dependence.php | 15 + webman/config/exception.php | 17 + webman/config/log.php | 32 + webman/config/middleware.php | 15 + webman/config/plugin/webman/console/app.php | 28 + webman/config/process.php | 67 + webman/config/route.php | 20 + webman/config/server.php | 23 + webman/config/session.php | 65 + webman/config/static.php | 23 + webman/config/think-orm.php | 42 + webman/config/translation.php | 25 + webman/config/view.php | 22 + webman/docker-compose.yml | 11 + webman/public/2026-05-10T170440.200.mpeg | Bin 0 -> 12528 bytes webman/public/2026-05-10T172805.200.mpeg | Bin 0 -> 9612 bytes webman/public/2026-05-10T172831.200.mpeg | Bin 0 -> 9612 bytes webman/public/2026-05-10T172852.200.mpeg | Bin 0 -> 8208 bytes webman/public/2026-05-10T172914.200.mpeg | Bin 0 -> 7236 bytes webman/public/2026-05-10T172925.200.mpeg | Bin 0 -> 8208 bytes webman/public/2026-05-10T173349.200.mpeg | Bin 0 -> 12528 bytes webman/public/2026-05-10T173840.200.mpeg | Bin 0 -> 10476 bytes webman/public/2026-05-10T173925.200.mpeg | Bin 0 -> 10368 bytes webman/public/favicon.ico | Bin 0 -> 4286 bytes webman/public/http-client.cookies | 1 + webman/public/http-requests-log.http | 643 +++ webman/start.php | 5 + webman/support/Request.php | 24 + webman/support/Response.php | 24 + webman/support/Setup.php | 1558 ++++++ webman/support/bootstrap.php | 3 + webman/webman | 71 + webman/windows.bat | 3 + webman/windows.php | 136 + 63 files changed, 9170 insertions(+) create mode 100644 .gitignore create mode 100644 webman/.gitignore create mode 100644 webman/Dockerfile create mode 100644 webman/LICENSE create mode 100644 webman/README.md create mode 100644 webman/app/controller/IndexController.php create mode 100644 webman/app/functions.php create mode 100644 webman/app/http/play.http create mode 100644 webman/app/http/play_next.http create mode 100644 webman/app/http/play_previous.http create mode 100644 webman/app/http/play_text_speak.http create mode 100644 webman/app/http/playground.http create mode 100644 webman/app/http/queue_playmode.http create mode 100644 webman/app/http/queue_vol.http create mode 100644 webman/app/http/stop.http create mode 100644 webman/app/middleware/StaticFile.php create mode 100644 webman/app/model/Test.php create mode 100644 webman/app/process/Http.php create mode 100644 webman/app/process/Monitor.php create mode 100644 webman/app/process/Timer.php create mode 100644 webman/app/service/AudioService.php create mode 100644 webman/app/view/index/view.html create mode 100644 webman/composer.json create mode 100644 webman/composer.lock create mode 100644 webman/config/app.php create mode 100644 webman/config/autoload.php create mode 100644 webman/config/bootstrap.php create mode 100644 webman/config/container.php create mode 100644 webman/config/database.php create mode 100644 webman/config/dependence.php create mode 100644 webman/config/exception.php create mode 100644 webman/config/log.php create mode 100644 webman/config/middleware.php create mode 100644 webman/config/plugin/webman/console/app.php create mode 100644 webman/config/process.php create mode 100644 webman/config/route.php create mode 100644 webman/config/server.php create mode 100644 webman/config/session.php create mode 100644 webman/config/static.php create mode 100644 webman/config/think-orm.php create mode 100644 webman/config/translation.php create mode 100644 webman/config/view.php create mode 100644 webman/docker-compose.yml create mode 100644 webman/public/2026-05-10T170440.200.mpeg create mode 100644 webman/public/2026-05-10T172805.200.mpeg create mode 100644 webman/public/2026-05-10T172831.200.mpeg create mode 100644 webman/public/2026-05-10T172852.200.mpeg create mode 100644 webman/public/2026-05-10T172914.200.mpeg create mode 100644 webman/public/2026-05-10T172925.200.mpeg create mode 100644 webman/public/2026-05-10T173349.200.mpeg create mode 100644 webman/public/2026-05-10T173840.200.mpeg create mode 100644 webman/public/2026-05-10T173925.200.mpeg create mode 100644 webman/public/favicon.ico create mode 100644 webman/public/http-client.cookies create mode 100644 webman/public/http-requests-log.http create mode 100644 webman/start.php create mode 100644 webman/support/Request.php create mode 100644 webman/support/Response.php create mode 100644 webman/support/Setup.php create mode 100644 webman/support/bootstrap.php create mode 100644 webman/webman create mode 100644 webman/windows.bat create mode 100644 webman/windows.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40f9fa5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/webman/runtime +/.idea +/.vscode +/webman/vendor +*.log +/webman/.env diff --git a/webman/.gitignore b/webman/.gitignore new file mode 100644 index 0000000..516299c --- /dev/null +++ b/webman/.gitignore @@ -0,0 +1,8 @@ +/runtime +/.idea +/.vscode +/vendor +*.log +.env +/tests/tmp +/tests/.phpunit.result.cache diff --git a/webman/Dockerfile b/webman/Dockerfile new file mode 100644 index 0000000..94ce154 --- /dev/null +++ b/webman/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/webman/LICENSE b/webman/LICENSE new file mode 100644 index 0000000..2c66292 --- /dev/null +++ b/webman/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 walkor and contributors (see https://github.com/walkor/webman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/webman/README.md b/webman/README.md new file mode 100644 index 0000000..4031784 --- /dev/null +++ b/webman/README.md @@ -0,0 +1,70 @@ +
+

webman

+ +基于workerman开发的超高性能PHP框架 + + +

学习

+ + + +
+ +

赞助商

+ +

特别赞助

+ + + + +

铂金赞助

+ + + + +
+ + +
+ +

请作者喝咖啡

+ + + +
+如果您觉得webman对您有所帮助,欢迎捐赠。 + + +
+ + +
+

LICENSE

+The webman is open-sourced software licensed under the MIT. +
+ +
+ + diff --git a/webman/app/controller/IndexController.php b/webman/app/controller/IndexController.php new file mode 100644 index 0000000..396ea80 --- /dev/null +++ b/webman/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/webman/app/functions.php b/webman/app/functions.php new file mode 100644 index 0000000..5c9c58d --- /dev/null +++ b/webman/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/webman/app/model/Test.php b/webman/app/model/Test.php new file mode 100644 index 0000000..92d70e3 --- /dev/null +++ b/webman/app/model/Test.php @@ -0,0 +1,29 @@ + + * @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/webman/app/process/Timer.php b/webman/app/process/Timer.php new file mode 100644 index 0000000..4d3526e --- /dev/null +++ b/webman/app/process/Timer.php @@ -0,0 +1,148 @@ +'...','port'=>...], ...] + private AudioService $audio; + + public function onWorkerStart(Worker $worker): void + { + $this->port = (int)(getenv('TIMER_PORT') ?: 10000); + $this->maxValue = (int)(getenv('TIMER_MAX') ?: 6); + $this->intervalSec = (float)(getenv('TIMER_INTERVAL') ?: 1); + + $this->loadTexts(); + $this->targets = $this->loadIps(); + $this->audio = new AudioService(); + + echo "Timer process is running.\n"; + echo "目标: "; + foreach ($this->targets as $t) { + echo "{$t['ip']}:{$t['port']} "; + } + echo "\n最大值: {$this->maxValue}, 间隔: {$this->intervalSec}s\n"; + + $value = 1; + + $timerCallback = function () use (&$value) { + $text = $this->texts[$value] ?? "当前: {$value}"; + + $okImg = $this->sendImage($value); + $status = $okImg ? '图✓' : '图✗'; + echo "\r[{$value}] {$status} {$text}"; + $okAudio = $this->audio->sendText($text); + $status .= ' ' . ($okAudio ? '音✓' : '音✗'); + echo "\r[{$value}] {$status} {$text}\n"; + + $value = $value >= $this->maxValue ? 1 : $value + 1; + }; + + // 立即执行一次,再定时 + $timerCallback(); + WorkermanTimer::add($this->intervalSec, $timerCallback); + } + + /** + * 加载目标 IP 列表 + * 优先检查 TIMER_IPS 环境变量(逗号分隔,可带端口如 192.168.1.1:10001) + * 否则回退到单个 TIMER_IP + TIMER_PORT + */ + private function loadIps(): array + { + $targets = []; + + $ipsEnv = getenv('TIMER_IPS'); + if ($ipsEnv !== false && trim($ipsEnv) !== '') { + // 多 IP 模式 + $parts = explode(',', $ipsEnv); + foreach ($parts as $part) { + $part = trim($part); + if ($part === '') continue; + if (strpos($part, ':') !== false) { + [$ip, $portStr] = explode(':', $part, 2); + $targets[] = [ + 'ip' => trim($ip), + 'port' => (int)trim($portStr) + ]; + } else { + $targets[] = [ + 'ip' => $part, + 'port' => $this->port + ]; + } + } + } else { + // 兼容单 IP 旧配置 + $ip = getenv('TIMER_IP') ?: '192.168.42.1'; + $targets[] = [ + 'ip' => $ip, + 'port' => $this->port + ]; + } + + return $targets; + } + + /** + * 向所有目标发送 HEX 数据包 + * @return bool 全部发送成功返回 true,任一失败返回 false + */ + private function sendImage(int $variable): bool + { + $data = self::HEX_DATA; + $data[self::VAR_POS] = ($variable + self::VAR_OFFSET) & 0xFF; + $packedData = pack('C*', ...$data); + + $allSuccess = true; + foreach ($this->targets as $target) { + try { + $socket = @fsockopen($target['ip'], $target['port'], $errno, $errstr, 5); + echo "\r图> 尝试连接 [{$target['ip']}:{$target['port']}]"; + if (!$socket) { + echo "\r图> 连接失败 [{$target['ip']}:{$target['port']}]: {$errstr} ({$errno})\n"; + $allSuccess = false; + continue; + } + echo "\r图> 已连接 [{$target['ip']}:{$target['port']}]"; + fwrite($socket, $packedData); + fflush($socket); + fclose($socket); + }catch (\Exception $exception){ + echo "发送数据失败 [{$target['ip']}:{$target['port']}]: {$exception->getMessage()}\n"; + $allSuccess = false; + } + } + return $allSuccess; + } + + private function loadTexts(): void + { + for ($i = 1; $i <= $this->maxValue; $i++) { + $t = getenv('TIMER_TEXT_' . $i); + if ($t !== false && $t !== '') { + $this->texts[$i] = $t; + } + } + } +} \ No newline at end of file diff --git a/webman/app/service/AudioService.php b/webman/app/service/AudioService.php new file mode 100644 index 0000000..3d97ad8 --- /dev/null +++ b/webman/app/service/AudioService.php @@ -0,0 +1,96 @@ +host = getenv('AUDIO_MUSIC_HOST') ?: '192.168.42.4'; + $this->port = (int)(getenv('AUDIO_MUSIC_PORT') ?: 8888); + $this->sn = getenv('AUDIO_SN') ?: 'ls20://0202AD1AE849'; + $this->tid = getenv('AUDIO_TID') ?: '234'; + $this->vol = (int)(getenv('AUDIO_VOL') ?: 50); + $this->ttsUrl = getenv('AUDIO_TTS_URL') ?: 'http://192.168.42.139:10008/tts_xf.single'; + $this->ttsVoice = getenv('AUDIO_TTS_VOICE') ?: 'xiaoyan'; + $this->ttsSpeed = (int)(getenv('AUDIO_TTS_SPEED') ?: 50); + $this->ttsVolume = (int)(getenv('AUDIO_TTS_VOLUME') ?: 100); + } + + public function sendText(string $text): bool + { +// $uri = $this->ttsUrl . '?' . http_build_query([ +// 'text' => $text, +// 'voice_name' => $this->ttsVoice, +// 'speed' => $this->ttsSpeed, +// 'volume' => $this->ttsVolume, +// ]); + + $host = getenv('LOCAL_IP') ?: '192.168.42.168'; + $uri = 'http://' . $host . ':8787/' . $text; + + return $this->sendCommand('songs_queue_append', [ + 'tid' => $this->tid, + 'vol' => $this->vol, + 'urls' => [ + [ + 'name' => 'G.mp3', + 'uri' => $uri, + ] + ] + ]); + } + + private function sendCommand(string $name, ?array $params = null): bool + { + $payload = [ + 'sn' => $this->sn, + 'type' => 'req', + 'name' => $name, + ]; + if ($params !== null) { + $payload['params'] = $params; + } + + $json = json_encode($payload, JSON_UNESCAPED_UNICODE); + if (!$json) { + return false; + } + + $socket = @fsockopen($this->host, $this->port, $errno, $errstr, 5); + if (!$socket) { + echo "音乐设备连接失败: {$errstr} ({$errno})\n"; + return false; + } + + $body = $json; + $request = "POST / HTTP/1.1\r\n" + . "Host: {$this->host}:{$this->port}\r\n" + . "Content-Type: application/json\r\n" + . "Content-Length: " . strlen($body) . "\r\n" + . "Connection: close\r\n" + . "\r\n" + . $body; + + fwrite($socket, $request); + fflush($socket); + + $response = ''; + while (!feof($socket)) { + $response .= fread($socket, 4096); + } + fclose($socket); + + return $response !== false && strlen($response) > 0; + } +} diff --git a/webman/app/view/index/view.html b/webman/app/view/index/view.html new file mode 100644 index 0000000..67ebb26 --- /dev/null +++ b/webman/app/view/index/view.html @@ -0,0 +1,14 @@ + + + + + + + + webman + + + +hello + + diff --git a/webman/composer.json b/webman/composer.json new file mode 100644 index 0000000..943e2c3 --- /dev/null +++ b/webman/composer.json @@ -0,0 +1,68 @@ +{ + "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.1", + "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.58", + "illuminate/events": "^12.58", + "symfony/var-dumper": "^7.4", + "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 +} diff --git a/webman/composer.lock b/webman/composer.lock new file mode 100644 index 0000000..e531c7c --- /dev/null +++ b/webman/composer.lock @@ -0,0 +1,5206 @@ +{ + "_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": "0192d80f3a04355068fd8db0a847c863", + "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.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "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", + "jshttp/mime-db": "1.54.0.1", + "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.9.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": "2026-03-10T16:41:02+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.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/bus.git", + "reference": "9e70f409c744e0ba6b301fa608bb5b3c8c38669a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/bus/zipball/9e70f409c744e0ba6b301fa608bb5b3c8c38669a", + "reference": "9e70f409c744e0ba6b301fa608bb5b3c8c38669a", + "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-03-09T13:55:34+00:00" + }, + { + "name": "illuminate/collections", + "version": "v12.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "83313b009c4afb6f02dbc090bdb67809756eefa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/83313b009c4afb6f02dbc090bdb67809756eefa2", + "reference": "83313b009c4afb6f02dbc090bdb67809756eefa2", + "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-03-11T14:13:25+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v12.58.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.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "9abe9dba6b3f5220205ce143069c975a601e4294" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/9abe9dba6b3f5220205ce143069c975a601e4294", + "reference": "9abe9dba6b3f5220205ce143069c975a601e4294", + "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-03-16T14:05:01+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v12.58.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.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/database.git", + "reference": "3439d4f2d52558fffda33f6cd39ecacfde40960c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/database/zipball/3439d4f2d52558fffda33f6cd39ecacfde40960c", + "reference": "3439d4f2d52558fffda33f6cd39ecacfde40960c", + "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-php84": "^1.34", + "symfony/polyfill-php85": "^1.34" + }, + "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-04-13T12:59:35+00:00" + }, + { + "name": "illuminate/events", + "version": "v12.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/events.git", + "reference": "ff22aa6017bdbd90ace660f50638ed9c580ba555" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/events/zipball/ff22aa6017bdbd90ace660f50638ed9c580ba555", + "reference": "ff22aa6017bdbd90ace660f50638ed9c580ba555", + "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-03-05T16:20:14+00:00" + }, + { + "name": "illuminate/filesystem", + "version": "v12.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/filesystem.git", + "reference": "bae1dd6826fe0f4e74136847638b1aa8f2f39a9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/bae1dd6826fe0f4e74136847638b1aa8f2f39a9a", + "reference": "bae1dd6826fe0f4e74136847638b1aa8f2f39a9a", + "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-04-05T14:09:21+00:00" + }, + { + "name": "illuminate/http", + "version": "v12.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/http.git", + "reference": "9e0cf400cb81333520994952a164c737224ce9f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/http/zipball/9e0cf400cb81333520994952a164c737224ce9f0", + "reference": "9e0cf400cb81333520994952a164c737224ce9f0", + "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-03-17T18:15:14+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v12.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc", + "reference": "e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc", + "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": "2026-03-30T19:05:19+00:00" + }, + { + "name": "illuminate/pagination", + "version": "v12.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pagination.git", + "reference": "8327d828676654053906a771abf5eea5426354ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pagination/zipball/8327d828676654053906a771abf5eea5426354ec", + "reference": "8327d828676654053906a771abf5eea5426354ec", + "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": "2026-02-25T15:25:18+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v12.58.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.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/reflection.git", + "reference": "348cf5da9de89b596d7723be6425fb048e2bf4bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/reflection/zipball/348cf5da9de89b596d7723be6425fb048e2bf4bb", + "reference": "348cf5da9de89b596d7723be6425fb048e2bf4bb", + "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-25T15:25:18+00:00" + }, + { + "name": "illuminate/session", + "version": "v12.58.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.58.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "783fbbdc81837fcea6bac897bb817f6234cf1989" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/783fbbdc81837fcea6bac897bb817f6234cf1989", + "reference": "783fbbdc81837fcea6bac897bb817f6234cf1989", + "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-04-18T13:27:49+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.13", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b566ee0dd251f3c4078bed003a7ce015f5ea6dce", + "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce", + "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-04-16T14:03:50+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.4", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/e890471a3494740f7d9326d72ce6a8c559ffee60", + "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60", + "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-04-07T09:57:54+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": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/674fa3b98e21531dd040e613479f5f6fa8f32111", + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "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/v7.4.8" + }, + "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-03-24T13:12:05+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/d7d2b64a45a89d607865927b176fa51c33ddbb58", + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58", + "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.9" + }, + "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-04-22T15:21:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-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.7.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": "2026-04-13T15:52:40+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", + "reference": "8dd79d8af777ee6cba2fd4d98da6ffb839f3c0fa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^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/v7.4.8" + }, + "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-03-24T13:12:05+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.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": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^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/v7.4.9" + }, + "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-04-18T13:18:21+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", + "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.7-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.7.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": "2026-01-05T13:30:16+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "e0be088d22278583a82da281886e8c3592fbf149" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149", + "reference": "e0be088d22278583a82da281886e8c3592fbf149", + "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.8" + }, + "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-03-24T13:12:05+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "9381209597ec66c25be154cbf2289076e64d1eab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9381209597ec66c25be154cbf2289076e64d1eab", + "reference": "9381209597ec66c25be154cbf2289076e64d1eab", + "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.8" + }, + "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-03-24T13:12:05+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "23486f59234c6fd6e8f1bec97124f3829d686627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/23486f59234c6fd6e8f1bec97124f3829d686627", + "reference": "23486f59234c6fd6e8f1bec97124f3829d686627", + "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.10" + }, + "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-05-06T12:07:34+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "2d550c4758ba4c47519a6667c36553d535705b0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/2d550c4758ba4c47519a6667c36553d535705b0c", + "reference": "2d550c4758ba4c47519a6667c36553d535705b0c", + "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.9" + }, + "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-04-29T13:21:53+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "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.37.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": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "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.37.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": "2026-04-26T13:13:48+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.37.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.37.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.37.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.37.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.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "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.37.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": "2026-04-10T17:25:58+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "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.37.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": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "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.37.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": "2026-04-10T17:25:58+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "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.37.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": "2026-04-10T18:47:49+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "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.37.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": "2026-04-26T13:10:57+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "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.7-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.7.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": "2026-03-28T09:44:51+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "114ac57257d75df748eda23dd003878080b8e688" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", + "reference": "114ac57257d75df748eda23dd003878080b8e688", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "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.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^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/v7.4.8" + }, + "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-03-24T13:12:05+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/ada7578c30dd5feaa8259cff3e885069ea81ddde", + "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5.3|^3.3" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^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/v7.4.10" + }, + "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-05-06T11:19:24+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-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.7.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": "2026-01-05T13:30:16+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9510c3966f749a1d1ff0059e1eabef6cc621e7fd", + "reference": "9510c3966f749a1d1ff0059e1eabef6cc621e7fd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^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/v7.4.8" + }, + "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-03-30T13:44:50+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.1.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" + }, + "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.1.1" + }, + "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": "2026-04-26T05:33:54+00:00" + }, + { + "name": "webman/console", + "version": "v2.2.3", + "source": { + "type": "git", + "url": "https://github.com/webman-php/console.git", + "reference": "df5fffe07efd61d99dacacde9dd41a899ae923c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/console/zipball/df5fffe07efd61d99dacacde9dd41a899ae923c4", + "reference": "df5fffe07efd61d99dacacde9dd41a899ae923c4", + "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-03-29T08:32:39+00:00" + }, + { + "name": "webman/database", + "version": "v2.1.10", + "source": { + "type": "git", + "url": "https://github.com/webman-php/database.git", + "reference": "90abfaae7e9fde5f9a4265ae47396bc5c9ecc61c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/database/zipball/90abfaae7e9fde5f9a4265ae47396bc5c9ecc61c", + "reference": "90abfaae7e9fde5f9a4265ae47396bc5c9ecc61c", + "shasum": "" + }, + "require": { + "illuminate/database": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/http": "^10.0 || ^11.0 || ^12.0 || ^13.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.10" + }, + "time": "2026-03-19T07:31:04+00:00" + }, + { + "name": "webman/think-orm", + "version": "v2.1.11", + "source": { + "type": "git", + "url": "https://github.com/webman-php/think-orm.git", + "reference": "81fb87a085ceb2d25be5e86da12befbbbfe70c0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/think-orm/zipball/81fb87a085ceb2d25be5e86da12befbbbfe70c0e", + "reference": "81fb87a085ceb2d25be5e86da12befbbbfe70c0e", + "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.11" + }, + "time": "2026-03-25T01:34:20+00:00" + }, + { + "name": "workerman/coroutine", + "version": "v1.1.5", + "source": { + "type": "git", + "url": "https://github.com/workerman-php/coroutine.git", + "reference": "b60e44267b90d398dbfa7a320f3e97b46357ac9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/b60e44267b90d398dbfa7a320f3e97b46357ac9f", + "reference": "b60e44267b90d398dbfa7a320f3e97b46357ac9f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "workerman/workerman": "^5.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.5" + }, + "time": "2026-03-12T02:07:37+00:00" + }, + { + "name": "workerman/webman-framework", + "version": "v2.2.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman-framework.git", + "reference": "ce54d8f8f4c1f2c336293dbc37df1ea46ec34c92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/ce54d8f8f4c1f2c336293dbc37df1ea46ec34c92", + "reference": "ce54d8f8f4c1f2c336293dbc37df1ea46ec34c92", + "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-03-26T01:51:42+00:00" + }, + { + "name": "workerman/workerman", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "1d8694c945bc64a5bc11ad753ec7220bcba37cb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/1d8694c945bc64a5bc11ad753ec7220bcba37cb1", + "reference": "1d8694c945bc64a5bc11ad753ec7220bcba37cb1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=8.1", + "workerman/coroutine": "^1.1 || dev-main" + }, + "conflict": { + "ext-swow": "=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/webman/config/app.php b/webman/config/app.php new file mode 100644 index 0000000..f26e358 --- /dev/null +++ b/webman/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/webman/config/autoload.php b/webman/config/autoload.php new file mode 100644 index 0000000..69a8135 --- /dev/null +++ b/webman/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/webman/config/bootstrap.php b/webman/config/bootstrap.php new file mode 100644 index 0000000..ce7bfb2 --- /dev/null +++ b/webman/config/bootstrap.php @@ -0,0 +1,18 @@ + + * @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, +]; diff --git a/webman/config/container.php b/webman/config/container.php new file mode 100644 index 0000000..106b7b4 --- /dev/null +++ b/webman/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/webman/config/database.php b/webman/config/database.php new file mode 100644 index 0000000..e436e95 --- /dev/null +++ b/webman/config/database.php @@ -0,0 +1,29 @@ + 'mysql', + 'connections' => [ + 'mysql' => [ + 'driver' => 'mysql', + 'host' => '127.0.0.1', + 'port' => '3306', + 'database' => 'your_database', + 'username' => 'your_username', + 'password' => 'your_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/webman/config/dependence.php b/webman/config/dependence.php new file mode 100644 index 0000000..8e964ed --- /dev/null +++ b/webman/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/webman/config/exception.php b/webman/config/exception.php new file mode 100644 index 0000000..f2aede3 --- /dev/null +++ b/webman/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/webman/config/log.php b/webman/config/log.php new file mode 100644 index 0000000..7f05de5 --- /dev/null +++ b/webman/config/log.php @@ -0,0 +1,32 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/webman.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/webman/config/middleware.php b/webman/config/middleware.php new file mode 100644 index 0000000..8e964ed --- /dev/null +++ b/webman/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/webman/config/plugin/webman/console/app.php b/webman/config/plugin/webman/console/app.php new file mode 100644 index 0000000..d7bf968 --- /dev/null +++ b/webman/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/webman/config/process.php b/webman/config/process.php new file mode 100644 index 0000000..5121740 --- /dev/null +++ b/webman/config/process.php @@ -0,0 +1,67 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Log; +use support\Request; +use app\process\Http; +use app\process\Timer; + +global $argv; + +return [ + 'timer' => [ + 'handler' => Timer::class, + 'count' => 1, + ], + 'webman' => [ + 'handler' => Http::class, + 'listen' => 'http://0.0.0.0:8787', + 'count' => cpu_count() * 4, + '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 === '/', + ] + ] + ] +]; diff --git a/webman/config/route.php b/webman/config/route.php new file mode 100644 index 0000000..a004849 --- /dev/null +++ b/webman/config/route.php @@ -0,0 +1,20 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Route; + + + + + diff --git a/webman/config/server.php b/webman/config/server.php new file mode 100644 index 0000000..054d01f --- /dev/null +++ b/webman/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/webman/config/session.php b/webman/config/session.php new file mode 100644 index 0000000..043f8c4 --- /dev/null +++ b/webman/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/webman/config/static.php b/webman/config/static.php new file mode 100644 index 0000000..6313679 --- /dev/null +++ b/webman/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/webman/config/think-orm.php b/webman/config/think-orm.php new file mode 100644 index 0000000..479ff90 --- /dev/null +++ b/webman/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/webman/config/translation.php b/webman/config/translation.php new file mode 100644 index 0000000..96589b2 --- /dev/null +++ b/webman/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/webman/config/view.php b/webman/config/view.php new file mode 100644 index 0000000..e3a7b85 --- /dev/null +++ b/webman/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/webman/docker-compose.yml b/webman/docker-compose.yml new file mode 100644 index 0000000..352871e --- /dev/null +++ b/webman/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/webman/public/2026-05-10T170440.200.mpeg b/webman/public/2026-05-10T170440.200.mpeg new file mode 100644 index 0000000000000000000000000000000000000000..c4fd6c6e10102fd8637060698d7c3b78a21cca81 GIT binary patch literal 12528 zcmd_wbx@Si!!Ph<>0V%IkZz zNEROWPANw7c|ZSA9`%*lx!8&+c?JU}U>PgGojyudmgOVdiY#MhJnR$yR66Py)NG74 z`Ofj)}Vz}u*~uY&F%y(OxfdvV4crq?8F%57B)Kf=M&QU_ARjaSz`Re!w6WjE)UyaXixLEe1`muU>ofFdayf2m5{U zVaLF-<^|kYbb{9(yPpxvy6_S8=}6rE?l_F$c7GD*_QaHI@WuQoqQXl8ecf{IXun~_7 zD9}9w(nVn(`bn<@6@79OY9iyK?&=p9p`Nv?5NmZQ>&DEcNs|*)& z)JC{sJeT#g%$j3-ygwgG!h;Y%2}f}Es8~#)I4bM!?}s5RD8(O@pI=@3+ZM_wL~VOo zcx}g3hyn+Paf@$$zvzoP6@c0_GJI1?8|ued%i<`2XP;=Np@jx>YucvS(>L=jNCNN( ztjQZNuSI)0zRfq-eb6Cc0~ydTvmII17J?IjZw-II=a*4v3WF@$JG8ejO?cM~tZWdz zat~{>Sg9#zu7Eqhg|FEUABor$Ar{wO%@@3gS;Y{-q(!UOLR$C~(Ky8@*^!`@+=VFc zTLTJ`3VMt9%?S)KTdNcBQB)H&_pv`XlvlKoyS@H;IJ29+4Duq$k1?r!tsp*{~A$w#?8c ze6HVJ^u-J$4Af1)MgDqF_zgo*220YslwpMdHqZ+9NAPDuv&Y-hB!{S3=Uv&O#%TGX z%Dyd0`*d}iS#XCAWX@xH7^N?L=kw*F;XM1ZYv&uKAMuu@nhynO;gB-m0FM;X4Qvi_ z9}$m@;ow1Jp8f9DUGYRPNBCr1>is+j4++l5p%Za-%`-sV;s5w!8*pGq*r&J$s^>+5 zjEq`4q(}Pb?xjmL{b1|&xeJIiS^Ez)PZ-swHtLy;A`%(TbtsMABP}TD#rB1Hu}T}G z+-SvTz6lgcf~O|qIu`r#u>m&A0FxXd8+IYI4g7+*Az}akU;{WsZnm!ln_HGkByl{A ze}*KuXowJ`ERW<1ZxqnP%Ytj;J(&i(&u{Tv*&eoM29!8^6~`)V0KN=^ECt zw^tjr9q3WJ3ztYitl6U`*04_JhFU5jzA?hD&A1Dr&kLTiS{&e^xgjkUXw>VO^#HWU zYkCNT1jF+#M+s8BT!6eN9ne)vruEky4+-L3Mf>WZovpVM9%R|{Ix zz_N~A{mlIESkRZvoy6XJ#xD7zNj31%Kh_HN4%H+7r&g%cMN-!_A1EAY@k5h}CLj4m zU%|EHP-#BhEzoQ9^uF0OCKChp#fEZjrBmWjdN3S+r^p_jNh9ThV+WyF zwhZ@u!vvkHDp)lQdkVG%9S-(NN31j^W}tRuPG~32@mIM|E}l=IHYb=e81vzLErr%w-JC zdbA8Bnc>4B)thl(3;D9gn}*w8`#^IwJ@&rezuo79-ddb!vU*p;*yLnC_mZQFWJRn^ z-RMemTH=(v^YO_YtVs&d+mzSC#-;C0SA4tm% zI%BZZ=@`c8Z$`0ElvpOkc2wi7Q^{+dFc>7x0u}_WKt?i}A{2hK+%OXgbsm=;MyYb_ zYl)yY^Ow1FeKGXr7Af-E9`p0>QsIma+NfVV$O;a!s3{sA6EqwCeMW%x8BOt3c@d=L z0ac#ZGxZ-JU@Wrib>IJM?Qq(1vcK>CCC)+%gL!`noTMdV1JVxE%?8_orSv(A< zSG{-@uU~(#S3TganHd*b$0~PMHDm7r9!3GpquS5ZaF^Mdr0YsNV^S8TgP;LVm+3+a zA!VecPh$1$)D$cyj3z}00{ae2mtqr9F=}}Ri(WZ69E6>a^B5F*^me0wiYJc*9Wo}p zY@b*w>1Fdljp>6$>M`{iL-~FtjoO(#$|SoVHuqmpH>PAh?f~W{qqY2EAOd1E2Spi&_ z;CzhWwfHwyGDH3C4xja+~BM}`i?2{C#C9;f1A&&=D7)IZL$4aq2P(Zc%*;+6`P4twSbv1HJk+W|v6a9(Qe(-Y;J zc=qCXaI?4@m2r_XZBBVJ^X$;#qyY=$DfN8d#;JQYJ zFiKSS<~1m~9NP#^-{<{kq{Rz$OkV5sk6kVry(=6)GQ3hWyY172)WDBOjp$>)1hrk^ zZTT^m*DViD>*KRWCo7|RyB+EJ&Vm|8rCSYq!*e}KuD?8!ny(7`t+?k549V|P;oZN9 zS;UXD@4^s-aj#uUZKPD^?IW$HRI)S0LB49&0(5UlIU33GcF4CGby+LR?gpCo1Jr-F zOB|(`vwEP;Zui2Ou--Oi*M$fAeLzv(wjBhZ6_X9#R$RznyQ%+Kh9LBRF>`rFVKJpn33i>$PiFuxnt|^Z> zy|!JEY4Dj&>%>S*RpjUK+vE5DJ1zJ-w_$#V^ByI^5>&{$i%xPUKBu;~kn+TdCBa(K z_A=h!^2uoZHTBKtDfbIA*#Q@F#t(L7t3_<4#SAE|vnU)%QL>Rb_l@JA3@} z*Ykiy;lyoPwjb08GvJ`&Gk80knF3;jjkIvcVld`V;vzw)S_&@&LLf#Hm=!ysoIi&z zY==Ya$XGm{b~Wn;X#ts%_1RQdfw7NmF;Z+zgE(Q=&0QwWlE7V(x)(u5l-S#e_IUs( zcRg~uE)AO~m4|Zrfx1!EBGILnD|D3E|2qyn7l7UNo=bP&vh#lkOghk!ZGqDi{S zD**POQ_L*r!=uGa~ll` ztR`VcHYRNaXf^2TYzw{q4fu_}@s-CcSD9%meeAAuR#Q*=M_*VlU_d*bnb%Ry*QEd+3!u zK7P`nv<>n|+CG)UaK`!6!=eZtZg(_vT-p_{l0bL(dbJdw8N>KK z(xbr8ZM?d2x2}VwzD2SH`E_zMXNS3cGWD!=x`iqJ4bx&->}CtrBruT}6PnXwye*lr zn29H{=meZ_M~;+qSD6H(U;8~5Tb5ED-0~}&;A=}MYP`WQh zzv%PLeX}54pBXlw)gcSd*k!`~h2;Ba zWd){g;-oU004E~p&0YSVn4sdWs!O>1(S>PC>txu!u~G-BJu*x4EL-SH`ck%IaR!K- z!JEh_?7Z7wt+k-|eF>c58QoP}A2_%|wk1Av8psZXc&~?Sxs7p$9ltEV@*dv>o=Z)Q zjLg*KRFzz$w!z-odFhGwSPXj&bH16urfs<~2MOr3|LZp_NX0cy%|k|6`%x{QQYA0` zb{n=!JYY}CSub8K!%#0@b&Tno_HkKap=S=NxfPW#SRj1V10p2f>1zw-nZcK$Z?Q6T z+*HJt*6B86@Sf>`vZK&bvn+6eyh;w$cz>>NhEmBTJvqWemZ2jlXgen;-hdm|zOvVl z!!cnJ1Sm_VL2{ZV4*{KzRK`eU1ykqpT%MiMP1Qv&@%E#S%%=!kdF^IfcIWk z{!DIds5x7k_+%x39c(?O!KejV!|F4ECY&%?ek2N8b~PKBvqoCpr1UefLF>j^4A>MV zj1lipz1#Y)7s9UdU=!h@t_O*G%0ORW@)0a}c|OH*q=^a>1qANLc=?WitQkZ}j}i$I zw>BYL^`=0XeS)@}q1vLo749UTJq#kA;P@ySZBmVBN)ym-E=xjMK~TPno<}LmIygz8 zIO5_a%c=-u|A*LJta-( zE?M*ajUlGvH#b{20RQ3ngiinSvkeh=_={0|n?fy+nzyXdqt`Q4NGldP)ma=Al&shN z;xP)!>?fdptkKd}V;0(r1)GPwN0iA1loJreUtsxI7m{XCh{eZuS)7d4_cCeP)>0kS zJ*4jaUgRdKMfRp_U1u_k(=c;KB`gH=65(l zNdaj5;#fvV)Ech)m1nC!W$H`7!mEX;h}MU;Z$wgi?=c>y^A$Q4!FB?WRv1|!q9mwK zE~g7I-2drVZ~!$S*W%?0g<=F|TDxVIoIl^u_Mn7+3}@FIB}qJ1#2hLuNFyMqHX8K^ zQwl(d#KFtm`2B3AUI4n!VC=q>rt4AC*;l0Io>}$%urhpN%j#VR#b)&9UZfR8(KGV7 z(|2(ZOWa3n?Q60~r}wqD%PKuOZ--QG(AmB+GtQ;y7sp2d} zA>8GO#=Z^xm}=9weKy?Wbc5X8>#O0A8#h6QjG7e3mI85na^U1x!(gK{KO5^9X=yTB zEf0MCFkU`#!_CDwgR^Jwq9v`D+AV0GNL)Q(PR)SrbKiH7#vTdYA zL!o@JZWMqi2YY~cqCHJ%8C)llV;|sgesy_O5b5rzS^wbRsi#)0d!0gD8|j-LPB3Ju zRR&B|*#Z=w5S71goNSHD5{I`vzfLYymsB*OK|EcsV$rs*t}&)~S&xraiFNpYPz#$-4kz`M9*#FGmg)z=TRpfU6kV8K2*Bf-HR+nk!Bk|a<#4y1zeTlVY#vs zBe_?IG?d~-BM_r}y!Gm7Q+RfOAXy^!$Am0;5Vqagvz{%!YvNvQ)jWVi9GxsLQ(Gj>=Sb;BcHg5pCh|sv(mS6z}X_X9@ z>jIVp2@zMlI~*)LGIgj&VTM_;2x)jw!rPad!Hu*BA55h^Xadzi$6C@7Fy^D%=Oh3Jkl{ZNn*$M+Ha`K( z{c7upk(L&zG@|%XA6`|6w0dZ@Iq1ej(Er7$` z-BS1rv&#BjaQ>6!hHsSObwc*p#H>vZ)A@CSx0cSOw*y!^6I3MeGqwqqClFi&@v ztcP`tw2Yyn3MHL>(T$dUBf8ku_MX~$xjX?w@&QJ7g^%~oTvFD;jy@*thzhwuD4-&u zLVA%#(+8mVjfedQ9T#}^s20US* z7+bVqTS&{61y~=%_2~(&D5ddO|?>X#&tu z=u1?kk6J50kOFH#c2RnccbgJ+sx7su(0e`Cbb06GZ;v%3Od2M!g{pRSBj*-bpQm{_ zx3w6DaB{;C=ST~e><@U`bY0H>%@&5ix_^qa{lwCTPce~$=wu94`)Gss#9e@iyZ%$U z&L35KIROE{s1J8T1PNWa^z2->C2Ut}1Pr1vd%Dkq88kj|J9VFlkAt!uI}c<&edB1_ zexy@HR$;J-evpT>2*|;V2vdSm)>`>aue7>n;ANCgL_D{!?hl&G9g5FsaBWOfj$YH8 z6Hvwk!bqS}e5_LGm;ng>9nvm7!|SY%@MOeixV>KXhY9hcw_HP%qDdYj4to@h>6G7% zW@=E;sOXE62NoTjkQNceUB#YhrsB79=r0XSiUHPN6_^51NuRKhN2Z-T(AlT9;MAs?Fan>PKe5Um_na`gy%Xrt%Eub@|8dU6 zmOl~8mH&{<(*OEFf)_vx7fMK6%_*au0u8bW9wdLx4i)@;8>@a=73zL6VX8`eb4QBv z3*ZmxUbNVK>W97Oq}+=3FBjCqI96|_zdxE=?3+(vg~%qS(jcl`lefRukDp3uoOg*h z>xHOHbh_b!aPvQ_??ja+{3!m6F%)F-^N?vYtQnrI+e#t+K3ShbjvLo0WOW6`@2LA{ z&fd-*6!(z^>2|Dk1@>`w==diz2x1DX#_2V zC!gcQ=Lm+zu}bM2iFeE7!1j0gk24aofIu$AGoKIU5Yqo&wEGW4E%RVNhX` zn@b2kl#}%#C)vcwshHcujPBD^l1k3pQ-#yf)B`y`Qudd1o)Pmm)!f(f?e`}ecA#Jo zS2|H(4~opoK3d+40CAgFLeg=ENQ(wqc7|cjlPNx{M9k58%`VN9eL3Lr{0v;|M|THq zrT6YNqT_uW_YM{HmeG7gO+YiM{>#}Yt|Z;x?kEK!8u}e*CJBK*x*MhRW*PFnlbYG0 zCw1l;dO4&D4k^Y>sj{4@acLq+ow+sQpVq+hM>yEAr=w?mI5!X^ZV1H2;^V#LjP@P%X%^4rNgh)Pt(# z^_GmHvz`l&s7Cd zT#peq)fcmW-u+!TX2Cii_Fn;ioMXtc{dlD+EAZ{Hxx(1AI6K@KI`2y}e%b$w(_InQhT zMuq1MftQJS6CeW6NkEF>`{Oc@#9u_a;7=w19&BV@<|~5?BRPvM_J~7aLv3Nzm$y{7 zEBZ&@+urwA(|5~Oq*}#}PZdcQ77}SKrV14}YBC#%A}t}3#HJn8NeU376s+H3S6@v={qIX&kcXd-kB49X|L8(mav%_@66o*s-{0Z?idFFc zw}k>iL4zn9VJN6ZK^E^S#pF?>N(|{YID6*+c73-X7)oofMoDek2Q?ghuG7P;laXi;U9ZjqJbu7Ds6PMk3!z ziUD^~{w+Py0;qpXFz=>nfr39D2$Pt#{jPsWbs^)W+*sBsHHQ~YkkaV`b*`0G+MSDW zuZk_C>*^(oE0N~p+eucY|Jy0INMfA)u(o}Pkq3voO#$+pJD=FY@!>nG;Q>>Gv& z`xrz=Dm5Ga?Ten0j^tTdj8lLvS~|H%wzI?Y#b=M}zT==<}Y`-!uOlMB`uS zUCa`QdRbsXOBWu>Z(c05_F>OF>IE*3{dqH0`BwL9tS=WBTiisql(v})Rz$UtTS|9N zIZus_ip5}4=7#^q9?cg>=U?w#Q@7CZd}xVZG-VhAr*w3U#u3E_bwSLE7wIU{(VtoC`7>9CpiO>%~{Ny zpjcWc^I^YolwDs^vq%MsUMw!YW&mq(;4-tl(cx{VrQ0cf``NoYC^cd(X<3t_bsk}3v629c}H75*K{c;B`7E+xUu4_ zF(zE2s_&Cvyz*eGcZ65b=5Q(O;=1Tk6{XN`NeE63_=N7BR6%GEPLb(!K4_|q&+!g? z&SLF`(Q>v^U#bj?#phm zvt5)T^E!sMcP#V*!Zqb6sPZmw+b7SJOf-zTD$#r7DNlShnS2nz^gPUQ>LVcf)`=g% zzAZoAO&&OM-9DVriMtuPO{?EOokix7A?4`Zr=1i9`4=+PVMVeRq;$>Cn+Xnu6XO)5 zfuzwMKx=vJ5xbR%_ZF0>8q`80&MF$uNl&v9`j29YiE0O zqnCjpOd)Zh5uq;<8ekNw)|6cIj|&)LUy;RH+s(-1!8w2}E=^#l(!=A;8;VEl1tk)| z&$9HAD~p@N|as}Tq>9(!wUUbtA(zu!A@1sq~q)2O|Vp_Vyd@kC|g*L6<F*(|b~N9#zQccKU?5O`;XPA6MPV1K;hB_Ii?3GZcxca) zB`o`R6wMFeN(I4Nuew)%+d0%Io)g7!u8Rq;x6*o=eUl*Vk4JgB{L1Jf1RCc2>iaN0 z2O;ctr$UnHM1r9?Th)RJ{j;&WLsgG})N8vPJ0WB)TIg`~j1fWV(Odw_%y_oA^;aG!J~Sqv`nW09!3M=1X|a=V@JuHX*vWl(;yf!rFZ&v| zj8iwLQ8}4g?Zd72WcORk+J=^@t^&P%3?ugv0U|ssdIyZ|^%3Jkx^CeN6I$aWeHmeT zgYb?x1^4=HZJDGWLsT|Hj}^NXf?aTMRN)M*U6-uDoFmc_ht{_(ymm672R@SnV-_bt z3D?aXK0(oRzqP+N&cDI^)ZtSw&Bn&hsiie$C!#YJ+0$Nd*wz&F5q?l`vmI5>+dC%O*gwf=e?#IR2 z?+-Mxvg&Ovv02uhA*dF`CsT=scp>6ukG<=}h51=)!+A%bDuET!^u5s=yOv{sBEFWu zK5#Ua9p?tmMTph>B4(utm6>P6M`d=~s>l!XO(lD=fl%e9L0Y=dp5%c%qu2}Gy}7&& zzF$n{XIl5+sHRHiD$(m$VB zouJwoy-tYX1j{g>cjJyj@Po-Pn%pD{GBKGxb`aMfx>nqTid!81?Tfx59zLxC5i4@n zStj~9zQPe`(WQ{Jdmn1p{yjr1BFpvJVGG>=NGkIAXX>sJ;>6AGwL=^zCNSXm?_Agg z`gdxdT442MZm~tUxQZh~V2QhmzL1Vblo)hQD^48quBX>~j+cP|%z%f?1tSsT`KE=t z$efybUj5qFvvsyVmixJ^&o>I!agj68@gl~%FNMM!usc%`S;GhePkNO2bBwNgn&1UP z#nmxI+_g7k3ib!JM83yIVZPVBXzZ2Bwedh+82>HhOu6)Ym`NJckORU3Se3awedpAMOS&{=-c+(`KrBjwU%*mH zUtEzYzDCDZ{6VY?h|z4=Xfw~?flYRG%r4D4qxE+TW4 zK>Y}|5xP^{lr(FJn8mATHGiD2Ao6>8INtr&`=}V-Ipa@n*dJa5-HMBBV67!TDpwfn0~)woU~-e9PhN21LM! zwCbV1Sli4c#-OcAC~=cXDtw3#2B4T+v=#@Tz`4Ys(6%3>(zooHrlk1xz^-kjHt&$p zVoxoZg@8-#2-Ial>UA>hA7_>EC2ejqavLo;`+ZM;3rY&Uuoz@mjkFx43FEV3f-zP3M%W{;`+(gqC zZvL&m2BAL@QJpUo)F{xPNyXaeGFpaF8Vfl6(o;fyIdUxMGNvl8aUv5?HG2$$3in~< zw=tw^#I67POC)5j86s^QHR#Y-;Qxd5*AbZq4m-GS4D#Pw|Km@Qx&E_G`5)iyzrVr% E0+-|1F8}}l literal 0 HcmV?d00001 diff --git a/webman/public/2026-05-10T172805.200.mpeg b/webman/public/2026-05-10T172805.200.mpeg new file mode 100644 index 0000000000000000000000000000000000000000..962c9d083efdf3bfdfae62b628a2f5c549257fec GIT binary patch literal 9612 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 E0+-|1F8}}l literal 0 HcmV?d00001 diff --git a/webman/public/2026-05-10T173840.200.mpeg b/webman/public/2026-05-10T173840.200.mpeg new file mode 100644 index 0000000000000000000000000000000000000000..8d42b5d05e452d7081c9414f711c4095f73d42d9 GIT binary patch literal 10476 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 literal 0 HcmV?d00001 diff --git a/webman/public/favicon.ico b/webman/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b9f722e0eeed8dc7f3639bd3295a7b0376b98eca GIT binary patch literal 4286 zcmbuDc}!GC9LI-{lvLYdn`%sIykgTPHU44S)cR&9&1g0})ukRu+b zsI4iH+Tum=wxm?+g|1x7X%O&eYs0dFEXuJv?|~SRU%$V%yN_jIWzi)cX5Q?Z`F`g& z^PBfT2tWLcA20Z~I66XzQ9_8f0h+{zfZMeXzI$!B14qNqe+Ms>0t^d|k|#hpcn^q~ z;CVg3dWG2j%ifLf3af($0x zFqHZ@&|>{}K^tIw<{IEO5%LUi;PHV0JOSi}L_G^kF%-24L#d6mnNZLTT>4y=fJs32 z>T3n#v;Jb>u1$s#{kl4$sCDYsB{mVVOo|!b%Y>Or`)Ip-WMT~hhU&qo|1rvkx`)Dc z3eN*q@6-It4HR~%KIfv@I@xr$`J)wSwu_-_>;xY!T~{fgJ^dN=dHt(3eaw6QQ5(&Q zwbSe!_bDQ$p3rC#TC=DQ1t2=B$w95sS3oCG8?O+V{O^KwP2A z#JKe59cj_##AAmi*eL7j1FC3lq3dmSsf7^;4_WBvlMm=;#>pmNa_ zbN6mz6D^FlYx?YA-dafqFMD`^pBqx8l17ELWJ#L6Tv|SjloLN+GW0x%FxdnZY!{J* zO>bu3zE@n@Xrm(g1kiF6>)}|NZnR~9*Ma-cgVuwiY@)K+DvIix zgRsv#uD3~jt+NXh3XhXo&T-yFZlPoKYP(QS z*TQ4y`W)ZW)fHq&oMLL^ISVq=dQNX$&xlL$K1x# z*7(XDl{RE3ywB*nWQl#}H3ly4Cosk+Zyun@=e8?%w36eyYL}hLt={+9v0{aOIj?AY z&fVhxb&f+O2n7A&0BwGLmwI}bwM^2!bF$w)ca6_E*FOh3kc$sBYV94a`E&Ugq}JFv zUI2YO@bePdQ$U|M@LuPAo?E48`R&s6T0+lcHq#kXA^lZmqVmQj`0L|ALEl9{RJ+9? zBwEvF5AT3{ps$4w`&^&nANhHd5c>@!tuxTZHQ}@=WhEJJ-SqVEFZ8Fd2lo#&S)Z8* z_5ps5X+Ag(Ntt}s_vQln>$*J0l6s4#{qxQkI&ok-*hafkmQvcTMCbFO_kQ%<4_@^2 zzeWJ+2eNu`#%p8DK354;Lol7p6fud@%wulH<1{44HRY0Ks{v&TtXTdEpTWA|O34LUvWxqcXMUTb6W9?1XuHtqZ&nG)l^rKAmuHEs4) zB@AIt%@K#v30S=-d0QdDYxZT9iy zD*2z!VqR{nZf6e)`*;iRdk3Gh&V8v;dak0+*M06>|6-+l@VPb}Ttsel z@|n4OhceLL37RLIYdu32dP)cEJ5k+^SlHD-nud)91PZ{o+ zzq%#)Txl0+$CLm(L*M6p4y`kYcOBka9)liSH{d#+x;9dMR}dLv$B}VbAQ?vokWn}= z3x8s4pi9FKr63Hx5d?v8ATSXag$OVU;co-o_?H7Z`k|xHI-r3NX+lrJ=!7o`Ul1Dq E2mbpSrT_o{ literal 0 HcmV?d00001 diff --git a/webman/public/http-client.cookies b/webman/public/http-client.cookies new file mode 100644 index 0000000..edfe326 --- /dev/null +++ b/webman/public/http-client.cookies @@ -0,0 +1 @@ +# domain path name value date diff --git a/webman/public/http-requests-log.http b/webman/public/http-requests-log.http new file mode 100644 index 0000000..f654a4a --- /dev/null +++ b/webman/public/http-requests-log.http @@ -0,0 +1,643 @@ +GET http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通事故、请谨慎驾驶。&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T173925.200.mpeg + +### + +GET http://192.168.42.4:8888 +Content-Type: application/json +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* +content-length: 381 + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "t.mp3", + "uri": "http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通事故、请谨慎驾驶。&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008" + } + ] + } +} + +<> 2026-05-10T173922.200.json + +### + +GET http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通拥堵、请谨慎驾驶。&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T173840.200.mpeg + +### + +GET http://192.168.42.4:8888 +Content-Type: application/json +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* +content-length: 381 + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "t.mp3", + "uri": "http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通拥堵、请谨慎驾驶。&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008" + } + ] + } +} + +<> 2026-05-10T173821.200.json + +### + +GET http://192.168.42.4:8888 +Content-Type: application/json +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* +content-length: 378 + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "t.mp3", + "uri": "http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通拥堵、请谨慎驾是&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008" + } + ] + } +} + +<> 2026-05-10T173808.200.json + +### + +GET http://192.168.42.4:8888 +Content-Type: application/json +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* +content-length: 378 + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "t.mp3", + "uri": "http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通拥堵、请谨慎驾是&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008" + } + ] + } +} + +<> 2026-05-10T173751.200.json + +### + +GET http://192.168.42.4:8888 +Content-Type: application/json +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* +content-length: 378 + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 75, + "urls": [ + { + "name": "t.mp3", + "uri": "http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通拥堵、请谨慎驾屎&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008" + } + ] + } +} + +<> 2026-05-10T173731.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 266 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "G.mp3", + "uri": "http://192.168.42.168:8787/2026-05-10T172805.200.mpeg" + } + ] + } +} + +<> 2026-05-10T173427.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 266 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "G.mp3", + "uri": "http://192.168.42.168:8787/2026-05-10T170440.200.mpeg" + } + ] + } +} + +<> 2026-05-10T173355.200.json + +### + +GET http://192.168.42.168:8787/2026-05-10T170440.200.mpeg +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T173349.200.mpeg + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 266 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "G.mp3", + "uri": "http://192.168.42.168:8787/2026-05-10T170440.200.mpeg" + } + ] + } +} + +<> 2026-05-10T173327.200.json + +### + +GET http://192.168.42.4:8888 +Content-Type: application/json +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* +content-length: 375 + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 75, + "urls": [ + { + "name": "t.mp3", + "uri": "http://192.168.42.139:10008/tts_xf.single?text=有车辆上磅35.5千克,请刷卡。&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008" + } + ] + } +} + +<> 2026-05-10T173315.200.json + +### + +GET http://192.168.42.139:10008/tts_xf.single?text=您已超速,请控制车速&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T172925.200.mpeg + +### + +GET http://192.168.42.139:10008/tts_xf.single?text=前方大雾、请控制车速&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T172914.200.mpeg + +### + +GET http://192.168.42.139:10008/tts_xf.single?text=前方危险,请立即停车&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T172852.200.mpeg + +### + +GET http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通事故、请谨慎驾驶&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T172831.200.mpeg + +### + +GET http://192.168.42.139:10008/tts_xf.single?text=前方1公里交通拥堵、请谨慎驾驶&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T172805.200.mpeg + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 266 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "G.mp3", + "uri": "http://192.168.42.168:8787/2026-05-10T170440.200.mpeg" + } + ] + } +} + +<> 2026-05-10T172147.200.json + +### + +GET http://192.168.42.168:8787/public/2026-05-10T170440.200.mpeg +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T170834.404.html + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 273 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "G.mp3", + "uri": "http://192.168.42.168:8787/public/2026-05-10T170440.200.mpeg" + } + ] + } +} + +<> 2026-05-10T170816.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 274 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "G.mp3", + "uri": "http://192.168.42.168:8787/publish/2026-05-10T170440.200.mpeg" + } + ] + } +} + +<> 2026-05-10T170747.200.json + +### + +GET http://192.168.42.139:10008/tts_xf.single?text=有车辆上磅35.5千克,请刷卡。&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +<> 2026-05-10T170440.200.mpeg + +### + +GET http://192.168.42.4:8888 +Content-Type: application/json +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* +content-length: 375 + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 75, + "urls": [ + { + "name": "t.mp3", + "uri": "http://192.168.42.139:10008/tts_xf.single?text=有车辆上磅35.5千克,请刷卡。&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008" + } + ] + } +} + +<> 2026-05-10T170351.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 559 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_append", + "params": { + "tid": "234", + "vol": 50, + "urls": [ + { + "name": "G.mp3", + "uri": "http://192.168.42.139:10008/tts_xf.single?text=等你写习惯后,看到$就知道是变量,读代码反而会变快,也算一点点小补偿吧。&voice_name=xiaoyan&speed=50&volume=100&origin=http:///192.168.42.139:10008" + }, + { + "name": "output64.mp3", + "uri": "http://192.168.42.139:3721/chfs/shared/output64.mp3" + } + ] + } +} + +<> 2026-05-10T170342.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 81 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_clear" +} + +<> 2026-05-09T174619.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 129 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_setvol", + "params": { + "vol": 60 + } +} + +<> 2026-05-09T174345.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 81 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_clear" +} + +<> 2026-05-09T174339.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 129 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_setvol", + "params": { + "vol": 40 + } +} + +<> 2026-05-09T174302.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 129 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_setvol", + "params": { + "vol": 40 + } +} + +<> 2026-05-09T174255.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 81 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_clear" +} + +<> 2026-05-09T174248.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 129 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_setvol", + "params": { + "vol": 40 + } +} + +<> 2026-05-09T174237.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 129 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_setvol", + "params": { + "vol": 12 + } +} + +<> 2026-05-09T174217.200.json + +### + +POST http://192.168.42.4:8888 +Content-Type: application/json +Content-Length: 128 +User-Agent: IntelliJ HTTP Client/PhpStorm 2026.1.1 +Accept-Encoding: br, deflate, gzip, x-gzip +Accept: */* + +{ + "sn": "ls20://0202AD1AE849", + "type": "req", + "name": "songs_queue_setvol", + "params": { + "vol": 6 + } +} + +<> 2026-05-09T174207.200.json + +### + diff --git a/webman/start.php b/webman/start.php new file mode 100644 index 0000000..41ad7ef --- /dev/null +++ b/webman/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/webman/support/Response.php b/webman/support/Response.php new file mode 100644 index 0000000..9bc4e1e --- /dev/null +++ b/webman/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/webman/support/Setup.php b/webman/support/Setup.php new file mode 100644 index 0000000..99320e8 --- /dev/null +++ b/webman/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/webman/support/bootstrap.php b/webman/support/bootstrap.php new file mode 100644 index 0000000..9495666 --- /dev/null +++ b/webman/support/bootstrap.php @@ -0,0 +1,3 @@ +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/webman/windows.bat b/webman/windows.bat new file mode 100644 index 0000000..f07ce53 --- /dev/null +++ b/webman/windows.bat @@ -0,0 +1,3 @@ +CHCP 65001 +php windows.php +pause \ No newline at end of file diff --git a/webman/windows.php b/webman/windows.php new file mode 100644 index 0000000..f37a72c --- /dev/null +++ b/webman/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); + } +}