Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Curl Bundle Laravel Package

synaq/curl-bundle

Synaq CurlBundle integrates cURL into Symfony applications, providing a configurable HTTP client service for making requests and handling responses. It simplifies setup and use of cURL-based calls within Symfony projects, making it easier to fetch URLs and communicate with external services.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony2-era bundle (last touched around the Symfony 2.1–2.3 timeframe, zero dependents on Packagist). Treat it as a thin, opinionated wrapper around PHP's ext-curl, not a modern HTTP client. Before anything else, confirm it will even install in your app.

  1. Install and pin

    composer require synaq/curl-bundle
    

    If you're on a modern Symfony/PHP stack, add "config": { "platform": { "php": "5.6.40" } } or use composer require --ignore-platform-reqs only long enough to inspect the source. In practice you'll find it's easier to vendor the class than fight resolution.

  2. Register the bundle in app/AppKernel.php (or config/bundles.php if you've backported):

    new Synaq\CurlBundle\SynaqCurlBundle(),
    
  3. Look here first, in this order:

    • DependencyInjection/Configuration.php — the only authoritative list of config keys (timeout, user_agent, proxy, verify_peer, etc.).
    • Resources/config/services.xml — the real service id you'll inject. Names vary by release (synaq.curl, synaq_curl, synaq_curl.curl); confirm with php app/console debug:container | grep -i curl.
    • Curl/Curl.php (or Curl/CurlRequest.php) — the public methods you actually call.
    • README.md — usually a two-line example; don't trust it over the source.
  4. Minimal config (append to app/config/config.yml):

    synaq_curl:
        timeout: 10
        user_agent: "AcmeApp/1.0"
        verify_peer: true
    
  5. First use case — the canonical one is "call a third-party JSON API from a service." Inject the service, do a GET, read the body:

    class WeatherService
    {
        private $curl;
    
        public function __construct($curl)
        {
            $this->curl = $curl;
        }
    
        public function current($city)
        {
            $this->curl->get('https://api.example.com/weather', ['city' => $city]);
    
            if ($this->curl->getHttpStatus() !== 200) {
                throw new \RuntimeException('Weather API failed: '.$this->curl->getError());
            }
    
            return json_decode($this->curl->getResponse(), true);
        }
    }
    

    Wire it with arguments: ["@synaq.curl"] (adjust the id to whatever debug:container reported).

Implementation Patterns

Wrap it, don't sprinkle it. The service holds mutable state (last response, last status, last error), so sharing one instance across a request is asking for crosstalk. The dominant pattern is a per-domain client that news up or clones the curl object per call:

class GithubClient
{
    public function __construct(private $curlFactory) {}

    private function request(string $method, string $path, array $opts = [])
    {
        $curl = clone $this->curlFactory;   // avoid shared response state
        $curl->{$method}('https://api.github.com'.$path, $opts);
        $status = $curl->getHttpStatus();

        if ($status >= 400) {
            throw new ApiException($curl->getResponse(), $status);
        }

        return json_decode($curl->getResponse(), true);
    }
}

Configuration-driven environments. Keep base URLs, timeouts, proxy, and CA bundle paths in parameters.yml and override per environment. If the bundle doesn't ship a base_url key (it usually doesn't), put it in your own client and leave the bundle responsible only for transport concerns.

Verb helpers as thin sugar. The bundle's value is really get/post/put/delete/download plus option merging. Compose them:

// JSON POST
$curl->post($url, json_encode($payload), [
    'Content-Type: application/json',
    'Accept: application/json',
]);

If the method signature doesn't accept headers, set them via curl_setopt on $curl->getHandle() (most versions expose the raw handle) — but wrap that in your own class so you can drop the bundle later.

Downloads and large payloads. Use the download-style method (or CURLOPT_FILE) and stream to a temp file, then move atomically. Never getResponse() a 200 MB artifact into a PHP string.

Retry and backoff at the client layer. The bundle has no retry, circuit breaker, or middleware. Build a tiny decorator around your client:

for ($try = 0; $try < 3; $try++) {
    try { return $client->fetch($url); }
    catch (TransientApiException $e) { usleep(100000 * 2 ** $try); }
}

Testing. Because the service is a concrete object with side effects, mock at your client interface, not at synaq.curl. In a Symfony2 functional test, swap the service id for a stub in config_test.yml. Don't try to mock curl_exec — you'll lose a day.

Migrating off it. The cleanest pattern is a seam from day one: define interface HttpTransport { public function request(...): Response; }, implement SynaqCurlTransport, and later swap in symfony/http-client. The bundle's own classes shouldn't leak past that boundary.

Gotchas and Tips

  • It's effectively abandoned. Zero stars, zero dependents, Symfony2-era service definitions. Expect incompatibilities with Symfony 4+ (service ids, services.xml schema, ContainerAware usage) and with PHP 7.4+ in older releases (dynamic properties, each(), missing return types). Vendor the two or three classes you need rather than depending on the Composer package.
  • Service id is not stable. Verify with debug:container. If you reference a wrong id you get a confusing ServiceNotFoundException at compile time, not a helpful message.
  • Mutable last-response state. A single injected instance remembers the previous call's status and error. If you check getHttpStatus() after a different service ran, you'll read stale data. Clone per request or instantiate fresh.
  • Errors are silent by default. curl_exec returns false on transport failure while HTTP 4xx/5xx look like success. Always check both getError()/curl_errno and the HTTP status. A 500 with a JSON body is not an exception unless you make it one.
  • SSL verification. Old releases default to CURLOPT_SSL_VERIFYPEER => false in some environments, or fail on missing CA bundles. Set verify_peer: true and point cacert at a real bundle in config.yml; on Debian/Ubuntu that's /etc/ssl/certs/ca-certificates.crt.
  • Timeouts are global config, not per call. If you need a 2s call to a health endpoint and a 60s upload, don't fight the bundle — override CURLOPT_TIMEOUT / CURLOPT_CONNECTTIMEOUT on the handle inside your wrapper.
  • Redirects and cookies. CURLOPT_FOLLOWLOCATION is often off by default; enabling it without CURLOPT_MAXREDIRS turns a misconfigured endpoint into a redirect loop. Cookie handling is per-handle, so a cloned object starts with a fresh jar.
  • Verb helpers may URL-encode differently than you expect. get($url, $params) in older releases concatenates with http_build_query, which uses + for spaces. Some strict APIs reject that; pass an already-encoded query string if needed.
  • Extension points are thin but real. The bundle typically exposes a curl.options config array merged into every request — use that for proxies, user agents, and global headers. For anything per-request, extend or decorate the curl class and rebind the service id to your subclass in services.yml.
  • Debugging. Set CURLOPT_VERBOSE => true and a CURLOPT_STDERR stream to a log file, or log curl_getinfo($handle) after each call (timings, effective URL, redirect count). A PSR-3 logger injected into your wrapper beats var_dump inside the bundle forever.
  • Memory. For downloads always set CURLOPT_FILE; for uploads use CURLOPT_INFILE/CURLOPT_UPLOAD. Loading a body into a string is the most common production surprise with these wrappers.
  • Plan the exit. Given the maintenance status, wrap this bundle behind your own interface on day one. The migration to symfony/http-client or Guzzle will then be a one-file change, and you won't be rewriting every call site.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle