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

Hardf Laravel Package

pietercolpaert/hardf

A PHP RDF library for parsing and serializing Turtle, TriG, N-Triples, and N-Quads. HARDF offers a lightweight API for reading and writing RDF triples and quads, useful for linked data and semantic web projects.

Deep Wiki
Context7

Getting Started

pietercolpaert/hardf is a small, dependency-free, low-level RDF 1.2 toolkit for PHP. It does not try to be an ORM, a graph store, or a SPARQL engine — it gives you streaming parsers and serialisers plus a handful of term/statement value objects. That narrow scope is the point: you feed it bytes, you get iterable quads, and vice versa.

Install and confirm what you got:

composer require pietercolpaert/hardf
ls vendor/pietercolpaert/hardf/src

The codebase is tiny, so the fastest onboarding path is:

  1. Read the README example once.
  2. Open src/ and skim the parser classes (TriGParser, NQuadsParser, NTriplesParser — names may vary slightly by version) and the serialiser twins.
  3. Skim the term classes: Iri, BlankNode, Literal, and the statement classes Triple/Quad.

First use case — read a Turtle/TriG file and iterate quads:

use hardf\TriGParser;

$parser = new TriGParser();
foreach ($parser->parse('data.ttl', 'https://example.org/base') as $quad) {
    printf(
        "%s %s %s %s\n",
        (string) $quad->subject,
        (string) $quad->predicate,
        (string) $quad->object,
        $quad->graph ? (string) $quad->graph : 'DEFAULT'
    );
}

First use case — write quads back out:

use hardf\TriGSerialiser;
use hardf\Quad;
use hardf\Iri;
use hardf\Literal;

$out = new TriGSerialiser('php://stdout');
$out->addQuad(new Quad(
    new Iri('https://example.org/s'),
    new Iri('https://example.org/p'),
    new Literal('hello', 'en'),
    new Iri('https://example.org/graph')
));
$out->finish(); // don't skip this — see Gotchas

If you are in Laravel, wrap the parser/serialiser in a small RdfService/RdfConverter class rather than calling it from controllers. Because everything is streaming, this fits neatly into an artisan command or a queued job without ballooning memory.

Implementation Patterns

Stream, never slurp. Both parsers and serialisers are designed around iteration. A typical pipeline is parse → transform → serialise, with each stage a generator so nothing is materialised:

function pipeline(string $in, string $out): void {
    $serialiser = new \hardf\TriGSerialiser($out);
    $parser = new \hardf\TriGParser();
    foreach ($parser->parse($in, 'https://example.org/base') as $quad) {
        $serialiser->addQuad(transform($quad)); // your rewrite
    }
    $serialiser->finish();
}

Chunked persistence in Laravel. For imports, batch quads into arrays of ~500–1000 and flush with DB::table('triples')->insert($chunk). Do not iterator_to_array() the generator or ->get() after ->cursor()-style loading — the memory win is the whole reason to use this library.

Normalise early. The quad object is the boundary between "RDF world" and "application world." Decide at the edge whether your code wants strings ((string) $term) or typed value objects, and convert once. Casting Iri/BlankNode/Literal to string is cheap and predictable; keep the object only when you need ->getDatatype() / language / direction.

Base IRI is a required mindset, not an option. Relative IRIs in Turtle/TriG resolve against the base you pass in. In production pass the document's public URL (route('rdf.show', $id)), not the filesystem path. Store what you resolved so round-trips stay stable.

Use a fixture-based test suite. RDF test suites (W3C Turtle/TriG/N-Quads manifests) are the cheapest confidence you can buy. Keep a tests/fixtures/*.ttl folder and assert against the canonical output produced by rapper/Jena once, then diff on every change to the package.

Serializer-per-format, not one mega-serialiser. Pick TriGSerialiser for graph-aware output, NQuadsSerialiser for line-per-quad dumps, Turtle for a single-graph pretty print. Treat these as interchangeable strategies behind a small interface so you can switch formats per route (.ttl, .nt, .trig) with the same domain code.

Laravel integration shape that works well:

  • App\Rdf\RdfReader — wraps parser instantiation and base-IRI resolution.
  • App\Rdf\RdfWriter — wraps serializer + finish().
  • App\Console\Commands\RdfImport — runs a streamed import.
  • App\Jobs\ExportGraph — streams to object storage via php://temp or a stream resource.
  • A Response::stream() controller method for on-the-fly Turtle serving — no full graph in memory.

RDF 1.2 features. The headline reason to choose hardf over older PHP RDF libs is RDF 1.2 support, which includes triple terms (RDF-star syntax << … >>) and directional language-tagged literals. If you're modelling provenance or reification, use the native triple-term representation rather than stringifying nested statements — it'll survive serialisation/round-tripping correctly.

Gotchas and Tips

Call finish() (or close the stream) on serialisers. Buffered output will be truncated otherwise. This is the single most common "why is my Turtle malformed" bug.

The API surface is tiny and sparsely documented — read src/. With ~38 stars and a niche user base, the README is the doc. Verify exact method names and constructor signatures against the version you installed (composer show pietercolpaert/hardf then open the files). Don't trust blog posts.

Spelling. Belgian author, so class names use British spellings (Serialiser, not Serializer). Watch your use statements.

No getGraph() on a plain Triple? Triples and quads share a shape, but default-graph quads may represent the graph as null or a default-graph IRI depending on the serialisation format. Normalise on ingest: check for both null and https://example.org/.well-known/genid/…-style default graph IRIs.

Blank node scope. BNodes are only unique within a document. If you merge multiple files into one store, prefix or remap blank-node labels per document, or you'll silently merge unrelated nodes.

Literal edges. Language tags are typically lowercased on parse; datatypes often come back as xsd:string even for plain literals. Decide once whether you canonicalise "foo" and "foo"^^xsd:string as the same thing and enforce it on ingest.

Streaming forbids seeking. You get one pass. If you need to look up a subject later, buffer just the index keyed by subject, not the whole quad set.

Unicode and escapes. Turtle's \uXXXX / \UXXXXXXXX escapes need to survive round-trip, so don't pre-decode literals before storing if you'll re-serialise. Similarly, Literal values with newlines will be escaped by the serialiser — good for N-Triples, but means byte-for-byte comparison with hand-written fixtures will fail until you normalise.

Version pinning. Release cadence is irregular and the last tagged release is future-dated in metadata you'll see in package registries — pin an exact version ("pietercolpaert/hardf": "x.y.z") and read the diff when bumping. There's no semver guarantee you can lean on given the low adoption.

Debugging tactic. When a quad comes out wrong, dump get_class($term) and the raw properties before casting to string — the string cast hides whether you have a Literal with an unexpected datatype or an Iri that failed to resolve. Cross-check against rapper -i turtle or Jena's riot once; if hardf disagrees, that's a one-line bug report to upstream, and given the repo's size you may need to fix it yourself.

Where it shines vs. alternatives. Faster and lighter than EasyRdf or ml/json-ld for raw parse/serialise work, and unlike most PHP RDF libs it's kept current with RDF 1.2. If you need SPARQL, a quad store, or JSON-LD framing, this package is one layer too low — pair it with a store rather than extending it.

Extension point. Term classes are simple value objects; the natural extension is a decorator around the parser that yields your own domain DTOs (e.g. App\Rdf\Statement) so the rest of the app never touches hardf types. That isolates you from API churn and makes swapping the underlying library a one-file change.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource