diff --git a/app/command/VehMoneyCommand.php b/app/command/VehMoneyCommand.php new file mode 100644 index 0000000..0485e09 --- /dev/null +++ b/app/command/VehMoneyCommand.php @@ -0,0 +1,29 @@ +service = new UsedcarCrawlResultsService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } +} \ No newline at end of file diff --git a/app/common/controller/UsedcarCrawlResultsImagesController.php b/app/common/controller/UsedcarCrawlResultsImagesController.php new file mode 100644 index 0000000..5560fda --- /dev/null +++ b/app/common/controller/UsedcarCrawlResultsImagesController.php @@ -0,0 +1,33 @@ +service = new UsedcarCrawlResultsImagesService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } +} \ No newline at end of file diff --git a/app/common/logic/UsedcarCrawlResultsImagesLogic.php b/app/common/logic/UsedcarCrawlResultsImagesLogic.php new file mode 100644 index 0000000..428bc16 --- /dev/null +++ b/app/common/logic/UsedcarCrawlResultsImagesLogic.php @@ -0,0 +1,17 @@ +validator; + } +} \ No newline at end of file diff --git a/app/common/service/UsedcarCrawlResultsService.php b/app/common/service/UsedcarCrawlResultsService.php new file mode 100644 index 0000000..5aa1cdd --- /dev/null +++ b/app/common/service/UsedcarCrawlResultsService.php @@ -0,0 +1,31 @@ +validator; + } +} \ No newline at end of file diff --git a/app/common/utils/crawl/service/UsedCarCrawl.php b/app/common/utils/crawl/service/UsedCarCrawl.php new file mode 100644 index 0000000..7e99090 --- /dev/null +++ b/app/common/utils/crawl/service/UsedCarCrawl.php @@ -0,0 +1,857 @@ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Accept-Language' => 'zh-CN,zh;q=0.9', + 'Referer' => 'https://www.dongchedi.com/', + ]; + + // Redis队列键名 + private const REDIS_QUEUE_KEY = 'usedcar:urls'; + + // 能源类型常量 + public const ENERGY_TYPE_RENEWABLE = 'RE'; + public const ENERGY_TYPE_CONVENTIONAL = 'CE'; + public const ENERGY_TYPE_UNKNOWN = 'UNKNOWN'; + + /** + * 插入队列url + * + * @param string $url URL地址 + * @param string $scene 场景:usedcar(二手信息) config(配置信息) + * @param int $id 记录ID + * @return int + */ + public function pushUrl(string $url = '', string $scene = 'usedcar', int $id = 0): int + { + $data = [ + 'scene' => $scene, + 'url' => $url, + 'id' => $id, + ]; + + return Redis::lpush( + self::REDIS_QUEUE_KEY, + json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) + ); + } + + /** + * 获取队列url + * + * @return array|null + * @throws BusinessException + */ + public function popUrl(): ?array + { + $data = Redis::rPop(self::REDIS_QUEUE_KEY); + if (!$data) { + return null; + } + + $task = json_decode($data, true); + if (json_last_error() !== JSON_ERROR_NONE || !is_array($task)) { + throw new BusinessException('任务数据解析失败:' . json_last_error_msg()); + } + + // 基本字段校验 + if (empty($task['url']) || empty($task['scene'])) { + throw new BusinessException('队列任务数据缺少必要字段:url 或 scene'); + } + + return $task; + } + + /** + * 执行抓取任务 + * + * @return bool + * @throws BusinessException + */ + public function execute(): bool + { + try { + $task = $this->popUrl(); + + if (!$task) { + // 队列为空,任务结束 + return false; + } + + $url = (string)$task['url']; + $scene = (string)$task['scene']; + $id = (int)($task['id'] ?? 0); + + $html = $this->fetchHtml($url); + if ($html === '') { + throw new BusinessException("页面内容为空: {$url}"); + } + + switch ($scene) { + case 'usedcar': + $info = $this->parseUsedCarDetail($html, $url); + $insertId = $this->saveUsedCarBasicInfo($info); + + // 下载图片 + 上传 + 入库(图片失败不影响主记录) + $imagesArray = json_decode($info['images'] ?? '[]', true); + if (json_last_error() !== JSON_ERROR_NONE) { + $imagesArray = []; + } + $saveDir = public_path('uploads/usedcar/' . $insertId); + $this->uploadAndRemove($imagesArray, $saveDir, $info['used_car_id'], $insertId); + + // 继续派发配置页任务 + if (!empty($info['car_id_url'])) { + $this->pushUrl($info['car_id_url'], 'config', $insertId); + } + break; + + case 'config': + if ($id <= 0) { + throw new BusinessException("配置任务缺少主记录ID"); + } + + $data = $this->parseConfigDetail($html); + $this->saveUsedCarConfigInfo($data, $id); + break; + + default: + throw new BusinessException("未知的任务场景: " . $scene); + } + + return true; + } catch (GuzzleException $e) { + throw new BusinessException("网络请求失败: " . $e->getMessage()); + } catch (BusinessException $e) { + // 业务异常原样抛出,方便上层知道是逻辑问题 + throw $e; + } catch (Throwable $th) { + // 兜底异常,避免 worker 直接挂掉 + throw new BusinessException( + "执行任务出错: " . $th->getMessage() + . ' | 行号: ' . $th->getLine() + . ' | 文件: ' . $th->getFile() + ); + } + } + + /** + * 获取网页HTML内容 + * + * @param string $url + * @return string + * @throws GuzzleException + */ + public function fetchHtml(string $url): string + { + $client = new Client(); + $response = $client->request('GET', $url, [ + 'headers' => self::DEFAULT_HEADERS, + 'timeout' => 10, + ]); + + return (string)$response->getBody(); + } + + /** + * 解析二手车详情页信息 + * + * @param string $html + * @param string $url + * @return array + * @throws BusinessException + */ + public function parseUsedCarDetail(string $html = '', string $url = ''): array + { + try { + $ql = QueryList::html($html); + } catch (Throwable $e) { + throw new BusinessException('解析 HTML 失败(详情页): ' . $e->getMessage()); + } + + $title = trim($ql->find('h1')->text()); + $price = trim($ql->find('.head-info_price-wrap__Y4bxi > p')->text()); + $pageText = $ql->find('body')->text(); + $jsonText = $ql->find('#__NEXT_DATA__')->text(); + + if ($jsonText === '') { + throw new BusinessException('详情页未找到 __NEXT_DATA__ JSON'); + } + + $jsonArray = $this->decodeJson($jsonText, '详情页 __NEXT_DATA__'); + + // 车辆相册 + $images = $jsonArray['props']['pageProps']['skuDetail']['head_images'] ?? []; + + // 车辆ID / 年份 + $carInfo = $jsonArray['props']['pageProps']['skuDetail']['car_info'] ?? []; + $carId = $carInfo['car_id'] ?? 0; + $year = (int)($carInfo['year'] ?? 0); + + // 车辆ID URL + $carIdUrl = $carId ? ('https://www.dongchedi.com/auto/params-carIds-' . $carId) : ''; + + // 能源类型(两个字段择一) + $overview = $jsonArray['props']['pageProps']['skuDetail']['car_config_overview'] ?? []; + $fuelForm = $overview['power']['fuel_form'] ?? ''; + $newFuelForm = $overview['new_energy_power']['fuel_form'] ?? ''; + $resultFuelForm = $fuelForm === '' ? $newFuelForm : $fuelForm; + + // 使用里程 + $importantText = $jsonArray['props']['pageProps']['skuDetail']['important_text'] ?? ''; + $importantTextArray = $importantText ? explode(' | ', $importantText) : []; + $usageMileage = ''; + if (isset($importantTextArray[1])) { + $usageMileage = str_replace('万公里', '', $importantTextArray[1]); + } + + // 车辆名称 品牌、车系、车型 + $brandName = $carInfo['brand_name'] ?? ''; + $seriesName = $carInfo['series_name'] ?? ''; + $carName = $carInfo['car_name'] ?? ''; + + // 商家信息 + $shopInfo = $jsonArray['props']['pageProps']['skuDetail']['shop_info'] ?? []; + $formatShopInfo = [ + 'shop_id' => $shopInfo['shop_id'] ?? 0, + 'shop_name' => $shopInfo['shop_short_name'] ?? '', + 'shop_address' => $shopInfo['shop_address'] ?? '', + ]; + + // 简介参数 + $otherParams = $jsonArray['props']['pageProps']['skuDetail']['other_params'] ?? []; + $displacement = $this->getParamValue($otherParams, '排量'); + $gearbox = $this->getParamValue($otherParams, '变速箱'); + + $formatCarInfo = [ + 'displacement' => $displacement, + 'gearbox' => $gearbox, + ]; + + // 提取新车指导价 / 比新车省金额 + $newPrice = $this->extractPatternValue($pageText, '/新车指导价[::]\s*([^\r\n万]*万)/u'); + $priceDifference = $this->extractPatternValue($pageText, '/比新车省[::]\s*([^\r\n万]*万)/u'); + + // 提取车况介绍 + $conditionDesc = ''; + if ($pageText && preg_match('/车况介绍\s*(.*?)\s*推荐理由/su', $pageText, $m)) { + $conditionDesc = trim($m[1]); + } + + return [ + 'used_car_id' => $this->extractIdFromUrl($url), + 'car_id' => $carId, + 'url' => $url, + 'title' => $title, + 'price' => str_replace('万', '', $this->decodePrice($price)), + 'new_price' => str_replace('万', '', $newPrice), + 'price_difference' => str_replace('万', '', $priceDifference), + 'condition_desc' => $conditionDesc, + 'images' => json_encode($images, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'car_id_url' => $carIdUrl, + 'used_detail' => $jsonText, + 'format_shop_info' => json_encode($formatShopInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'fuel_form' => $resultFuelForm, + 'classify_energy' => $this->detectEnergyType($resultFuelForm), + 'usage_mileage' => $usageMileage, + 'brand_name' => $brandName, + 'series_name' => $seriesName, + 'car_name' => $carName, + 'year' => $year, + 'format_car_info' => json_encode($formatCarInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + ]; + } + + /** + * 解析配置详情页信息 + * + * @param string $html + * @return array + * @throws BusinessException + */ + public function parseConfigDetail(string $html = ''): array + { + try { + $ql = QueryList::html($html); + } catch (Throwable $e) { + throw new BusinessException('解析 HTML 失败(配置页): ' . $e->getMessage()); + } + + $jsonText = $ql->find('#__NEXT_DATA__')->text(); + if ($jsonText === '') { + throw new BusinessException('配置页未找到 __NEXT_DATA__ JSON'); + } + + $raw = $this->decodeJson($jsonText, '配置页 __NEXT_DATA__'); + + $properties = $raw['props']['pageProps']['rawData']['properties'] ?? []; + $carInfo = $raw['props']['pageProps']['rawData']['car_info'][0] ?? []; + + $valuesMap = $this->buildValuesMap($carInfo); + $groups = $this->buildCarSpecGroups($properties, $valuesMap); + + return [ + 'format_car_info' => [ + 'market_time' => $this->getFieldValue($groups, 'market_time'), // 上市时间 + 'driver_form' => $this->getFieldValue($groups, 'driver_form'), // 驱动形式 + 'seat_count' => $this->getFieldValue($groups, 'seat_count'), // 座位数 + 'engine_description' => $this->getFieldValue($groups, 'engine_description'), // 发动机 + 'engine_max_power_horsepower' => $this->getFieldValue($groups, 'engine_max_power_horsepower'), // 发动机最大功率 + 'electric_description' => $this->getFieldValue($groups, 'electric_description'), // 电动机 + 'battery_capacity' => $this->getFieldValue($groups, 'battery_capacity'), // 电池容量 + 'battery_type' => $this->getFieldValue($groups, 'battery_type'), // 电池类型 + 'recharge_mileage' => $this->getFieldValue($groups, 'recharge_mileage'), // 续航里程 + ], + 'groups' => $groups, + ]; + } + + /** + * 从车辆配置中获取能源类型 + * + * @param array $data JSON数据 + * @return string|null 能源类型 + */ + public function getFuelFormByConfig(array $data): ?string + { + if (empty($data[0]['items']) || !is_array($data[0]['items'])) { + return null; + } + + foreach ($data[0]['items'] as $item) { + if (isset($item['key']) && $item['key'] === 'fuel_form') { + return $item['value'] ?? null; + } + } + + return null; + } + + /** + * 从数据中根据字段key获取值 + * + * @param mixed $data 数据 + * @param string $field 字段名 + * @return string|null + */ + public function getFieldValue($data, string $field): ?string + { + if (is_string($data)) { + $data = json_decode($data, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return null; + } + } + + if (!is_array($data)) { + return null; + } + + foreach ($data as $group) { + if (isset($group['items']) && is_array($group['items'])) { + foreach ($group['items'] as $item) { + if (($item['key'] ?? null) === $field) { + return $item['value'] ?? null; + } + + if (!empty($item['children']) && is_array($item['children'])) { + foreach ($item['children'] as $child) { + if (($child['key'] ?? null) === $field) { + return $child['value'] ?? null; + } + } + } + } + } + } + + return null; + } + + /** + * 从参数列表中获取指定名称的值 + */ + public function getParamValue(array $params, string $name): ?string + { + foreach ($params as $item) { + if (($item['name'] ?? null) === $name) { + return $item['value'] ?? null; + } + } + return null; + } + + /** + * 判断车辆能源类型 + */ + public function detectEnergyType(string $text): string + { + if (function_exists('mb_internal_encoding')) { + mb_internal_encoding('UTF-8'); + } + + // 明确定义的新能源关键字 + $newEnergyKeywords = [ + '纯电', '纯电动', '电动车', 'BEV', + '插电混动', '插电式混合', '插电式', '插电', 'PHEV', + '增程式', '增程', 'REEV', + '氢能', '氢燃料', '燃料电池', 'FCEV', + '太阳能', + ]; + + // 传统能源关键字 + $traditionalKeywords = [ + '汽油', + '柴油', + '天然气', 'LNG', 'CNG', + ]; + + // 普通混动关键字(归为传统能源) + $hybridKeywords = [ + '油电混合', '油电混动', '油电', + '混动', + '双擎', + 'HEV', + ]; + + $contains = function (string $haystack, string $needle): bool { + if ($needle === '') { + return false; + } + if (function_exists('mb_stripos')) { + return mb_stripos($haystack, $needle) !== false; + } + return stripos($haystack, $needle) !== false; + }; + + foreach ($newEnergyKeywords as $kw) { + if ($contains($text, $kw)) { + return self::ENERGY_TYPE_RENEWABLE; + } + } + + foreach ($hybridKeywords as $kw) { + if ($contains($text, $kw)) { + return self::ENERGY_TYPE_CONVENTIONAL; + } + } + + foreach ($traditionalKeywords as $kw) { + if ($contains($text, $kw)) { + return self::ENERGY_TYPE_CONVENTIONAL; + } + } + + return self::ENERGY_TYPE_UNKNOWN; + } + + /** + * 保存二手车基本信息到数据库 + * + * @param array $data 数据 + * @return int 插入记录ID + * @throws BusinessException + */ + public function saveUsedCarBasicInfo(array $data): int + { + try { + $time = time(); + $data['created_at'] = $data['created_at'] ?? $time; + $data['updated_at'] = $time; + + return (int)Db::table('usedcar_crawl_results')->insertGetId($data); + } catch (Throwable $e) { + throw new BusinessException('保存二手车基础信息失败:' . $e->getMessage()); + } + } + + /** + * 更新配置信息到数据库 + * + * @param array $data 配置数据 + * @param int $id 记录ID + * @return bool|int + * @throws BusinessException + */ + public function saveUsedCarConfigInfo(array $data, int $id) + { + try { + $record = Db::table('usedcar_crawl_results')->find($id); + + if (!$record) { + // 找不到记录就算了,不抛致命错误,视业务情况也可以抛异常 + return false; + } + + $groups = $data['groups'] ?? []; + $oldFormat = []; + + if (!empty($record->format_car_info)) { + $decoded = json_decode($record->format_car_info, true); + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { + $oldFormat = $decoded; + } + } + + $formatCarInfo = array_merge( + $oldFormat, + $data['format_car_info'] ?? [] + ); + + return Db::table('usedcar_crawl_results') + ->where('id', $id) + ->update([ + 'format_car_info' => json_encode($formatCarInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'config_detail' => json_encode($groups, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'updated_at' => time(), + ]); + } catch (Throwable $e) { + throw new BusinessException('保存车辆配置信息失败:' . $e->getMessage()); + } + } + + /** + * 构建值映射表 + */ + public function buildValuesMap(array $carInfo): array + { + $valuesMap = []; + + // 顶层字段 + foreach ($carInfo as $k => $v) { + if ($k === 'info' || is_array($v)) { + continue; + } + $valuesMap[$k] = ['value' => $v]; + } + + // info中的配置项 + if (!empty($carInfo['info']) && is_array($carInfo['info'])) { + foreach ($carInfo['info'] as $k => $v) { + if (is_array($v) && array_key_exists('value', $v)) { + $valuesMap[$k] = $v; + } + } + } + + return $valuesMap; + } + + /** + * 构建车辆规格分组 + */ + public function buildCarSpecGroups(array $properties, array $valuesMap): array + { + $groups = []; + $currentKey = null; + + foreach ($properties as $item) { + $type = (int)($item['type'] ?? 1); + $key = $item['key'] ?? ''; + $text = $item['text'] ?? ''; + $subList = $item['sub_list'] ?? null; + + // 分类字段:type = 0 / 2 + if (in_array($type, [0, 2], true)) { + $currentKey = $key ?: ($text ?: ('group_' . count($groups))); + + if (!isset($groups[$currentKey])) { + $groups[$currentKey] = [ + 'group_key' => $currentKey, + 'group_text' => $text, + 'group_type' => $type, + 'items' => [], + ]; + } + + continue; + } + + // 普通字段:挂到最近的分类下 + if ($currentKey === null) { + $currentKey = '_default'; + $groups[$currentKey] = [ + 'group_key' => '_default', + 'group_text' => '其他', + 'group_type' => -1, + 'items' => [], + ]; + } + + $value = ''; + if ($key && isset($valuesMap[$key])) { + $value = trim((string)($valuesMap[$key]['value'] ?? '')); + } + + $node = [ + 'key' => $key, + 'text' => $text, + 'value' => $value, + ]; + + if (is_array($subList) && !empty($subList)) { + $children = []; + foreach ($subList as $sub) { + $subKey = $sub['key'] ?? ''; + $subText = $sub['text'] ?? ''; + $subValue = ''; + + if ($subKey && isset($valuesMap[$subKey])) { + $subValue = trim((string)($valuesMap[$subKey]['value'] ?? '')); + } + + $children[] = [ + 'key' => $subKey, + 'text' => $subText, + 'value' => $subValue, + ]; + } + + $node['children'] = $children; + } + + $groups[$currentKey]['items'][] = $node; + } + + return array_values($groups); + } + + /** + * 解码懂车帝价格 + */ + protected function decodePrice(string $str): string + { + $map = [ + "\u{E53D}" => '1', + "\u{E3F0}" => '2', + "\u{E422}" => '3', + "\u{E42C}" => '4', + "\u{E49C}" => '5', + "\u{E42B}" => '6', + "\u{E4FE}" => '7', + "\u{E548}" => '8', + "\u{E4C8}" => '9', + "\u{E453}" => '0', + "\u{E45F}" => '万', + ]; + + return strtr($str, $map); + } + + /** + * 从URL中提取ID + */ + protected function extractIdFromUrl(string $url) + { + if (preg_match('/\/(\d+)$/', $url, $matches)) { + return (int)$matches[1]; + } + return false; + } + + /** + * 提取符合正则表达式的值(只保留数字和小数点) + */ + protected function extractPatternValue(string $text, string $pattern): string + { + $value = ''; + if ($text && preg_match($pattern, $text, $m)) { + $value = trim(preg_replace('/[^0-9.]/', '', $m[1])); + } + return $value; + } + + /** + * 下载单张图片 + */ + public function downloadImage(string $url, string $saveDir, ?string $filename = null, array $headers = []): ?string + { + if (!is_dir($saveDir)) { + if (!@mkdir($saveDir, 0777, true) && !is_dir($saveDir)) { + // echo "[Image] 创建目录失败:{$saveDir}\n"; + return null; + } + } + + if ($filename === null) { + $path = parse_url($url, PHP_URL_PATH); + $ext = pathinfo($path ?? '', PATHINFO_EXTENSION); + + if ($ext === '') { + $ext = 'jpg'; + } + + $filename = md5($url) . '.' . $ext; + } + + $savePath = rtrim($saveDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename; + + $defaultHeaders = [ + 'User-Agent: ' . self::DEFAULT_HEADERS['User-Agent'], + 'Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8', + 'Referer: ' . self::DEFAULT_HEADERS['Referer'], + ]; + + foreach ($headers as $h) { + $defaultHeaders[] = $h; + } + + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_TIMEOUT => 15, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HTTPHEADER => $defaultHeaders, + ]); + + $data = curl_exec($ch); + $errNo = curl_errno($ch); + $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($errNo !== 0 || $code !== 200 || $data === false || $data === '') { + return null; + } + + $bytes = @file_put_contents($savePath, $data); + if ($bytes === false) { + return null; + } + + return $savePath; + } + + /** + * 批量下载相册 + * + * @param array $urls 图片URL数组 + * @param string $saveDir 保存目录 + * @return array [原始URL => 保存路径或null] + */ + public function downloadImageAlbum(array $urls, string $saveDir): array + { + $result = []; + + foreach ($urls as $url) { + $url = trim((string)$url); + if ($url === '') { + continue; + } + + if (strpos($url, '//') === 0) { + $url = 'https:' . $url; + } + + try { + $path = $this->downloadImage($url, $saveDir); + } catch (Throwable $e) { + $path = null; + } + + $result[$url] = $path; + } + + return $result; + } + + /** + * 下载 -> 上传 -> 删除本地 + * + * @param array $images 原始相册URL列表 + * @param string $path 本地保存目录 + * @param int $usedCarId 懂车帝 used_car_id + * @param int $id 本地 crawl 记录id + * @return bool + */ + public function uploadAndRemove(array $images, string $path, $usedCarId, $id): bool + { + if (empty($images)) { + return false; + } + + // 下载图片 + $filePatch = $this->downloadImageAlbum($images, $path); + if (empty($filePatch)) { + return false; + } + + $uploadService = new UploadService(); + + foreach ($filePatch as $url => $filePath) { + if ($filePath === null) { + continue; + } + + try { + // 上传云端 + $param = [ + 'path' => $filePath, + 'scene' => 'usedcar_crawl', + 'category' => 'usedcar', + ]; + + $result = $uploadService->uploadLocalFile($param); + if (empty($result['file_name'])) { + // 上传失败则跳过,但不抛异常影响主流程 + continue; + } + + // 保存图片信息 + UsedcarCrawlResultsImages::create([ + 'uc_id' => $id, + 'used_car_id' => $usedCarId, + 'original_name'=> $result['original_name'] ?? '', + 'file_path' => $result['file_name'], + ]); + } catch (Throwable $e) { + // 单张图失败不影响其他 + // 可以按需写日志 + } finally { + // 删除本地文件 + if (is_file($filePath)) { + @unlink($filePath); + } + } + } + + // 删除目录 + rmdir($path); + + return true; + } + + /** + * 安全解 JSON + * + * @param string $json + * @param string $context + * @return array + * @throws BusinessException + */ + protected function decodeJson(string $json, string $context = 'JSON'): array + { + $data = json_decode($json, true); + if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) { + throw new BusinessException($context . ' 解析失败:' . json_last_error_msg()); + } + return $data; + } +} \ No newline at end of file diff --git a/app/common/utils/crawl/service/UsedCarCrawl_bak.php b/app/common/utils/crawl/service/UsedCarCrawl_bak.php new file mode 100644 index 0000000..11ea8c8 --- /dev/null +++ b/app/common/utils/crawl/service/UsedCarCrawl_bak.php @@ -0,0 +1,681 @@ + $scene, + 'url' => $url, + 'id' => $id + ], JSON_UNESCAPED_UNICODE)); + } + + /** + * 获取队列url + * + * $scene 二手信息 usedcar 配置信息 config + */ + public function rUrl() + { + return json_decode(Redis::rPop('usedcar:urls'), true); + } + + /** + * 执行抓取 + */ + public function doData() + { + try { + $raw = $this->rUrl(); + + if (!$raw) { + return false; + } + + $html = $this->getHtml($raw['url']); + + switch ($raw['scene']) { + case 'usedcar': + $info = $this->getUsedDetail($html, $raw['url']); + $id = $this->saveUsedcarBasicToDb($info); + $this->iUrl($info['car_id_url'], 'config', $id); + break; + + case 'config': + $data = $this->getConfigDetail($html); + $this->saveUsedcarConfigToDb($data, $raw['id']); + break; + } + + return true; + } catch (\Throwable $th) { + throw new BusinessException($th->getMessage().'|'.$th->getLine().'|'.$th->getFile()); + } + } + + /** + * 获取html + */ + public function getHtml($url) + { + $client = new Client(); + $res = $client->request('GET', $url, [ + 'headers' => [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + . 'AppleWebKit/537.36 (KHTML, like Gecko) ' + . 'Chrome/120.0.0.0 Safari/537.36', + 'Accept-Language' => 'zh-CN,zh;q=0.9', + 'Referer' => 'https://www.dongchedi.com/' + ] + ]); + return (string)$res->getBody(); + } + + /** + * 获取二手信息 + */ + public function getUsedDetail(string $html = '', string $url = '') + { + $ql = QueryList::html($html); + + $title = $ql->find('h1')->text(); + $price = $ql->find('.head-info_price-wrap__Y4bxi > p')->text(); + $pageText = $ql->find('body')->text(); + $jsonText = $ql->find('#__NEXT_DATA__')->text(); + + // 新车指导价 + $newPrice = ''; + if ($pageText) { + if (preg_match('/新车指导价[::]\s*([^\r\n万]*万)/u', $pageText, $m)) { + $newPrice = trim(preg_replace('/[^0-9.]/', '', $m[1])); + } + } + + // 比新车省 + $priceDifference = ''; + if ($pageText) { + if (preg_match('/比新车省[::]\s*([^\r\n万]*万)/u', $pageText, $m)) { + $priceDifference = trim(preg_replace('/[^0-9.]/', '', $m[1])); + } + } + + // 车况介绍 + $conditionDesc = ''; + if ($pageText) { + if (preg_match('/车况介绍\s*(.*?)\s*推荐理由/su', $pageText, $m)) { + $conditionDesc = trim($m[1]); + } + } + + $jsonArray = json_decode($jsonText, true); + + // 车辆相册 + $images = $jsonArray['props']['pageProps']['skuDetail']['head_images'] ?? []; + + // 车辆ID + $carId = $jsonArray['props']['pageProps']['skuDetail']['car_info']['car_id'] ?? 0; + + // 车辆年份 + $year = $jsonArray['props']['pageProps']['skuDetail']['car_info']['year'] ?? 0; + + // 车辆ID URL + $carIdUrl = $carId ? ('https://www.dongchedi.com/auto/params-carIds-' . $carId) : ''; + + // 能源类型 + $fuelForm = $jsonArray['props']['pageProps']['skuDetail']['car_config_overview']['power']['fuel_form'] ?? ''; + $newFuelForm = $jsonArray['props']['pageProps']['skuDetail']['car_config_overview']['new_energy_power']['fuel_form'] ?? ''; + $resultFuelForm = $fuelForm === '' ? $newFuelForm : $fuelForm; + + // 使用里程 + $importantText = $jsonArray['props']['pageProps']['skuDetail']['important_text'] ?? ''; + $importantTextArray = explode(' | ', $importantText); + $usageMileage = str_replace('万公里', '', isset($importantTextArray[1]) ? $importantTextArray[1] : ''); + + // 车辆名称 品牌、车系、车型 + $brandName = $jsonArray['props']['pageProps']['skuDetail']['car_info']['brand_name'] ?? ''; + $seriesName = $jsonArray['props']['pageProps']['skuDetail']['car_info']['series_name'] ?? ''; + $carName = $jsonArray['props']['pageProps']['skuDetail']['car_info']['car_name'] ?? ''; + + // 商家信息 + $shopInfo = $jsonArray['props']['pageProps']['skuDetail']['shop_info'] ?? []; + $formatShopInfo = [ + 'shop_id' => $shopInfo['shop_id'] ?? 0, + 'shop_name' => $shopInfo['shop_short_name'] ?? '', + 'shop_address' => $shopInfo['shop_address'] ?? '' + ]; + + // 简介参数 + $otherParams = $jsonArray['props']['pageProps']['skuDetail']['other_params'] ?? []; + // 排量 + $displacement = $this->getOtherParamValue($otherParams, '排量'); + // 变速箱 + $gearbox = $this->getOtherParamValue($otherParams, '变速箱'); + // 格式化车辆信息 + $formatCarInfo = [ + 'displacement' => $displacement, + 'gearbox' => $gearbox + ]; + + // 整理车辆信息 + $info['used_car_id'] = $this->getLastIdFromUrl($url); + $info['car_id'] = $carId; + $info['url'] = $url; + $info['title'] = $title; + $info['price'] = str_replace('万', '', $this->decodeDongchediPrice($price)); + $info['new_price'] = str_replace('万', '', $newPrice); + $info['price_difference'] = str_replace('万', '', $priceDifference); + $info['condition_desc'] = $conditionDesc; + $info['images'] = json_encode($images); + $info['car_id_url'] = $carIdUrl; + $info['used_detail'] = $jsonText; + $info['format_shop_info'] = json_encode($formatShopInfo); + $info['fuel_form'] = $resultFuelForm; + $info['classify_energy'] = $this->detectEnergyType($resultFuelForm); + $info['usage_mileage'] = $usageMileage; + $info['brand_name'] = $brandName; + $info['series_name'] = $seriesName; + $info['car_name'] = $carName; + $info['year'] = $year; + $info['format_car_info'] = json_encode($formatCarInfo); + + return $info; + } + + /** + * 获取配置信息 + */ + public function getConfigDetail(string $html = '') + { + $ql = QueryList::html($html); + + $jsonText = $ql->find('#__NEXT_DATA__')->text(); + + $raw = json_decode($jsonText, true); + $properties = $raw['props']['pageProps']['rawData']['properties'] ?? []; + $carInfo = $raw['props']['pageProps']['rawData']['car_info'][0] ?? []; + + $valuesMap = $this->buildValuesMapFromCarInfo($carInfo); + $groups = $this->buildCarSpecGroups($properties, $valuesMap); + + $data['format_car_info'] = [ + 'market_time' => $this->getFieldValue($groups, 'market_time'), // 上市时间 + 'driver_form' => $this->getFieldValue($groups, 'driver_form'), // 驱动形式 + 'seat_count' => $this->getFieldValue($groups, 'seat_count'), // 座位数 + 'engine_description' => $this->getFieldValue($groups, 'engine_description'), // 发动机 + 'engine_max_power_horsepower' => $this->getFieldValue($groups, 'engine_max_power_horsepower'), // 发动机最大功率 + 'electric_description' => $this->getFieldValue($groups, 'electric_description'), //电动机 + 'battery_capacity' => $this->getFieldValue($groups, 'battery_capacity'), //电池容量 + 'battery_type' => $this->getFieldValue($groups, 'battery_type'), // 电池类型 + 'recharge_mileage' => $this->getFieldValue($groups, 'recharge_mileage') // 续航里程 + ]; + $data['groups'] = $groups; + return $data; + } + + /** + * 从车辆配置 JSON 数据中获取能源类型 + * + * @param array $data JSON 数据 + * @return string|null 能源类型,如 '油电混合'、'纯电动'、 + */ + public function getFuelFormByConfigInfo(array $data) + { + $fuelForm = null; + foreach ($data[0]['items'] as $item) { + if ($item['key'] === 'fuel_form') { + $fuelForm = $item['value']; + break; + } + } + return $fuelForm; + } + + /** + * 从车辆配置 JSON 数据中根据字段 key 获取对应的 value + * + * @param string|array $data JSON 字符串 或 已解析的数组 + * @param string $field 要查找的字段 key(如 'fuel_form') + * @return string|null 找到则返回 value,否则返回 null + */ + public function getFieldValue($data, string $field) + { + // 如果传入的是 JSON 字符串,先解码 + if (is_string($data)) { + $data = json_decode($data, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return null; // JSON 格式错误 + } + } + + // 遍历第一层(假设结构是 [ { "items": [...] } ]) + foreach ($data as $group) { + if (isset($group['items']) && is_array($group['items'])) { + foreach ($group['items'] as $item) { + if (isset($item['key']) && $item['key'] === $field) { + return $item['value'] ?? null; + } + } + } + } + + return null; // 未找到 + } + + /** + * 从 other_params 数组中根据 name 获取对应的 value + * + * @param array $otherParams 格式为 [ ['name' => 'xxx', 'value' => 'yyy'], ... ] + * @param string $name 要查找的字段名称(如 '上牌地') + * @return string|null 找到则返回 value,否则返回 null + */ + public function getOtherParamValue(array $otherParams, string $name): ?string + { + foreach ($otherParams as $item) { + if (isset($item['name']) && $item['name'] === $name) { + return $item['value'] ?? null; + } + } + return null; + } + + /** + * 根据给定字符串判断车辆能源类型 + * + * 返回: + * - RE 新能源(纯电、插电混动、增程、氢燃料等) + * - CE 传统能源(汽油、柴油、天然气、非插电混动等) + * - UNKNOWN 无法判断 + * + * @param string $text 例如:'2022款 某某牌 2.0L 油电混合 双擎' + * @return string + */ + public function detectEnergyType(string $text): string + { + if (function_exists('mb_internal_encoding')) { + mb_internal_encoding('UTF-8'); + } + + // 明确定义的新能源关键字(纯电 / 插电 / 增程 / 氢 / 太阳能 等) + $newEnergyKeywords = [ + // 纯电 + '纯电', '纯电动', '电动车', 'BEV', + // 插电式混合 + '插电混动', '插电式混合', '插电式', '插电', 'PHEV', + // 增程式 + '增程式', '增程', 'REEV', + // 氢能 / 燃料电池 + '氢能', '氢燃料', '燃料电池', 'FCEV', + // 其它 + '太阳能', + ]; + + // 传统能源关键字(包括燃油/燃气) + $traditionalKeywords = [ + '汽油', + '柴油', + '天然气', 'LNG', 'CNG', + ]; + + // 普通混动关键字(按你的要求,统一归为传统能源) + $hybridKeywords = [ + '油电混合', '油电混动', '油电', + '混动', // 不带“插电”的混动 + '双擎', // 丰田常见表达 + 'HEV', + ]; + + // 辅助:不区分大小写的包含判断,优先 mb_stripos + $contains = function (string $haystack, string $needle): bool { + if ($needle === '') { + return false; + } + if (function_exists('mb_stripos')) { + return mb_stripos($haystack, $needle) !== false; + } + return stripos($haystack, $needle) !== false; + }; + + // 1. 先判断“明确新能源”关键词(插电、增程、氢、纯电等) + foreach ($newEnergyKeywords as $kw) { + if ($contains($text, $kw)) { + return 'RE'; + } + } + + // 2. 再判断“普通油电混动”——按照你的业务,一律归为传统能源 + foreach ($hybridKeywords as $kw) { + if ($contains($text, $kw)) { + return 'CE'; + } + } + + // 3. 再判断传统能源(纯燃油/气) + foreach ($traditionalKeywords as $kw) { + if ($contains($text, $kw)) { + return 'CE'; + } + } + + // 4. 都没匹配上 + return 'UNKNOWN'; + } + + /** + * 二手信息入库 + */ + public function saveUsedcarBasicToDb(array $data = []) + { + $data['created_at'] = time(); + $data['updated_at'] = time(); + return Db::table('usedcar_crawl_results')->insertGetId($data); + } + + /** + * 更新信息入库 + */ + public function saveUsedcarConfigToDb(array $data = [], int $id = 0) + { + $info = Db::table('usedcar_crawl_results')->find($id); + + if (!$info) { + return false; + } + + $groups = $data['groups']; + $formatCarInfo = array_merge($data['format_car_info'], json_decode($info->format_car_info, true)); + + return Db::table('usedcar_crawl_results') + ->where('id', $id) + ->update([ + 'format_car_info' => json_encode($formatCarInfo, JSON_UNESCAPED_UNICODE), + 'config_detail' => json_encode($groups, JSON_UNESCAPED_UNICODE), + 'updated_at' => time() + ]); + } + + /** + * 把 car_info 扁平成一个 [key => ['value' => xxx]] 的 map + * + * @param array $carInfo rawData['car_info'][0] + * @return array + */ + public function buildValuesMapFromCarInfo(array $carInfo): array + { + $valuesMap = []; + + // 1) car_info 顶层的一些字段(official_price、series_name 等) + foreach ($carInfo as $k => $v) { + if ($k === 'info' || is_array($v)) { + continue; + } + $valuesMap[$k] = ['value' => $v]; + } + + // 2) info 里的配置项 + if (!empty($carInfo['info']) && is_array($carInfo['info'])) { + foreach ($carInfo['info'] as $k => $v) { + if (is_array($v) && array_key_exists('value', $v)) { + $valuesMap[$k] = $v; + } + } + } + + return $valuesMap; + } + + /** + * 根据 type 重新分组: + * - type=0,2 为分组(左侧分类) + * - 其它 type 都视为该分组下的字段 + * + * @param array $properties rawData['properties'] + * @param array $valuesMap 来自 buildValuesMapFromCarInfo() + * @return array + */ + public function buildCarSpecGroups(array $properties, array $valuesMap): array + { + $groups = []; + $currentKey = null; // 当前分组 key + + foreach ($properties as $item) { + $type = (int)($item['type'] ?? 1); + $key = $item['key'] ?? ''; + $text = $item['text'] ?? ''; + $subList = $item['sub_list'] ?? null; + + // ---------- 1) 分类字段:type = 0 / 2 ---------- + if (in_array($type, [0, 2], true)) { + $currentKey = $key ?: ($text ?: ('group_' . count($groups))); + + if (!isset($groups[$currentKey])) { + $groups[$currentKey] = [ + 'group_key' => $currentKey, + 'group_text' => $text, + 'group_type' => $type, + 'items' => [], + ]; + } + + continue; + } + + // ---------- 2) 普通字段:挂到最近的分类下 ---------- + if ($currentKey === null) { + // 如果一开始就没有遇到分类,给一个默认分组 + $currentKey = '_default'; + $groups[$currentKey] = [ + 'group_key' => '_default', + 'group_text' => '其他', + 'group_type' => -1, + 'items' => [], + ]; + } + + // 取 value + $value = ''; + if ($key && isset($valuesMap[$key])) { + $value = trim((string)($valuesMap[$key]['value'] ?? '')); + } + + $node = [ + 'key' => $key, + 'text' => $text, + 'value' => $value, + ]; + + // ---------- 3) sub_list 子项(有就当成 children) ---------- + if (is_array($subList) && !empty($subList)) { + $children = []; + foreach ($subList as $sub) { + $subKey = $sub['key'] ?? ''; + $subText = $sub['text'] ?? ''; + $subValue = ''; + + if ($subKey && isset($valuesMap[$subKey])) { + $subValue = trim((string)($valuesMap[$subKey]['value'] ?? '')); + } + + $children[] = [ + 'key' => $subKey, + 'text' => $subText, + 'value' => $subValue, + ]; + } + + $node['children'] = $children; + } + + $groups[$currentKey]['items'][] = $node; + } + + // 你可以按需返回关联数组或索引数组,这里转成索引数组方便前端遍历 + return array_values($groups); + } + + /** + * 转义价格 + */ + protected function decodeDongchediPrice(string $str): string + { + // 映射表:PUA 字符 => 数字 / 万 + $map = [ + "\u{E53D}" => '1', + "\u{E3F0}" => '2', + "\u{E422}" => '3', + "\u{E42C}" => '4', + "\u{E49C}" => '5', + "\u{E42B}" => '6', + "\u{E4FE}" => '7', + "\u{E548}" => '8', + "\u{E4C8}" => '9', + "\u{E453}" => '0', + "\u{E45F}" => '万', + ]; + + return strtr($str, $map); + } + + /** + * 获取URL最后ID + */ + protected function getLastIdFromUrl($url) + { + // 方法1:使用正则表达式提取最后的数字ID + if (preg_match('/\/(\d+)$/', $url, $matches)) { + return $matches[1]; + } + return false; + } + + /** + * 下载单张图片 + * + * @param string $url 图片完整 URL + * @param string $saveDir 保存目录(如 /www/usedcar_images/2025-11-20) + * @param string|null $filename 指定文件名(可选,不含路径),为空则自动生成 + * @param array $headers 额外 HTTP 头(可选) + * + * @return string|null 成功返回保存路径,失败返回 null + */ + protected function downloadImage(string $url, string $saveDir, ?string $filename = null, array $headers = []): ?string + { + // 1. 目录检查 & 创建 + if (!is_dir($saveDir)) { + if (!mkdir($saveDir, 0777, true) && !is_dir($saveDir)) { + echo "[Image] 创建目录失败:{$saveDir}\n"; + return null; + } + } + + // 2. 自动生成文件名(基于 URL md5 + 原扩展名) + if ($filename === null) { + $path = parse_url($url, PHP_URL_PATH); // /xxx/xxx.jpg + $ext = pathinfo($path, PATHINFO_EXTENSION); // jpg 或 webp 之类 + + if ($ext === '') { + $ext = 'jpg'; // 没有扩展名时兜底 + } + + $filename = md5($url) . '.' . $ext; + } + + $savePath = rtrim($saveDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename; + + // 3. 组装 HTTP 头 + $defaultHeaders = [ + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' + . 'AppleWebKit/537.36 (KHTML, like Gecko) ' + . 'Chrome/120.0.0.0 Safari/537.36', + 'Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8', + 'Referer: https://www.dongchedi.com/', // 很多图床需要 Referer + ]; + + foreach ($headers as $h) { + $defaultHeaders[] = $h; + } + + // 4. 使用 cURL 下载 + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_TIMEOUT => 15, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HTTPHEADER => $defaultHeaders, + ]); + + $data = curl_exec($ch); + $errNo = curl_errno($ch); + $errMsg = curl_error($ch); + $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($errNo !== 0) { + echo "[Image] 下载失败(cURL):{$url} | {$errNo} {$errMsg}\n"; + return null; + } + + if ($code !== 200 || $data === false || $data === '') { + echo "[Image] 下载失败(HTTP {$code}):{$url}\n"; + return null; + } + + // 5. 写入本地文件 + $bytes = file_put_contents($savePath, $data); + if ($bytes === false) { + echo "[Image] 保存失败:{$savePath}\n"; + return null; + } + + echo "[Image] 下载成功:{$url} => {$savePath}\n"; + + return $savePath; + } + + /** + * 批量下载相册 + * + * @param array $urls 图片 URL 数组 + * @param string $saveDir 保存目录 + * + * @return array [原始 URL => 保存路径或 null] + */ + protected function downloadImageAlbum(array $urls, string $saveDir): array + { + $result = []; + + foreach ($urls as $url) { + $url = trim($url); + if ($url === '') { + continue; + } + + // 如果存在 // 开头的相对协议,补全 + if (strpos($url, '//') === 0) { + $url = 'https:' . $url; + } + + $path = $this->downloadImage($url, $saveDir); + $result[$url] = $path; + } + + return $result; + } +} diff --git a/app/common/utils/crawl/service/UsedCarCrawl_bak2.php b/app/common/utils/crawl/service/UsedCarCrawl_bak2.php new file mode 100644 index 0000000..2612baa --- /dev/null +++ b/app/common/utils/crawl/service/UsedCarCrawl_bak2.php @@ -0,0 +1,798 @@ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Accept-Language' => 'zh-CN,zh;q=0.9', + 'Referer' => 'https://www.dongchedi.com/' + ]; + + // Redis队列键名 + private const REDIS_QUEUE_KEY = 'usedcar:urls'; + + // 能源类型常量 + public const ENERGY_TYPE_RENEWABLE = 'RE'; + public const ENERGY_TYPE_CONVENTIONAL = 'CE'; + public const ENERGY_TYPE_UNKNOWN = 'UNKNOWN'; + + /** + * 插入队列url + * + * @param string $url URL地址 + * @param string $scene 场景:usedcar(二手信息) config(配置信息) + * @param int $id 记录ID + * @return int + */ + public function pushUrl(string $url = '', string $scene = 'usedcar', int $id = 0): int + { + $data = [ + 'scene' => $scene, + 'url' => $url, + 'id' => $id + ]; + + return Redis::lpush(self::REDIS_QUEUE_KEY, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } + + /** + * 获取队列url + * + * @return array|null + */ + public function popUrl(): ?array + { + $data = Redis::rPop(self::REDIS_QUEUE_KEY); + if (!$data) { + return null; + } + + return json_decode($data, true); + } + + /** + * 执行抓取任务 + * + * @return bool + * @throws BusinessException + */ + public function execute(): bool + { + try { + $task = $this->popUrl(); + + if (!$task) { + return false; + } + + $html = $this->fetchHtml($task['url']); + + switch ($task['scene']) { + case 'usedcar': + $info = $this->parseUsedCarDetail($html, $task['url']); + $id = $this->saveUsedCarBasicInfo($info); + $this->uploadAndRemove(json_decode($info['images'], true), public_path('uploads/usedcar/' . $id), $info['used_car_id'], $id); + $this->pushUrl($info['car_id_url'], 'config', $id); + break; + + case 'config': + $data = $this->parseConfigDetail($html); + $this->saveUsedCarConfigInfo($data, $task['id']); + break; + + default: + throw new BusinessException("未知的任务场景: " . $task['scene']); + } + + return true; + } catch (GuzzleException $e) { + throw new BusinessException("网络请求失败: " . $e->getMessage()); + } catch (Throwable $th) { + throw new BusinessException("执行任务出错: " . $th->getMessage() . ' | 行号: ' . $th->getLine() . ' | 文件: ' . $th->getFile()); + } + } + + /** + * 获取网页HTML内容 + * + * @param string $url + * @return string + * @throws GuzzleException + */ + public function fetchHtml(string $url): string + { + $client = new Client(); + $response = $client->request('GET', $url, [ + 'headers' => self::DEFAULT_HEADERS + ]); + return (string)$response->getBody(); + } + + /** + * 解析二手车详情页信息 + * + * @param string $html + * @param string $url + * @return array + */ + public function parseUsedCarDetail(string $html = '', string $url = ''): array + { + $ql = QueryList::html($html); + + $title = $ql->find('h1')->text(); + $price = $ql->find('.head-info_price-wrap__Y4bxi > p')->text(); + $pageText = $ql->find('body')->text(); + $jsonText = $ql->find('#__NEXT_DATA__')->text(); + + // 解析JSON数据 + $jsonArray = json_decode($jsonText, true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new BusinessException("JSON解析失败: " . json_last_error_msg()); + } + + // 车辆相册 + $images = $jsonArray['props']['pageProps']['skuDetail']['head_images'] ?? []; + + // 车辆ID + $carId = $jsonArray['props']['pageProps']['skuDetail']['car_info']['car_id'] ?? 0; + + // 车辆年份 + $year = $jsonArray['props']['pageProps']['skuDetail']['car_info']['year'] ?? 0; + + // 车辆ID URL + $carIdUrl = $carId ? ('https://www.dongchedi.com/auto/params-carIds-' . $carId) : ''; + + // 能源类型 + $fuelForm = $jsonArray['props']['pageProps']['skuDetail']['car_config_overview']['power']['fuel_form'] ?? ''; + $newFuelForm = $jsonArray['props']['pageProps']['skuDetail']['car_config_overview']['new_energy_power']['fuel_form'] ?? ''; + $resultFuelForm = $fuelForm === '' ? $newFuelForm : $fuelForm; + + // 使用里程 + $importantText = $jsonArray['props']['pageProps']['skuDetail']['important_text'] ?? ''; + $importantTextArray = explode(' | ', $importantText); + $usageMileage = str_replace('万公里', '', isset($importantTextArray[1]) ? $importantTextArray[1] : ''); + + // 车辆名称 品牌、车系、车型 + $brandName = $jsonArray['props']['pageProps']['skuDetail']['car_info']['brand_name'] ?? ''; + $seriesName = $jsonArray['props']['pageProps']['skuDetail']['car_info']['series_name'] ?? ''; + $carName = $jsonArray['props']['pageProps']['skuDetail']['car_info']['car_name'] ?? ''; + + // 商家信息 + $shopInfo = $jsonArray['props']['pageProps']['skuDetail']['shop_info'] ?? []; + $formatShopInfo = [ + 'shop_id' => $shopInfo['shop_id'] ?? 0, + 'shop_name' => $shopInfo['shop_short_name'] ?? '', + 'shop_address' => $shopInfo['shop_address'] ?? '' + ]; + + // 简介参数 + $otherParams = $jsonArray['props']['pageProps']['skuDetail']['other_params'] ?? []; + // 排量 + $displacement = $this->getParamValue($otherParams, '排量'); + // 变速箱 + $gearbox = $this->getParamValue($otherParams, '变速箱'); + // 格式化车辆信息 + $formatCarInfo = [ + 'displacement' => $displacement, + 'gearbox' => $gearbox + ]; + + // 提取新车指导价 + $newPrice = $this->extractPatternValue($pageText, '/新车指导价[::]\s*([^\r\n万]*万)/u'); + + // 提取比新车省金额 + $priceDifference = $this->extractPatternValue($pageText, '/比新车省[::]\s*([^\r\n万]*万)/u'); + + // 提取车况介绍 + $conditionDesc = ''; + if ($pageText) { + if (preg_match('/车况介绍\s*(.*?)\s*推荐理由/su', $pageText, $m)) { + $conditionDesc = trim($m[1]); + } + } + + // 整理车辆信息 + return [ + 'used_car_id' => $this->extractIdFromUrl($url), + 'car_id' => $carId, + 'url' => $url, + 'title' => $title, + 'price' => str_replace('万', '', $this->decodePrice($price)), + 'new_price' => str_replace('万', '', $newPrice), + 'price_difference' => str_replace('万', '', $priceDifference), + 'condition_desc' => $conditionDesc, + 'images' => json_encode($images, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'car_id_url' => $carIdUrl, + 'used_detail' => $jsonText, + 'format_shop_info' => json_encode($formatShopInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'fuel_form' => $resultFuelForm, + 'classify_energy' => $this->detectEnergyType($resultFuelForm), + 'usage_mileage' => $usageMileage, + 'brand_name' => $brandName, + 'series_name' => $seriesName, + 'car_name' => $carName, + 'year' => $year, + 'format_car_info' => json_encode($formatCarInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) + ]; + } + + /** + * 解析配置详情页信息 + * + * @param string $html + * @return array + */ + public function parseConfigDetail(string $html = ''): array + { + $ql = QueryList::html($html); + $jsonText = $ql->find('#__NEXT_DATA__')->text(); + + $raw = json_decode($jsonText, true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new BusinessException("配置页面JSON解析失败: " . json_last_error_msg()); + } + + $properties = $raw['props']['pageProps']['rawData']['properties'] ?? []; + $carInfo = $raw['props']['pageProps']['rawData']['car_info'][0] ?? []; + + $valuesMap = $this->buildValuesMap($carInfo); + $groups = $this->buildCarSpecGroups($properties, $valuesMap); + + return [ + 'format_car_info' => [ + 'market_time' => $this->getFieldValue($groups, 'market_time'), // 上市时间 + 'driver_form' => $this->getFieldValue($groups, 'driver_form'), // 驱动形式 + 'seat_count' => $this->getFieldValue($groups, 'seat_count'), // 座位数 + 'engine_description' => $this->getFieldValue($groups, 'engine_description'), // 发动机 + 'engine_max_power_horsepower' => $this->getFieldValue($groups, 'engine_max_power_horsepower'), // 发动机最大功率 + 'electric_description' => $this->getFieldValue($groups, 'electric_description'), //电动机 + 'battery_capacity' => $this->getFieldValue($groups, 'battery_capacity'), //电池容量 + 'battery_type' => $this->getFieldValue($groups, 'battery_type'), // 电池类型 + 'recharge_mileage' => $this->getFieldValue($groups, 'recharge_mileage') // 续航里程 + ], + 'groups' => $groups + ]; + } + + /** + * 从车辆配置中获取能源类型 + * + * @param array $data JSON数据 + * @return string|null 能源类型 + */ + public function getFuelFormByConfig(array $data): ?string + { + if (empty($data[0]['items'])) { + return null; + } + + foreach ($data[0]['items'] as $item) { + if (isset($item['key']) && $item['key'] === 'fuel_form') { + return $item['value'] ?? null; + } + } + + return null; + } + + /** + * 从数据中根据字段key获取值 + * + * @param mixed $data 数据 + * @param string $field 字段名 + * @return string|null + */ + public function getFieldValue($data, string $field): ?string + { + // 如果传入的是 JSON 字符串,先解码 + if (is_string($data)) { + $data = json_decode($data, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return null; + } + } + + // 如果不是数组,直接返回null + if (!is_array($data)) { + return null; + } + + // 遍历寻找对应字段 + foreach ($data as $group) { + if (isset($group['items']) && is_array($group['items'])) { + foreach ($group['items'] as $item) { + if (isset($item['key']) && $item['key'] === $field) { + return $item['value'] ?? null; + } + + // 检查子元素 + if (isset($item['children']) && is_array($item['children'])) { + foreach ($item['children'] as $child) { + if (isset($child['key']) && $child['key'] === $field) { + return $child['value'] ?? null; + } + } + } + } + } + } + + return null; + } + + /** + * 从参数列表中获取指定名称的值 + * + * @param array $params 参数列表 + * @param string $name 名称 + * @return string|null + */ + public function getParamValue(array $params, string $name): ?string + { + foreach ($params as $item) { + if (isset($item['name']) && $item['name'] === $name) { + return $item['value'] ?? null; + } + } + return null; + } + + /** + * 判断车辆能源类型 + * + * @param string $text 描述文本 + * @return string 能源类型标识 + */ + public function detectEnergyType(string $text): string + { + if (function_exists('mb_internal_encoding')) { + mb_internal_encoding('UTF-8'); + } + + // 明确定义的新能源关键字 + $newEnergyKeywords = [ + // 纯电 + '纯电', '纯电动', '电动车', 'BEV', + // 插电式混合 + '插电混动', '插电式混合', '插电式', '插电', 'PHEV', + // 增程式 + '增程式', '增程', 'REEV', + // 氢能 / 燃料电池 + '氢能', '氢燃料', '燃料电池', 'FCEV', + // 其它 + '太阳能', + ]; + + // 传统能源关键字 + $traditionalKeywords = [ + '汽油', + '柴油', + '天然气', 'LNG', 'CNG', + ]; + + // 普通混动关键字(归为传统能源) + $hybridKeywords = [ + '油电混合', '油电混动', '油电', + '混动', // 不带"插电"的混动 + '双擎', // 丰田常见表达 + 'HEV', + ]; + + // 辅助函数:不区分大小写的包含判断 + $contains = function (string $haystack, string $needle): bool { + if ($needle === '') { + return false; + } + if (function_exists('mb_stripos')) { + return mb_stripos($haystack, $needle) !== false; + } + return stripos($haystack, $needle) !== false; + }; + + // 1. 先判断"明确新能源"关键词 + foreach ($newEnergyKeywords as $kw) { + if ($contains($text, $kw)) { + return self::ENERGY_TYPE_RENEWABLE; + } + } + + // 2. 再判断"普通油电混动" + foreach ($hybridKeywords as $kw) { + if ($contains($text, $kw)) { + return self::ENERGY_TYPE_CONVENTIONAL; + } + } + + // 3. 再判断传统能源 + foreach ($traditionalKeywords as $kw) { + if ($contains($text, $kw)) { + return self::ENERGY_TYPE_CONVENTIONAL; + } + } + + // 4. 都没匹配上 + return self::ENERGY_TYPE_UNKNOWN; + } + + /** + * 保存二手车基本信息到数据库 + * + * @param array $data 数据 + * @return int 插入记录ID + */ + public function saveUsedCarBasicInfo(array $data): int + { + $data['created_at'] = time(); + $data['updated_at'] = time(); + return Db::table('usedcar_crawl_results')->insertGetId($data); + } + + /** + * 更新配置信息到数据库 + * + * @param array $data 配置数据 + * @param int $id 记录ID + * @return bool|int + */ + public function saveUsedCarConfigInfo(array $data, int $id) + { + $record = Db::table('usedcar_crawl_results')->find($id); + + if (!$record) { + return false; + } + + $groups = $data['groups']; + $formatCarInfo = array_merge( + $data['format_car_info'], + json_decode($record->format_car_info, true) + ); + + return Db::table('usedcar_crawl_results') + ->where('id', $id) + ->update([ + 'format_car_info' => json_encode($formatCarInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'config_detail' => json_encode($groups, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'updated_at' => time() + ]); + } + + /** + * 构建值映射表 + * + * @param array $carInfo 车辆信息 + * @return array + */ + public function buildValuesMap(array $carInfo): array + { + $valuesMap = []; + + // 处理顶层字段 + foreach ($carInfo as $k => $v) { + if ($k === 'info' || is_array($v)) { + continue; + } + $valuesMap[$k] = ['value' => $v]; + } + + // 处理info中的配置项 + if (!empty($carInfo['info']) && is_array($carInfo['info'])) { + foreach ($carInfo['info'] as $k => $v) { + if (is_array($v) && array_key_exists('value', $v)) { + $valuesMap[$k] = $v; + } + } + } + + return $valuesMap; + } + + /** + * 构建车辆规格分组 + * + * @param array $properties 属性列表 + * @param array $valuesMap 值映射表 + * @return array + */ + public function buildCarSpecGroups(array $properties, array $valuesMap): array + { + $groups = []; + $currentKey = null; + + foreach ($properties as $item) { + $type = (int)($item['type'] ?? 1); + $key = $item['key'] ?? ''; + $text = $item['text'] ?? ''; + $subList = $item['sub_list'] ?? null; + + // 分类字段:type = 0 / 2 + if (in_array($type, [0, 2], true)) { + $currentKey = $key ?: ($text ?: ('group_' . count($groups))); + + if (!isset($groups[$currentKey])) { + $groups[$currentKey] = [ + 'group_key' => $currentKey, + 'group_text' => $text, + 'group_type' => $type, + 'items' => [], + ]; + } + + continue; + } + + // 普通字段:挂到最近的分类下 + if ($currentKey === null) { + // 如果一开始就没有遇到分类,给一个默认分组 + $currentKey = '_default'; + $groups[$currentKey] = [ + 'group_key' => '_default', + 'group_text' => '其他', + 'group_type' => -1, + 'items' => [], + ]; + } + + // 取 value + $value = ''; + if ($key && isset($valuesMap[$key])) { + $value = trim((string)($valuesMap[$key]['value'] ?? '')); + } + + $node = [ + 'key' => $key, + 'text' => $text, + 'value' => $value, + ]; + + // 处理子项 + if (is_array($subList) && !empty($subList)) { + $children = []; + foreach ($subList as $sub) { + $subKey = $sub['key'] ?? ''; + $subText = $sub['text'] ?? ''; + $subValue = ''; + + if ($subKey && isset($valuesMap[$subKey])) { + $subValue = trim((string)($valuesMap[$subKey]['value'] ?? '')); + } + + $children[] = [ + 'key' => $subKey, + 'text' => $subText, + 'value' => $subValue, + ]; + } + + $node['children'] = $children; + } + + $groups[$currentKey]['items'][] = $node; + } + + // 转成索引数组方便前端遍历 + return array_values($groups); + } + + /** + * 解码懂车帝价格 + * + * @param string $str 加密字符串 + * @return string 解码后的字符串 + */ + protected function decodePrice(string $str): string + { + // 映射表:PUA 字符 => 数字 / 万 + $map = [ + "\u{E53D}" => '1', + "\u{E3F0}" => '2', + "\u{E422}" => '3', + "\u{E42C}" => '4', + "\u{E49C}" => '5', + "\u{E42B}" => '6', + "\u{E4FE}" => '7', + "\u{E548}" => '8', + "\u{E4C8}" => '9', + "\u{E453}" => '0', + "\u{E45F}" => '万', + ]; + + return strtr($str, $map); + } + + /** + * 从URL中提取ID + * + * @param string $url URL + * @return int|false + */ + protected function extractIdFromUrl(string $url) + { + if (preg_match('/\/(\d+)$/', $url, $matches)) { + return (int)$matches[1]; + } + return false; + } + + /** + * 提取符合正则表达式的值 + * + * @param string $text 文本 + * @param string $pattern 正则表达式 + * @return string + */ + protected function extractPatternValue(string $text, string $pattern): string + { + $value = ''; + if ($text) { + if (preg_match($pattern, $text, $m)) { + $value = trim(preg_replace('/[^0-9.]/', '', $m[1])); + } + } + return $value; + } + + /** + * 下载单张图片 + * + * @param string $url 图片完整URL + * @param string $saveDir 保存目录 + * @param string|null $filename 指定文件名 + * @param array $headers 额外HTTP头 + * @return string|null 成功返回保存路径,失败返回null + */ + public function downloadImage(string $url, string $saveDir, ?string $filename = null, array $headers = []): ?string + { + // 目录检查 & 创建 + if (!is_dir($saveDir)) { + if (!mkdir($saveDir, 0777, true) && !is_dir($saveDir)) { + echo "[Image] 创建目录失败:{$saveDir}\n"; + return null; + } + } + + // 自动生成文件名 + if ($filename === null) { + $path = parse_url($url, PHP_URL_PATH); + $ext = pathinfo($path, PATHINFO_EXTENSION); + + if ($ext === '') { + $ext = 'jpg'; + } + + $filename = md5($url) . '.' . $ext; + } + + $savePath = rtrim($saveDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename; + + // 组装HTTP头 + $defaultHeaders = [ + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8', + 'Referer: https://www.dongchedi.com/', + ]; + + foreach ($headers as $h) { + $defaultHeaders[] = $h; + } + + // 使用cURL下载 + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_TIMEOUT => 15, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HTTPHEADER => $defaultHeaders, + ]); + + $data = curl_exec($ch); + $errNo = curl_errno($ch); + $errMsg = curl_error($ch); + $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($errNo !== 0) { + // echo "[Image] 下载失败(cURL):{$url} | {$errNo} {$errMsg}\n"; + return null; + } + + if ($code !== 200 || $data === false || $data === '') { + // echo "[Image] 下载失败(HTTP {$code}):{$url}\n"; + return null; + } + + // 写入本地文件 + $bytes = file_put_contents($savePath, $data); + if ($bytes === false) { + // echo "[Image] 保存失败:{$savePath}\n"; + return null; + } + + // echo "[Image] 下载成功:{$url} => {$savePath}\n"; + + return $savePath; + } + + /** + * 批量下载相册 + * + * @param array $urls 图片URL数组 + * @param string $saveDir 保存目录 + * @return array [原始URL => 保存路径或null] + */ + public function downloadImageAlbum(array $urls, string $saveDir): array + { + $result = []; + + foreach ($urls as $url) { + $url = trim($url); + if ($url === '') { + continue; + } + + // 补全协议 + if (strpos($url, '//') === 0) { + $url = 'https:' . $url; + } + + $path = $this->downloadImage($url, $saveDir); + $result[$url] = $path; + } + + return $result; + } + + + public function uploadAndRemove($images, $path, $usedCarId, $id) + { + // 下载图片 + $filePatch = $this->downloadImageAlbum($images, $path); + + // 检查图片 + if (empty($filePatch)) { + return false; + } + + // 上传图片 + foreach ($filePatch as $url => $path) { + if ($path === null) { + continue; + } + + // 上传云端 + $param = [ + 'path' => $path, + 'scene' => 'usedcar_crawl', + 'category' => 'usedcar' + ]; + $up = new \app\common\service\UploadService(); + $result = $up->uploadLocalFile($param); + + // 保存图片信息 + UsedcarCrawlResultsImages::create([ + 'uc_id' => $id, + 'used_car_id' => $usedCarId, + 'original_name' => $result['original_name'], + 'file_path' => $result['file_name'], + ]); + + // 删除本地文件 + @unlink($path); + } + } +} \ No newline at end of file diff --git a/app/common/validate/UsedcarCrawlResultsImagesValidate.php b/app/common/validate/UsedcarCrawlResultsImagesValidate.php new file mode 100644 index 0000000..e6cbbd5 --- /dev/null +++ b/app/common/validate/UsedcarCrawlResultsImagesValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/common/validate/UsedcarCrawlResultsValidate.php b/app/common/validate/UsedcarCrawlResultsValidate.php new file mode 100644 index 0000000..f0766bd --- /dev/null +++ b/app/common/validate/UsedcarCrawlResultsValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index c3c75ae..4b4fb43 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -74,25 +74,25 @@ class IndexController $options = [ 'controller' => [ 'module' => 'common', - 'name' => 'finance_recon_dubai_files' + 'name' => 'usedcar_crawl_results_images' ], 'model' => [ 'module' => 'common', - 'name' => 'finance_recon_dubai_files' + 'name' => 'usedcar_crawl_results_images' ], 'service' => [ 'module' => 'common', - 'name' => 'finance_recon_dubai_files' + 'name' => 'usedcar_crawl_results_images' ], 'logic' => [ 'module' => 'common', - 'name' => 'finance_recon_dubai_files' + 'name' => 'usedcar_crawl_results_images' ], 'validate' => [ 'module' => 'common', - 'name' => 'finance_recon_dubai_files' + 'name' => 'usedcar_crawl_results_images' ], - 'table' => 'finance_recon_dubai_files' + 'table' => 'usedcar_crawl_results_images' ]; $result = (new Generate($options))->doGenerate(); @@ -159,11 +159,16 @@ class IndexController public function test(Request $request) { - $tmp = (new \app\common\service\OssService)->generatePrivateUrl('fdesk_test/20251104/20251104_财务.xlsx');; - - $service = new \app\common\service\FinanceRecoDubaiFilesService(); - $result = $service->import($tmp, true); + $service = new \app\common\utils\crawl\service\UsedCarCrawl(); - return json(['code' => 0, 'msg' => 'ok', 'data' => $result]); + $result = $service->pushUrl('https://www.dongchedi.com/usedcar/21631642'); + + // $result = $service->doData(); + + // $result = $service->decodeDongchediPrice('.'); + + // $result = $service->detectEnergyType('油电混合'); + + return json($result); } } \ No newline at end of file diff --git a/app/process/CheckWeb.php b/app/process/CheckWeb.php index 8b99d61..f3439ef 100644 --- a/app/process/CheckWeb.php +++ b/app/process/CheckWeb.php @@ -22,6 +22,12 @@ class CheckWeb // }); // } + // 每秒执行:队列二手车数据处理 + new Crontab('* * * * * *', function(){ + $service = new \app\common\utils\crawl\service\UsedCarCrawl(); + $service->execute(); + }); + new Crontab('*/50 * * * * *', function(){ $userIds = User::where('status', 1)->pluck('id')->toArray(); foreach ($userIds as $userId) { diff --git a/app/process/Task.php b/app/process/Task.php index 7d8059c..c90ff85 100644 --- a/app/process/Task.php +++ b/app/process/Task.php @@ -15,7 +15,7 @@ class Task { // 每半小时执行一次,通知待跟进客户 new Crontab('0 */30 * * * *', function(){ - echo date('Y-m-d H:i:s') . "\n"; + // echo date('Y-m-d H:i:s') . "\n"; Log::info('send follow start',[time()]); $where = [ diff --git a/composer.lock b/composer.lock index ba35bd5..2bf0ca9 100644 --- a/composer.lock +++ b/composer.lock @@ -59,25 +59,25 @@ }, { "name": "brick/math", - "version": "0.13.1", + "version": "0.14.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", + "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -107,7 +107,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.13.1" + "source": "https://github.com/brick/math/tree/0.14.0" }, "funding": [ { @@ -115,7 +115,7 @@ "type": "github" } ], - "time": "2025-03-29T13:50:30+00:00" + "time": "2025-08-29T12:40:03+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -267,33 +267,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "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\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -338,7 +337,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -354,7 +353,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "firebase/php-jwt", @@ -554,22 +553,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.3", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "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" @@ -660,7 +659,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -676,20 +675,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:37:11+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -697,7 +696,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -743,7 +742,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.2.0" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -759,20 +758,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:27:01+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -788,7 +787,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -859,7 +858,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.1" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -875,20 +874,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:30:47+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.4", + "version": "v1.0.5", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "30e286560c137526eccd4ce21b2de477ab0676d2" + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/30e286560c137526eccd4ce21b2de477ab0676d2", - "reference": "30e286560c137526eccd4ce21b2de477ab0676d2", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", "shasum": "" }, "require": { @@ -897,7 +896,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", "uri-template/tests": "1.0.0" }, "type": "library", @@ -945,7 +944,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.4" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" }, "funding": [ { @@ -961,20 +960,20 @@ "type": "tidelift" } ], - "time": "2025-02-03T10:55:03+00:00" + "time": "2025-08-22T14:27:06+00:00" }, { "name": "illuminate/bus", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/bus.git", - "reference": "60da78ea881c539ce56c5b66321be73755c5918c" + "reference": "797f768b7a52fb98443b8127cda71840cc2dbc5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/bus/zipball/60da78ea881c539ce56c5b66321be73755c5918c", - "reference": "60da78ea881c539ce56c5b66321be73755c5918c", + "url": "https://api.github.com/repos/illuminate/bus/zipball/797f768b7a52fb98443b8127cda71840cc2dbc5b", + "reference": "797f768b7a52fb98443b8127cda71840cc2dbc5b", "shasum": "" }, "require": { @@ -1014,27 +1013,29 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-13T15:08:45+00:00" + "time": "2025-10-30T12:22:29+00:00" }, { "name": "illuminate/collections", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "e5711846b7c68128bc8de72c13e017606043c996" + "reference": "deb291b109b6f7fd776a3550a120771137b3c5d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/e5711846b7c68128bc8de72c13e017606043c996", - "reference": "e5711846b7c68128bc8de72c13e017606043c996", + "url": "https://api.github.com/repos/illuminate/collections/zipball/deb291b109b6f7fd776a3550a120771137b3c5d1", + "reference": "deb291b109b6f7fd776a3550a120771137b3c5d1", "shasum": "" }, "require": { "illuminate/conditionable": "^12.0", "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", - "php": "^8.2" + "php": "^8.2", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "suggest": { "illuminate/http": "Required to convert collections to API resources (^12.0).", @@ -1071,11 +1072,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-28T13:08:33+00:00" + "time": "2025-10-30T12:22:05+00:00" }, { "name": "illuminate/conditionable", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", @@ -1121,26 +1122,36 @@ }, { "name": "illuminate/container", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "ff9dde2c8dce16ea9ecf0418095749311240aff9" + "reference": "d6eaa8afd48dbe16b6b3c412a87479cad67eeb12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/ff9dde2c8dce16ea9ecf0418095749311240aff9", - "reference": "ff9dde2c8dce16ea9ecf0418095749311240aff9", + "url": "https://api.github.com/repos/illuminate/container/zipball/d6eaa8afd48dbe16b6b3c412a87479cad67eeb12", + "reference": "d6eaa8afd48dbe16b6b3c412a87479cad67eeb12", "shasum": "" }, "require": { "illuminate/contracts": "^12.0", "php": "^8.2", - "psr/container": "^1.1.1|^2.0.1" + "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": { @@ -1168,20 +1179,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-09T14:04:48+00:00" + "time": "2025-09-12T14:35:11+00:00" }, { "name": "illuminate/contracts", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "152313571553ef1be907a3c18b8ef2d635cb4339" + "reference": "5ab717c8f0dd4e84be703796bbb415ccff8de57a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/152313571553ef1be907a3c18b8ef2d635cb4339", - "reference": "152313571553ef1be907a3c18b8ef2d635cb4339", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/5ab717c8f0dd4e84be703796bbb415ccff8de57a", + "reference": "5ab717c8f0dd4e84be703796bbb415ccff8de57a", "shasum": "" }, "require": { @@ -1216,24 +1227,24 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-09T18:23:03+00:00" + "time": "2025-10-07T19:59:08+00:00" }, { "name": "illuminate/database", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/database.git", - "reference": "da274285e4c09f6794945354b5b11157f4d9cab1" + "reference": "e42bec6ad999bcc78ce9f3789f5ef937ee2e5982" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/da274285e4c09f6794945354b5b11157f4d9cab1", - "reference": "da274285e4c09f6794945354b5b11157f4d9cab1", + "url": "https://api.github.com/repos/illuminate/database/zipball/e42bec6ad999bcc78ce9f3789f5ef937ee2e5982", + "reference": "e42bec6ad999bcc78ce9f3789f5ef937ee2e5982", "shasum": "" }, "require": { - "brick/math": "^0.11|^0.12|^0.13", + "brick/math": "^0.11|^0.12|^0.13|^0.14", "ext-pdo": "*", "illuminate/collections": "^12.0", "illuminate/container": "^12.0", @@ -1241,7 +1252,8 @@ "illuminate/macroable": "^12.0", "illuminate/support": "^12.0", "laravel/serializable-closure": "^1.3|^2.0", - "php": "^8.2" + "php": "^8.2", + "symfony/polyfill-php85": "^1.33" }, "suggest": { "ext-filter": "Required to use the Postgres database driver.", @@ -1286,20 +1298,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-09T14:07:55+00:00" + "time": "2025-11-03T14:22:34+00:00" }, { "name": "illuminate/events", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/events.git", - "reference": "bf1f121ea51e077e893d32e2848e102513d4b1b5" + "reference": "e0de667c68040d59a6ffc09e914536a1186870f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/events/zipball/bf1f121ea51e077e893d32e2848e102513d4b1b5", - "reference": "bf1f121ea51e077e893d32e2848e102513d4b1b5", + "url": "https://api.github.com/repos/illuminate/events/zipball/e0de667c68040d59a6ffc09e914536a1186870f0", + "reference": "e0de667c68040d59a6ffc09e914536a1186870f0", "shasum": "" }, "require": { @@ -1341,20 +1353,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-13T15:08:45+00:00" + "time": "2025-10-21T15:10:34+00:00" }, { "name": "illuminate/filesystem", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", - "reference": "80fe8605cfa360fdbc85f67c19801a9657615aab" + "reference": "b1fbb20010e868f838feac86aeac8ba439fca10d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/80fe8605cfa360fdbc85f67c19801a9657615aab", - "reference": "80fe8605cfa360fdbc85f67c19801a9657615aab", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/b1fbb20010e868f838feac86aeac8ba439fca10d", + "reference": "b1fbb20010e868f838feac86aeac8ba439fca10d", "shasum": "" }, "require": { @@ -1408,20 +1420,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-13T15:08:45+00:00" + "time": "2025-10-29T15:59:33+00:00" }, { "name": "illuminate/http", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/http.git", - "reference": "bbafb2b2e629530e516d52a1fa46dfeb09b5e184" + "reference": "5c864f562b2fded08622ee19cd53260aef3db9a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/http/zipball/bbafb2b2e629530e516d52a1fa46dfeb09b5e184", - "reference": "bbafb2b2e629530e516d52a1fa46dfeb09b5e184", + "url": "https://api.github.com/repos/illuminate/http/zipball/5c864f562b2fded08622ee19cd53260aef3db9a0", + "reference": "5c864f562b2fded08622ee19cd53260aef3db9a0", "shasum": "" }, "require": { @@ -1437,7 +1449,8 @@ "symfony/http-foundation": "^7.2.0", "symfony/http-kernel": "^7.2.0", "symfony/mime": "^7.2.0", - "symfony/polyfill-php83": "^1.31" + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "suggest": { "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image()." @@ -1469,11 +1482,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-06T18:14:49+00:00" + "time": "2025-10-28T14:38:15+00:00" }, { "name": "illuminate/macroable", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", @@ -1519,16 +1532,16 @@ }, { "name": "illuminate/pagination", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/pagination.git", - "reference": "7f68669ad7d8a706883b8409f6b7254fdc437f67" + "reference": "1d95e70671177108b202e6ceb61bc7bc9924bdbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/pagination/zipball/7f68669ad7d8a706883b8409f6b7254fdc437f67", - "reference": "7f68669ad7d8a706883b8409f6b7254fdc437f67", + "url": "https://api.github.com/repos/illuminate/pagination/zipball/1d95e70671177108b202e6ceb61bc7bc9924bdbf", + "reference": "1d95e70671177108b202e6ceb61bc7bc9924bdbf", "shasum": "" }, "require": { @@ -1565,27 +1578,31 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-28T21:37:18+00:00" + "time": "2025-09-02T15:31:06+00:00" }, { "name": "illuminate/pipeline", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/pipeline.git", - "reference": "a1039dfe54854470cdda37782bab0901aa588dd4" + "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/pipeline/zipball/a1039dfe54854470cdda37782bab0901aa588dd4", - "reference": "a1039dfe54854470cdda37782bab0901aa588dd4", + "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": { @@ -1613,20 +1630,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-13T15:08:45+00:00" + "time": "2025-08-20T13:36:50+00:00" }, { "name": "illuminate/redis", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/redis.git", - "reference": "b225dc28092b00ed0555df66ce3a3a448b4dd8ae" + "reference": "8a397f00036974404d5d4d94eacfe8050af8e7f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/redis/zipball/b225dc28092b00ed0555df66ce3a3a448b4dd8ae", - "reference": "b225dc28092b00ed0555df66ce3a3a448b4dd8ae", + "url": "https://api.github.com/repos/illuminate/redis/zipball/8a397f00036974404d5d4d94eacfe8050af8e7f2", + "reference": "8a397f00036974404d5d4d94eacfe8050af8e7f2", "shasum": "" }, "require": { @@ -1667,20 +1684,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-13T15:08:45+00:00" + "time": "2025-10-26T17:11:19+00:00" }, { "name": "illuminate/session", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/session.git", - "reference": "9d27155b34bca502fe3e1adc16035b46d5ef3ed8" + "reference": "c376e4a0dda79a88462e8a7d16cc924aa3eb6e15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/session/zipball/9d27155b34bca502fe3e1adc16035b46d5ef3ed8", - "reference": "9d27155b34bca502fe3e1adc16035b46d5ef3ed8", + "url": "https://api.github.com/repos/illuminate/session/zipball/c376e4a0dda79a88462e8a7d16cc924aa3eb6e15", + "reference": "c376e4a0dda79a88462e8a7d16cc924aa3eb6e15", "shasum": "" }, "require": { @@ -1724,20 +1741,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-13T15:08:45+00:00" + "time": "2025-10-26T17:11:19+00:00" }, { "name": "illuminate/support", - "version": "v12.18.0", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "3c3894ba09a86663b747efaa6822639f1c32ea01" + "reference": "480df979c8ffe700d6adb6e6e64abe04b32e02e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/3c3894ba09a86663b747efaa6822639f1c32ea01", - "reference": "3c3894ba09a86663b747efaa6822639f1c32ea01", + "url": "https://api.github.com/repos/illuminate/support/zipball/480df979c8ffe700d6adb6e6e64abe04b32e02e3", + "reference": "480df979c8ffe700d6adb6e6e64abe04b32e02e3", "shasum": "" }, "require": { @@ -1751,6 +1768,8 @@ "illuminate/macroable": "^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": { @@ -1801,7 +1820,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-06T18:15:26+00:00" + "time": "2025-10-31T16:20:10+00:00" }, { "name": "jaeger/g-http", @@ -1960,16 +1979,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.4", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841" + "reference": "038ce42edee619599a1debb7e81d7b3759492819" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841", - "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819", + "reference": "038ce42edee619599a1debb7e81d7b3759492819", "shasum": "" }, "require": { @@ -2017,27 +2036,21 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-03-19T13:51:03+00:00" + "time": "2025-10-09T13:42:30+00:00" }, { "name": "league/flysystem", - "version": "3.29.0", + "version": "3.30.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0" + "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0", - "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "shasum": "" }, "require": { "league/flysystem-local": "^3.0.0", @@ -2060,13 +2073,13 @@ "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", - "ext-mongodb": "^1.3", + "ext-mongodb": "^1.3|^2", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "guzzlehttp/psr7": "^2.6", "microsoft/azure-storage-blob": "^1.1", - "mongodb/mongodb": "^1.2", + "mongodb/mongodb": "^1.2|^2", "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", @@ -2104,29 +2117,23 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" }, - "time": "2024-09-29T11:59:11+00:00" + "time": "2025-11-10T17:13:11+00:00" }, { "name": "league/flysystem-local", - "version": "3.29.0", + "version": "3.30.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", + "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", + "shasum": "" }, "require": { "ext-fileinfo": "*", @@ -2159,29 +2166,23 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" }, - "time": "2024-08-09T21:24:39+00:00" + "time": "2025-11-10T11:23:37+00:00" }, { "name": "league/mime-type-detection", - "version": "1.15.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" }, "require": { "ext-fileinfo": "*", @@ -2211,7 +2212,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { @@ -2223,26 +2224,26 @@ "type": "tidelift" } ], - "time": "2024-01-28T23:22:08+00:00" + "time": "2024-09-21T08:32:55+00:00" }, { "name": "maennchen/zipstream-php", - "version": "3.1.2", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", - "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-zlib": "*", - "php-64bit": "^8.2" + "php-64bit": "^8.3" }, "require-dev": { "brianium/paratest": "^7.7", @@ -2251,7 +2252,7 @@ "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.5", - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^12.0", "vimeo/psalm": "^6.0" }, "suggest": { @@ -2293,7 +2294,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" }, "funding": [ { @@ -2301,7 +2302,7 @@ "type": "github" } ], - "time": "2025-01-27T12:07:53+00:00" + "time": "2025-07-17T11:15:13+00:00" }, { "name": "markbaker/complex", @@ -2747,16 +2748,16 @@ }, { "name": "nesbot/carbon", - "version": "3.10.0", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9" + "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", - "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", + "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", "shasum": "" }, "require": { @@ -2774,13 +2775,13 @@ "require-dev": { "doctrine/dbal": "^3.6.3 || ^4.0", "doctrine/orm": "^2.15.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.75.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.17", - "phpunit/phpunit": "^10.5.46", - "squizlabs/php_codesniffer": "^3.13.0" + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4" }, "bin": [ "bin/carbon" @@ -2848,7 +2849,7 @@ "type": "tidelift" } ], - "time": "2025-06-12T10:24:28+00:00" + "time": "2025-09-06T13:39:36+00:00" }, { "name": "nikic/fast-route", @@ -2952,16 +2953,16 @@ }, { "name": "phpmailer/phpmailer", - "version": "v6.10.0", + "version": "v6.12.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144" + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144", - "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d1ac35d784bf9f5e61b424901d5a014967f15b12", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12", "shasum": "" }, "require": { @@ -3021,7 +3022,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.10.0" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.12.0" }, "funding": [ { @@ -3029,7 +3030,7 @@ "type": "github" } ], - "time": "2025-04-24T15:19:31+00:00" + "time": "2025-10-15T16:49:08+00:00" }, { "name": "phpoffice/phpspreadsheet", @@ -3139,16 +3140,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.3", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", "shasum": "" }, "require": { @@ -3156,7 +3157,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { @@ -3198,7 +3199,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" }, "funding": [ { @@ -3210,7 +3211,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:41:07+00:00" + "time": "2025-08-21T11:53:16+00:00" }, { "name": "psr/cache", @@ -3801,13 +3802,7 @@ "type": "zip", "url": "https://api.github.com/repos/shopwwi/filesystem-oss/zipball/85cb385d1a5d11ca3c22bc485a7b18c588fe7645", "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "shasum": "" }, "require": { "aliyuncs/oss-sdk-php": "^2.4", @@ -3860,13 +3855,7 @@ "type": "zip", "url": "https://api.github.com/repos/shopwwi/webman-filesystem/zipball/26ac82515d3787943573c50d1a6f0ca339f0d3d4", "reference": "26ac82515d3787943573c50d1a6f0ca339f0d3d4", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "shasum": "" }, "require": { "league/flysystem": "^2.3|^3.0", @@ -3909,16 +3898,16 @@ }, { "name": "symfony/cache", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "c4b217b578c11ec764867aa0c73e602c602965de" + "reference": "1277a1ec61c8d93ea61b2a59738f1deb9bfb6701" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/c4b217b578c11ec764867aa0c73e602c602965de", - "reference": "c4b217b578c11ec764867aa0c73e602c602965de", + "url": "https://api.github.com/repos/symfony/cache/zipball/1277a1ec61c8d93ea61b2a59738f1deb9bfb6701", + "reference": "1277a1ec61c8d93ea61b2a59738f1deb9bfb6701", "shasum": "" }, "require": { @@ -3987,7 +3976,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.3.0" + "source": "https://github.com/symfony/cache/tree/v7.3.6" }, "funding": [ { @@ -3998,12 +3987,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-06T19:00:13+00:00" + "time": "2025-10-30T13:22:58+00:00" }, { "name": "symfony/cache-contracts", @@ -4157,16 +4150,16 @@ }, { "name": "symfony/console", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44" + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44", + "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", "shasum": "" }, "require": { @@ -4231,7 +4224,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.0" + "source": "https://github.com/symfony/console/tree/v7.3.6" }, "funding": [ { @@ -4242,12 +4235,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T10:34:04+00:00" + "time": "2025-11-04T01:21:42+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4318,16 +4315,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83" + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/cf68d225bc43629de4ff54778029aee6dc191b83", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/bbe40bfab84323d99dab491b716ff142410a92a8", + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8", "shasum": "" }, "require": { @@ -4375,7 +4372,7 @@ "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.3.0" + "source": "https://github.com/symfony/error-handler/tree/v7.3.6" }, "funding": [ { @@ -4386,25 +4383,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-10-31T19:12:50+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", "shasum": "" }, "require": { @@ -4455,7 +4456,7 @@ "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.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" }, "funding": [ { @@ -4466,12 +4467,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-22T09:11:45+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -4551,16 +4556,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", "shasum": "" }, "require": { @@ -4597,7 +4602,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.6" }, "funding": [ { @@ -4608,25 +4613,29 @@ "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-10-25T15:15:23+00:00" + "time": "2025-11-05T09:52:27+00:00" }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", "shasum": "" }, "require": { @@ -4661,7 +4670,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.3.5" }, "funding": [ { @@ -4672,25 +4681,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2025-10-15T18:45:57+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "4236baf01609667d53b20371486228231eb135fd" + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4236baf01609667d53b20371486228231eb135fd", - "reference": "4236baf01609667d53b20371486228231eb135fd", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6379e490d6ecfc5c4224ff3a754b90495ecd135c", + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c", "shasum": "" }, "require": { @@ -4740,7 +4753,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.6" }, "funding": [ { @@ -4751,25 +4764,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-12T14:48:23+00:00" + "time": "2025-11-06T11:05:57+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f" + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ac7b8e163e8c83dce3abcc055a502d4486051a9f", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9a34dc0196677250e3609c2fac9de9e1551a262", + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262", "shasum": "" }, "require": { @@ -4854,7 +4871,7 @@ "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.3.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.6" }, "funding": [ { @@ -4865,25 +4882,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:47:32+00:00" + "time": "2025-11-06T20:58:12+00:00" }, { "name": "symfony/mime", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35", + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35", "shasum": "" }, "require": { @@ -4938,7 +4959,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.0" + "source": "https://github.com/symfony/mime/tree/v7.3.4" }, "funding": [ { @@ -4949,16 +4970,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-19T08:51:26+00:00" + "time": "2025-09-16T08:38:17+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -5017,7 +5042,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -5028,6 +5053,10 @@ "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" @@ -5037,16 +5066,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -5095,7 +5124,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -5106,16 +5135,20 @@ "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" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -5178,7 +5211,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -5189,6 +5222,10 @@ "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" @@ -5198,7 +5235,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -5259,7 +5296,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -5270,6 +5307,10 @@ "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" @@ -5279,7 +5320,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -5340,7 +5381,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -5351,6 +5392,10 @@ "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" @@ -5360,7 +5405,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -5420,7 +5465,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -5431,6 +5476,10 @@ "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" @@ -5440,16 +5489,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -5496,7 +5545,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -5507,25 +5556,189 @@ "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" + "time": "2025-07-08T02:45:35+00:00" }, { - "name": "symfony/service-contracts", - "version": "v3.6.0", + "name": "symfony/polyfill-php84", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -5579,7 +5792,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -5590,25 +5803,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -5623,7 +5840,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -5666,7 +5882,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -5677,32 +5893,30 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "symfony/translation", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667" + "reference": "ec25870502d0c7072d086e8ffba1420c85965174" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4aba29076a29a3aa667e09b791e5f868973a8667", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174", + "reference": "ec25870502d0c7072d086e8ffba1420c85965174", + "shasum": "" }, "require": { "php": ">=8.2", @@ -5768,7 +5982,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.0" + "source": "https://github.com/symfony/translation/tree/v7.3.4" }, "funding": [ { @@ -5779,25 +5993,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-09-07T11:39:36+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -5846,7 +6064,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -5857,25 +6075,29 @@ "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-27T08:32:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.0", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e" + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/548f6760c54197b1084e1e5c71f6d9d523f2f78e", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", "shasum": "" }, "require": { @@ -5887,7 +6109,6 @@ "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", @@ -5930,7 +6151,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" }, "funding": [ { @@ -5941,25 +6162,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-27T18:39:23+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c" + "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c", - "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", + "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", "shasum": "" }, "require": { @@ -6007,7 +6232,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.3.0" + "source": "https://github.com/symfony/var-exporter/tree/v7.3.4" }, "funding": [ { @@ -6018,12 +6243,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-15T09:04:05+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "topthink/think-container", @@ -6358,16 +6587,16 @@ }, { "name": "webman/console", - "version": "v2.1.8", + "version": "v2.1.9", "source": { "type": "git", "url": "https://github.com/webman-php/console.git", - "reference": "2d4ce527810f91e1b4f583ec90eae24a0334ab10" + "reference": "09c995bf3d9a136c7c471b6db837a1fd56b7d206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webman-php/console/zipball/2d4ce527810f91e1b4f583ec90eae24a0334ab10", - "reference": "2d4ce527810f91e1b4f583ec90eae24a0334ab10", + "url": "https://api.github.com/repos/webman-php/console/zipball/09c995bf3d9a136c7c471b6db837a1fd56b7d206", + "reference": "09c995bf3d9a136c7c471b6db837a1fd56b7d206", "shasum": "" }, "require": { @@ -6408,7 +6637,7 @@ "source": "https://github.com/webman-php/console", "wiki": "http://www.workerman.net/doc/webman" }, - "time": "2025-06-02T00:42:16+00:00" + "time": "2025-10-14T09:11:21+00:00" }, { "name": "webman/database", @@ -6553,16 +6782,16 @@ }, { "name": "workerman/coroutine", - "version": "v1.1.3", + "version": "v1.1.4", "source": { "type": "git", "url": "https://github.com/workerman-php/coroutine.git", - "reference": "df8fc428967d512a74a8a7d80355c1d40228c9fa" + "reference": "b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/df8fc428967d512a74a8a7d80355c1d40228c9fa", - "reference": "df8fc428967d512a74a8a7d80355c1d40228c9fa", + "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44", + "reference": "b0bebfa9d41b992ad0a835ddf2ee8fa5d58eca44", "shasum": "" }, "require": { @@ -6586,9 +6815,9 @@ "description": "Workerman coroutine", "support": { "issues": "https://github.com/workerman-php/coroutine/issues", - "source": "https://github.com/workerman-php/coroutine/tree/v1.1.3" + "source": "https://github.com/workerman-php/coroutine/tree/v1.1.4" }, - "time": "2025-02-17T03:34:21+00:00" + "time": "2025-10-11T15:09:08+00:00" }, { "name": "workerman/crontab", @@ -6696,23 +6925,17 @@ }, { "name": "workerman/redis", - "version": "v2.0.4", + "version": "v2.0.5", "source": { "type": "git", "url": "https://github.com/walkor/redis.git", - "reference": "82b398c5bdd5d683a3458aaf645b159b7b39b84e" + "reference": "49627c1809eff1ef7175eb8ee7549234a1d67ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/walkor/redis/zipball/82b398c5bdd5d683a3458aaf645b159b7b39b84e", - "reference": "82b398c5bdd5d683a3458aaf645b159b7b39b84e", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/walkor/redis/zipball/49627c1809eff1ef7175eb8ee7549234a1d67ec5", + "reference": "49627c1809eff1ef7175eb8ee7549234a1d67ec5", + "shasum": "" }, "require": { "php": ">=7", @@ -6731,9 +6954,9 @@ "homepage": "http://www.workerman.net", "support": { "issues": "https://github.com/walkor/redis/issues", - "source": "https://github.com/walkor/redis/tree/v2.0.4" + "source": "https://github.com/walkor/redis/tree/v2.0.5" }, - "time": "2025-04-05T07:45:12+00:00" + "time": "2025-04-07T01:58:58+00:00" }, { "name": "workerman/redis-queue", @@ -6747,13 +6970,7 @@ "type": "zip", "url": "https://api.github.com/repos/walkor/redis-queue/zipball/75dbf7ed2ea228c45dc0df82c0fea35879b715d0", "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "shasum": "" }, "require": { "php": ">=7.0", @@ -6780,16 +6997,16 @@ }, { "name": "workerman/webman-framework", - "version": "v2.1.2", + "version": "v2.1.4", "source": { "type": "git", "url": "https://github.com/walkor/webman-framework.git", - "reference": "f803bd867f07bb0929faef060b59a19a44186bfc" + "reference": "f19d1f9e47cc50210555f0b63db5ae1dd584f793" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/walkor/webman-framework/zipball/f803bd867f07bb0929faef060b59a19a44186bfc", - "reference": "f803bd867f07bb0929faef060b59a19a44186bfc", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/f19d1f9e47cc50210555f0b63db5ae1dd584f793", + "reference": "f19d1f9e47cc50210555f0b63db5ae1dd584f793", "shasum": "" }, "require": { @@ -6797,7 +7014,7 @@ "nikic/fast-route": "^1.3", "php": ">=8.1", "psr/container": ">=1.0", - "psr/log": "^3.0", + "psr/log": "^2.0 || ^3.0", "workerman/workerman": "^5.1 || dev-master" }, "suggest": { @@ -6842,20 +7059,20 @@ "source": "https://github.com/walkor/webman-framework", "wiki": "https://doc.workerman.net/" }, - "time": "2025-03-10T11:52:22+00:00" + "time": "2025-11-10T06:59:23+00:00" }, { "name": "workerman/workerman", - "version": "v5.1.3", + "version": "v5.1.6", "source": { "type": "git", "url": "https://github.com/walkor/workerman.git", - "reference": "371f3a5decb28f1bd3464ae26d47ea1a4cf0a3c5" + "reference": "9b83bdf9fd0eaff9419a240b47da870447583112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/walkor/workerman/zipball/371f3a5decb28f1bd3464ae26d47ea1a4cf0a3c5", - "reference": "371f3a5decb28f1bd3464ae26d47ea1a4cf0a3c5", + "url": "https://api.github.com/repos/walkor/workerman/zipball/9b83bdf9fd0eaff9419a240b47da870447583112", + "reference": "9b83bdf9fd0eaff9419a240b47da870447583112", "shasum": "" }, "require": { @@ -6868,9 +7085,9 @@ }, "require-dev": { "guzzlehttp/guzzle": "^7.0", - "mockery/mockery": "^1.6", + "mockery/mockery": "2.0.x-dev", "pestphp/pest": "2.x-dev", - "phpstan/phpstan": "1.11.x-dev" + "phpstan/phpstan": "2.1.x-dev" }, "suggest": { "ext-event": "For better performance. "