<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Curl on MongrelDB</title><link>https://www.mongreldb.com/articles/tags/curl/</link><description>Recent content in Curl on MongrelDB</description><image><title>MongrelDB</title><url>https://www.mongreldb.com/assets/og-mongreldb.png</url><link>https://www.mongreldb.com/assets/og-mongreldb.png</link></image><generator>Hugo</generator><language>en-US</language><lastBuildDate>Mon, 17 Aug 2026 09:00:00 -0500</lastBuildDate><atom:link href="https://www.mongreldb.com/articles/tags/curl/index.xml" rel="self" type="application/rss+xml"/><item><title>The Transport Interface: cURL, Streams, or Your Own Adapter</title><link>https://www.mongreldb.com/articles/2026/08/the-transport-interface-curl-streams-or-your-own-adapter/</link><pubDate>Mon, 17 Aug 2026 09:00:00 -0500</pubDate><guid>https://www.mongreldb.com/articles/2026/08/the-transport-interface-curl-streams-or-your-own-adapter/</guid><description>The MongrelDB PHP client hides all of HTTP behind a one-method TransportInterface, ships cURL as the default with a stream fallback, and lets you inject a fake for tests or a PSR-18 bridge for Guzzle without touching application code.</description><content:encoded><![CDATA[<p>A database client that speaks HTTP is mostly a translation layer between your language and a daemon, and the part that actually moves bytes is the part everyone ignores until it breaks somewhere you do not control, like a shared host without ext-curl, or a test suite that should not open real sockets, or a platform team that routes every outbound call through a proxy with its own auth. The MongrelDB PHP client assumes that day comes, so instead of scattering <code>curl_exec</code> calls through the codebase it funnels every request through one small interface, and that decision is what makes the awkward environments boring.</p>
<h2 id="the-seam-is-one-method">The seam is one method</h2>
<p>The whole contract is four parameters in, one value object out:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">TransportInterface</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">/**
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">     * @param array&lt;string,string&gt; $headers
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">     * @throws ConnectionException On network errors
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">     */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">request</span>(<span style="color:#a6e22e">string</span> $method, <span style="color:#a6e22e">string</span> $url, <span style="color:#66d9ef">array</span> $headers <span style="color:#f92672">=</span> [], <span style="color:#f92672">?</span><span style="color:#a6e22e">string</span> $body <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">Response</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>Response</code> is a <code>final readonly</code> value object carrying <code>status</code>, <code>body</code>, and lowercase-keyed <code>headers</code>, with <code>json()</code> and <code>isSuccessful()</code> helpers, and that is genuinely the whole seam; the transport owns connection concerns like timeouts, TLS, pooling, and redirect policy, while everything above it, the status-code-to-exception mapping that turns a 401 into <code>AuthException</code> and a 409 into <code>ConstraintException</code>, lives in the client and never sees cURL at all.</p>
<h2 id="curl-by-default-streams-when-you-have-to">cURL by default, streams when you have to</h2>
<p>The default is <code>CurlTransport</code>, which keeps a per-request handle pool keyed by host so sequential calls reuse warm connections, the right pattern here because a database client&rsquo;s workload is sequential rather than concurrent, so <code>curl_multi</code> would buy nothing, and it enforces a 256 MB response cap so a buggy or malicious server cannot exhaust your memory, set advisory through <code>CURLOPT_MAXFILESIZE</code> against the Content-Length header and then enforced authoritatively by counting the bytes that actually arrived, and on PHP 8.5 it can opt into persistent share handles, which the previous article covered in detail. When ext-curl simply is not there, which still happens on stripped-down shared hosting builds in 2026, you drop to <code>StreamTransport</code>, the fallback built on PHP&rsquo;s native stream wrappers:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\MongrelDB</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Transport\StreamTransport</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$client <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MongrelDB</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;http://127.0.0.1:8453&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">token</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;secret&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">transport</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">StreamTransport</span>(<span style="color:#a6e22e">timeout</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">10</span>),
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>The stream fallback is honest about its limits: no keep-alive, no connection pooling, a fresh TCP setup per call, so latency climbs on chatty workloads, but the API your application code sees is identical, which is the entire point of the seam. One rule both shipped transports share is worth stating plainly, neither follows redirects, because a redirect target that is not your daemon would receive your <code>Authorization</code> header on a platter, and a transport that silently follows a 302 is a credential leak wearing a convenience feature&rsquo;s clothes.</p>
<h2 id="bringing-your-own-tests-proxies-psr-18">Bringing your own: tests, proxies, PSR-18</h2>
<p>The case that pays for the interface immediately is testing, because a fake that returns canned responses is twenty lines and turns your whole data layer into something you can exercise without a daemon:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Transport\Response</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Transport\TransportInterface</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">final</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">FakeTransport</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">TransportInterface</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">/** @var list&lt;Response&gt; */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">array</span> $queue <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">/** @var list&lt;array{string, string, array, ?string}&gt; */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">array</span> $requests <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">Response</span> $response)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">queue</span>[] <span style="color:#f92672">=</span> $response;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">request</span>(<span style="color:#a6e22e">string</span> $method, <span style="color:#a6e22e">string</span> $url, <span style="color:#66d9ef">array</span> $headers <span style="color:#f92672">=</span> [], <span style="color:#f92672">?</span><span style="color:#a6e22e">string</span> $body <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">Response</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">requests</span>[] <span style="color:#f92672">=</span> [$method, $url, $headers, $body];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">array_shift</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">queue</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">??</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Response</span>(<span style="color:#ae81ff">500</span>, <span style="color:#e6db74">&#39;{&#34;error&#34;:&#34;no queued response&#34;}&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Push a 401 onto the queue and assert your code surfaces <code>AuthException</code>, push a 409 and assert the constraint path fires, and inspect <code>$requests</code> to verify the idempotency key header actually went out, all with zero sockets. The second case is the enterprise-shaped one, where outbound HTTP must pass through a proxy or carry credentials your application never sees, and there the right move is a PSR-18 bridge over whatever client the platform already standardized on:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Psr\Http\Client\ClientExceptionInterface</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Psr\Http\Client\ClientInterface</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Psr\Http\Message\RequestFactoryInterface</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Psr\Http\Message\StreamFactoryInterface</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Exceptions\ConnectionException</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Transport\Response</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Transport\TransportInterface</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">final</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Psr18Transport</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">TransportInterface</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">__construct</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">readonly</span> <span style="color:#a6e22e">ClientInterface</span> $http,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">readonly</span> <span style="color:#a6e22e">RequestFactoryInterface</span> $requestFactory,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">readonly</span> <span style="color:#a6e22e">StreamFactoryInterface</span> $streamFactory,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">request</span>(<span style="color:#a6e22e">string</span> $method, <span style="color:#a6e22e">string</span> $url, <span style="color:#66d9ef">array</span> $headers <span style="color:#f92672">=</span> [], <span style="color:#f92672">?</span><span style="color:#a6e22e">string</span> $body <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">Response</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        $request <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">requestFactory</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">createRequest</span>($method, $url);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span> ($headers <span style="color:#66d9ef">as</span> $name <span style="color:#f92672">=&gt;</span> $value) {
</span></span><span style="display:flex;"><span>            $request <span style="color:#f92672">=</span> $request<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">withHeader</span>($name, $value);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> ($body <span style="color:#f92672">!==</span> <span style="color:#66d9ef">null</span>) {
</span></span><span style="display:flex;"><span>            $request <span style="color:#f92672">=</span> $request<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">withBody</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">streamFactory</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">createStream</span>($body));
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            $response <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">http</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">sendRequest</span>($request);
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">ClientExceptionInterface</span> $e) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">ConnectionException</span>($e<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getMessage</span>(), <span style="color:#a6e22e">previous</span><span style="color:#f92672">:</span> $e);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        $responseHeaders <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span> ($response<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getHeaders</span>() <span style="color:#66d9ef">as</span> $name <span style="color:#f92672">=&gt;</span> $values) {
</span></span><span style="display:flex;"><span>            $responseHeaders[<span style="color:#a6e22e">strtolower</span>($name)] <span style="color:#f92672">=</span> <span style="color:#a6e22e">implode</span>(<span style="color:#e6db74">&#39;, &#39;</span>, $values);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Response</span>($response<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getStatusCode</span>(), (<span style="color:#a6e22e">string</span>) $response<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getBody</span>(), $responseHeaders);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Guzzle, Symfony HttpClient, or anything else with a PSR-18 front drops in through this shape, and the wiring stays a one-line change at construction. One caveat before you paste it into production: PSR-18 has no standard timeout API, so timeouts and retry policy live on the concrete client you inject, and if you leave Guzzle or Symfony HttpClient at its defaults a dead upstream will hang your request far longer than the thirty seconds the shipped transports give you.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Database</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\MongrelDB</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$client <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MongrelDB</span>(<span style="color:#e6db74">&#39;http://127.0.0.1:8453&#39;</span>, <span style="color:#a6e22e">token</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;secret&#39;</span>, <span style="color:#a6e22e">transport</span><span style="color:#f92672">:</span> $psr18);
</span></span><span style="display:flex;"><span>$db <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Database</span>(<span style="color:#a6e22e">client</span><span style="color:#f92672">:</span> $client);
</span></span></code></pre></div><h2 id="what-belongs-in-the-seam">What belongs in the seam</h2>
<p>The discipline that keeps this interface small is knowing what not to put in it, so a custom transport should return the raw status and body and let the client map errors, it should throw <code>ConnectionException</code> only when the network itself failed, it should refuse redirects rather than follow them, and it should never grow methods for queries or transactions, because the moment transport code learns about SQL the seam has leaked and you are back to <code>curl_exec</code> in the business logic with extra steps. We used to reach for a full HTTP abstraction library just to fake a database call in a unit test, which is how a three-method mock turned into a weekend; the modern equivalent is an interface narrow enough that the fake is smaller than the test that uses it, and that is the shape worth copying.</p>
]]></content:encoded></item><item><title>Persistent cURL Sharing on PHP 8.5+</title><link>https://www.mongreldb.com/articles/2026/08/persistent-curl-sharing-on-php-8-5/</link><pubDate>Sun, 16 Aug 2026 09:00:00 -0500</pubDate><guid>https://www.mongreldb.com/articles/2026/08/persistent-curl-sharing-on-php-8-5/</guid><description>The MongrelDB PHP client opts into PHP 8.5&amp;#39;s persistent cURL share handles to carry DNS, TLS sessions, and the connection pool across FPM requests, and silently falls back to per-request pooling on 8.4.</description><content:encoded><![CDATA[<p>PHP-FPM&rsquo;s shared-nothing request model is the reason PHP scales the way it does, and it is also the reason every request starts by paying a tax it already paid five seconds ago, because the process that just served request ten thousand still opens a fresh TCP connection, negotiates TLS again, and resolves the same hostname again when request ten thousand and one arrives. For a database client that speaks HTTP to a daemon on the same host or the same rack, that handshake tax is pure waste; the query itself might take two milliseconds while the connection setup around it takes twenty, and until PHP 8.5 there was no clean way for userland code to carry that connection state across the boundary between one FPM request and the next. PHP 8.5, released in November 2025, added <code>curl_share_init_persistent</code>, and the MongrelDB PHP client uses it when it is there.</p>
<h2 id="what-a-persistent-share-handle-actually-shares">What a persistent share handle actually shares</h2>
<p>A classic cURL share handle lets handles inside one request reuse DNS results, TLS session tickets, and the connection pool, which is what the client has always done internally with its per-request handle pool keyed by host. The persistent variant takes the same idea and lifts the lifetime, so the shared state survives the request and is waiting for the next FPM invocation that hits the same daemon; PHP deduplicates persistent share handles by their option set, which means creating one per client instance is cheap, because the second call hands you back the first handle rather than building a new one.</p>
<p>The three things worth sharing are DNS resolution, TLS sessions, and the connection pool itself, exposed as <code>CURL_LOCK_DATA_DNS</code>, <code>CURL_LOCK_DATA_SSL_SESSION</code>, and <code>CURL_LOCK_DATA_CONNECT</code>, and each one removes a different slice of the handshake tax. DNS sharing kills the resolver round trip, TLS session resumption kills most of the handshake, and connection-pool sharing kills the TCP setup entirely when an idle connection is still warm, which for a <code>mongreldb-server</code> sitting on loopback or one hop away is the difference between a query that costs two milliseconds and a query that costs two milliseconds wrapped in twenty milliseconds of ceremony.</p>
<h2 id="turning-it-on">Turning it on</h2>
<p>The feature is off by default and opt-in, because a client library has no business silently carrying state across your requests, so you flip it with one constructor argument:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Database</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Sensible defaults: DNS + TLS sessions + connection pool
</span></span></span><span style="display:flex;"><span>$db <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Database</span>(<span style="color:#e6db74">&#39;http://127.0.0.1:8453&#39;</span>, <span style="color:#a6e22e">persistentSharing</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>);
</span></span></code></pre></div><p>If you want to decide exactly what gets shared, pass an explicit list of <code>CURL_LOCK_DATA_*</code> constants at the transport layer instead:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\MongrelDB</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Visorcraft\MongrelDB\Transport\CurlTransport</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Share DNS only, keep everything else per-request
</span></span></span><span style="display:flex;"><span>$transport <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">CurlTransport</span>(<span style="color:#a6e22e">persistentSharing</span><span style="color:#f92672">:</span> [<span style="color:#a6e22e">\CURL_LOCK_DATA_DNS</span>]);
</span></span><span style="display:flex;"><span>$client <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MongrelDB</span>(<span style="color:#e6db74">&#39;http://127.0.0.1:8453&#39;</span>, <span style="color:#a6e22e">token</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;secret&#39;</span>, <span style="color:#a6e22e">transport</span><span style="color:#f92672">:</span> $transport);
</span></span><span style="display:flex;"><span>$db <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Database</span>(<span style="color:#a6e22e">client</span><span style="color:#f92672">:</span> $client);
</span></span></code></pre></div><p>The defaults include <code>CURL_LOCK_DATA_CONNECT</code>, the connection-pool sharing option, only when the constant is defined, because it needs libcurl 7.67 or newer underneath; the client checks with <code>defined()</code> rather than trusting the PHP version number, since the version of PHP and the version of libcurl it was built against are two different facts.</p>
<h2 id="the-84-fallback-and-the-cookie-guard">The 8.4 fallback and the cookie guard</h2>
<p>The client supports PHP 8.4 as its floor, and it cannot require a function that did not exist until 8.5, so the detection is a plain <code>function_exists('curl_share_init_persistent')</code> at the moment the share handle would be created. On 8.4 the option quietly degrades to the per-request pooling the client always had, no exception, no warning, and no behavior change, which is exactly how a version-gated feature should behave when the floor is one release behind.</p>
<p>One share option is rejected outright, and it is rejected at construction time rather than at first request: <code>CURL_LOCK_DATA_COOKIE</code>. PHP itself refuses the same option by throwing a <code>ValueError</code>, because cookies carried across requests would leak session state between unrelated callers, and a stateless database client has no use for cookies in the first place, so the client goes one step earlier and throws its own <code>InvalidArgumentException</code> the moment you build the transport. That guard deliberately does not depend on the 8.5 runtime API, which means it catches the mistake on 8.4 too, where the persistent handle would otherwise be skipped and the bad option would sit there looking harmless.</p>
<h2 id="where-the-win-shows-up-and-where-it-does-not">Where the win shows up, and where it does not</h2>
<p>The honest tradeoff is that this is a latency feature, not a throughput feature, and it earns its keep wherever each request rebuilds the client from scratch: PHP-FPM serving many short requests against the same daemon is the canonical case, where the first query of each request used to pay full connection setup. Under FrankenPHP or a long-running worker the picture changes but does not disappear, because process lifetime and handle lifetime are different facts: if your client is bootstrapped once in worker scope and its cURL handles already survive the request boundary, the per-request pool was never the bottleneck and persistent sharing adds little, while a client instantiated per request inside a worker still pays the setup each time and still benefits, which is exactly the long-lived-SAPI shape PHP built the feature for; under a CLI script that runs one migration and exits, persistent sharing buys nothing at all, since there is no next request to inherit the state.</p>
<p>That is also why the default stays off, because the client cannot know which of those shapes it is running inside, and opting in is one boolean once you do know. The older me, the one who tuned <code>KeepAlive</code> on Apache 1.3 boxes and measured handshake overhead with <code>tcpdump</code> because nothing better existed, would have loved having this switch; the modern equivalent is a constructor argument and a runtime check, and the result is the same as it ever was, which is that the fastest connection is the one you do not have to set up again.</p>
]]></content:encoded></item></channel></rss>