madbob/easyrdf-on-guzzle
Provides a Guzzle-backed HTTP client adapter for EasyRdf, acting as a drop-in replacement for its default client so you can fetch and parse RDF resources through Guzzle's request handling, middleware, authentication, proxies, and timeout configuration while keeping EasyRdf's familiar API.
What it is. EasyRdf is the de-facto PHP library for RDF/JSON-LD/Turtle parsing, SPARQL, and Linked Data fetching. It ships its own tiny HTTP layer (curl/stream adapters) that is functional but primitive. madbob/easyrdf-on-guzzle replaces that HTTP layer with Guzzle, so all requests EasyRdf makes — Graph::load(), Sparql\Client, content negotiation, redirects — go through a Guzzle client you control.
Why a Laravel dev cares. You almost certainly already have Guzzle configured in the app (proxies, TLS, OAuth middleware, logging, retry, cache, MockHandler in tests). This package lets you reuse that instead of maintaining a second, parallel HTTP stack just for RDF.
Install.
composer require madbob/easyrdf-on-guzzle
This pulls in easyrdf/easyrdf and guzzlehttp/guzzle. Check composer.lock afterwards — EasyRdf 1.1.x is effectively frozen upstream, and Guzzle 6 vs 7 matters (see Gotchas).
Where to look first. Open vendor/madbob/easyrdf-on-guzzle/src and the README before writing anything. The package is small; the entire surface is one or two classes. Confirm two things:
MadBob\EasyRdfOnGuzzle\Client).GuzzleHttp\ClientInterface, a handler stack, or config array.First use case. Register it as EasyRdf's global default client, then load a document:
use EasyRdf\Http;
use EasyRdf\Graph;
// use MadBob\EasyRdfOnGuzzle\Client as GuzzleHttpClient; // confirm FQCN from vendor/src
$guzzle = new \GuzzleHttp\Client([
'timeout' => 10,
'connect_timeout' => 3,
'headers' => ['Accept' => 'text/turtle, application/ld+json;q=0.9, */*;q=0.1'],
'http_errors' => false, // EasyRdf wants a response object, not an exception
]);
Http::setDefaultHttpClient(new GuzzleHttpClient($guzzle));
$graph = Graph::newAndLoad('https://example.org/resource.ttl');
echo $graph->dump();
That single setDefaultHttpClient() call is the whole integration. Everything downstream — Graph::load(), EasyRdf\Sparql\Client, EasyRdf\Serialiser fetches — inherits it.
Bind one configured client in a service provider. Don't construct Guzzle ad hoc. Put it in AppServiceProvider::boot() (or a dedicated RdfServiceProvider) so there is exactly one default EasyRdf client for the request lifecycle:
$this->app->singleton(\EasyRdf\Http\Client::class, function ($app) {
$guzzle = $app->make(\GuzzleHttp\ClientInterface::class); // your app-configured client
return new \MadBob\EasyRdfOnGuzzle\Client($guzzle);
});
// after the binding, push it into EasyRdf's static default
\EasyRdf\Http::setDefaultHttpClient($this->app->make(\EasyRdf\Http\Client::class));
Wrap RDF access in a repository, not in controllers. EasyRdf's global default client is process-wide static state; hiding it behind App\Services\Rdf\LinkedDataRepository keeps the surface small and makes testing sane.
Middleware is the main reason to do this. Attach to the handler stack:
rdfs:label lookups, FOAF crawling). Use GuzzleHttp\Middleware::cache() (Guzzle 6) or kevinrob/guzzle-cache-middleware (Guzzle 7), with a PSR-6 adapter over Illuminate\Cache\Repository.\GuzzleHttp\Middleware::retry()) for flaky endpoints — SPARQL endpoints and vocabulary hosts are notoriously unavailable.\GuzzleHttp\Middleware::log() into a dedicated channel, so you can see exactly which URIs EasyRdf reached out for.Auth against protected SPARQL endpoints. EasyRdf's SPARQL client accepts a graph/endpoint URI; the credentials live on the Guzzle client (headers, auth middleware, OAuth subscriber). This is much cleaner than EasyRdf's own setCredentials()-style plumbing.
Concurrent fan-out. EasyRdf's API is synchronous, but Guzzle is not. For "fetch N URIs and merge", use a Guzzle Pool, get raw bodies asynchronously, then parse each with Graph::parse($data, $format, $baseUri). This routinely turns a minute-long crawl into a few seconds.
$pool = new \GuzzleHttp\Pool($guzzle, $requests, [
'fulfilled' => function ($response, $index) use (&$graph) {
$graph->parse((string) $response->getBody(), 'text/turtle', $index);
},
'rejected' => fn ($reason, $index) => Log::warning("RDF fetch failed: $index", ['reason' => (string) $reason]),
]);
$pool->promise()->wait();
Content negotiation is your responsibility. EasyRdf does not always set a rich Accept header. If a server serves HTML to */*, EasyRdf will fail to parse it as RDF. Set Accept on the Guzzle client per format.
Testing. Replace the client in tests with a Guzzle MockHandler + history middleware, and remember to reset EasyRdf's static state (see Gotchas):
public function setUp(): void
{
parent::setUp();
\EasyRdf\Graph::reset();
$mock = new \GuzzleHttp\Handler\MockHandler([
new \GuzzleHttp\Psr7\Response(200, ['Content-Type' => 'text/turtle'], $this->turtleFixture),
]);
\EasyRdf\Http::setDefaultHttpClient(
new \MadBob\EasyRdfOnGuzzle\Client(new \GuzzleHttp\Client(['handler' => \GuzzleHttp\HandlerStack::create($mock)]))
);
}
setDefaultHttpClient() is global static state. Any other package, or EasyRdf\Http::getDefaultHttpClient(), will see the change. Register it once at boot, and be aware that long-running workers (Octane, queue workers) keep it across requests — that's fine if configured once, disastrous if you re-bind per request with request-scoped credentials.http_errors => false. Guzzle throws RequestException on 4xx/5xx by default; EasyRdf's client contract expects a response object it can inspect for status. Without this you get confusing exceptions from deep inside EasyRdf.$graph->getUri() is the final, post-redirect URI — otherwise relative IRIs resolve against the wrong base.Graph::newAndLoad($uri) in one process return the same graph object. In tests and in queue loops this leaks state; call \EasyRdf\Graph::reset() (or $graph->reset()) between iterations.Graph::load() alone. Use Pool explicitly if you need parallelism.Middleware::retry exists in both, but kevinrob/guzzle-cache-middleware majors differ). Confirm which the package permits in its composer.json before designing around it.decode_content is on (default) and that the response body is read with (string) $response->getBody() before handing it to Graph::parse(); EasyRdf will not rewind a stream.verify, proxy, cert) fixes vocabulary fetching app-wide. Keep a local file-based fallback for the handful of vocabularies you depend on.EasyRdf\Http\Client\Adapter\AdapterInterface (or subclassing EasyRdf\Http\Client and overriding request()). Budget for that as an exit path.'debug' => true on the Guzzle client dumps the wire; combined with Graph::dump() and the history middleware you can trace any Graph::newAndLoad() failure end to end. Also check $httpClient->getLastResponse()->getStatus() — the most common failure is a 406 from aggressive content negotiation.How can I help you explore Laravel packages today?