isInstagramUrl($url)) { return $this->download($url, $pathWithoutExtension); } return null; } private function download(string $url, string $pathWithoutExtension): ?string { // Stream the response straight to disk via Guzzle's `sink` option // instead of buffering the whole body in memory — a several-MB // video/reel can easily exceed PHP's default memory_limit if // fetched the normal way (Http::get()->body() reads it all into a // string first). The extension isn't known until we see the // response, so this writes to a temp file and renames it after. $tempPath = $pathWithoutExtension.'.downloading-'.uniqid(); $absoluteTempPath = Storage::disk('public')->path($tempPath); File::ensureDirectoryExists(dirname($absoluteTempPath)); try { $response = Http::withHeaders(['User-Agent' => 'Mozilla/5.0']) ->timeout(60) ->withOptions(['sink' => $absoluteTempPath]) ->get($url); } catch (Throwable $e) { Log::warning(static::class.": failed to fetch {$url}: {$e->getMessage()}"); @unlink($absoluteTempPath); return null; } if (! $response->successful()) { Log::warning(static::class.": got {$response->status()} fetching {$url}"); @unlink($absoluteTempPath); return null; } $path = $pathWithoutExtension.'.'.$this->guessExtension($url, $response->header('Content-Type')); Storage::disk('public')->move($tempPath, $path); return '/storage/'.$path; } private function guessExtension(string $url, ?string $contentType): string { $fromUrl = pathinfo(parse_url($url, PHP_URL_PATH) ?? '', PATHINFO_EXTENSION); if ($fromUrl && strlen($fromUrl) <= 4) { return strtolower($fromUrl); } return str_contains((string) $contentType, 'video') ? 'mp4' : 'jpg'; } }