PHPackages                             amateescu/prov - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. amateescu/prov

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

amateescu/prov
==============

PHP implementation of the W3C Provenance Data Model (PROV-DM)

1.3.0(1mo ago)17MITPHPPHP ^8.4CI passing

Since Apr 23Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/amateescu/prov)[ Packagist](https://packagist.org/packages/amateescu/prov)[ RSS](/packages/amateescu-prov/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (5)Dependencies (8)Versions (6)Used By (0)

prov: W3C Provenance for PHP
============================

[](#prov-w3c-provenance-for-php)

[![Release](https://camo.githubusercontent.com/1787e47971fe9bba96f42275afa8ad4a06e5c0601f331d12d5e02ea526210858/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f616d617465657363752f70726f762e737667)](https://packagist.org/packages/amateescu/prov)[![CI](https://github.com/amateescu/prov/actions/workflows/ci.yml/badge.svg)](https://github.com/amateescu/prov/actions/workflows/ci.yml)

PHP implementation of the [W3C Provenance Data Model (PROV-DM)](https://www.w3.org/TR/prov-dm/).

PROV-DM describes where things come from: **entities** (things you care about), **activities** (things that happen), and **agents** (who's responsible). Relations like `wasGeneratedBy` and `wasAttributedTo` connect them to form a provenance graph.

PROV-DM fits data lineage, audit trails, scientific-workflow provenance, attribution graphs, and any case where you need to record where information came from.

This library provides a fluent builder for assembling that graph, round-trip serializers for [PROV-JSON](https://www.w3.org/Submission/prov-json/), [PROV-N](https://www.w3.org/TR/prov-n/), and [PROV-XML](https://www.w3.org/TR/prov-xml/) (plus serialize-only [PROV-JSONLD](https://www.w3.org/Submission/prov-jsonld/)), document operations (`merge`, `flatten`, semantic equality), and a partial [PROV-CONSTRAINTS](https://www.w3.org/TR/prov-constraints/) validator.

Requirements
------------

[](#requirements)

- PHP 8.4+
- `ext-dom` (only if you use `XmlSerializer`)

Installation
------------

[](#installation)

```
composer require amateescu/prov

```

Quick start
-----------

[](#quick-start)

```
use Prov\Format;
use Prov\Prov;

$builder = Prov::documentBuilder();
$builder->namespace('ex', 'http://example.org/');
$builder->entity('ex:article');
$builder->activity('ex:writing', startTime: new DateTimeImmutable('2024-01-15'));
$builder->agent('ex:alice');
$builder->wasGeneratedBy(entity: 'ex:article', activity: 'ex:writing');
$builder->wasAssociatedWith(activity: 'ex:writing', agent: 'ex:alice');

$doc = $builder->build();

$json = Prov::serialize($doc, Format::Json);
echo $json;
// Other formats: Format::ProvN, Format::Xml, Format::JsonLd.

$parsed = Prov::deserialize($json, Format::Json);
```

The static `Prov::` calls are a convenience facade. Under a dependency-injection container, construct the underlying classes directly: `DocumentBuilder`, the per-format serializers (`Format::Json->createSerializer()` / `createDeserializer()`), and `ConstraintValidator`.

> **Always pass relation arguments by name.** PROV-DM fixes a per-relation positional order that does *not* follow subject-before-object. `wasGeneratedBy` takes `(entity, activity)` but `used` takes `(activity, entity)`: the two sit in opposite orders even though they connect the same two records. Positional calls silently invert the relation:
>
> ```
> // These two lines describe DIFFERENT facts, even though both identifiers are the same:
> $builder->wasGeneratedBy('ex:article', 'ex:writing'); // article wasGeneratedBy writing ✓
> $builder->used('ex:article', 'ex:writing');           // article used writing ✗ (reversed)
>
> // Always use named arguments:
> $builder->wasGeneratedBy(entity: 'ex:article', activity: 'ex:writing');
> $builder->used(activity: 'ex:writing', entity: 'ex:article');
> ```
>
>
>
> The optional relation identifier is the *last* parameter of every relation method, so a positional call binds endpoints, never the id. Pass it by name: `wasGeneratedBy(entity: ..., activity: ..., identifier: 'ex:gen1')`.

Format support
--------------

[](#format-support)

FormatSerializeDeserializePROV-JSONyesyesPROV-NyesyesPROV-XMLyesyesPROV-JSONLDyesno (would require an RDF-aware parser)### Output ordering

[](#output-ordering)

PROV serializations are unordered (a document is a set of records, namespaces a set of declarations), so ordering never affects meaning. For stable, readable output every serializer always sorts namespace declarations: the `prov`/`xsd` built-ins first, then the rest alphabetically by prefix. Records keep the order you added them by default; pass `sortRecords: true` to a serializer to order them into PROV-DM concept order instead (elements first, then relations in component order, each group sorted by identifier):

```
$json = new JsonSerializer(sortRecords: true)->serialize($doc);
```

### PROV-N notes

[](#prov-n-notes)

The PROV-N parser accepts two convenience extensions beyond the published grammar, so input that parses here is not necessarily canonical PROV-N: line (`//`) and block (`/* */`) comments, and optional commas between a relation's arguments. Output always uses the canonical form.

PROV-N has no slot for an explicit identifier on `specializationOf`, `alternateOf`, `hadMember`, or `mentionOf`. When a document carries one of these relations *with* an identifier (legal in PROV-JSON/PROV-XML), the PROV-N serializer drops the identifier, since the grammar cannot express it. `DocumentComparator::equals()` will flag the difference on a JSON-to-PROV-N-to-JSON round trip; keep such relations in PROV-JSON or PROV-XML if their identifiers matter.

Document operations
-------------------

[](#document-operations)

```
use Prov\Operation\DocumentOperations;
use Prov\Operation\DocumentComparator;

$merged = DocumentOperations::merge($docA, $docB);
$flat = DocumentOperations::flatten($docWithBundles);            // throws if Mentions present
$flat = DocumentOperations::flattenDroppingMentions($docWithBundles);

DocumentComparator::equals($a, $b);  // structural (semantic) equality
```

Querying a document
-------------------

[](#querying-a-document)

`ProvGraph` indexes a document (or bundle) once and answers edge queries by identifier, accepting `QualifiedName` objects, `prefix:local` shorthands, or full URIs:

```
use Prov\Operation\ProvGraph;

$graph = new ProvGraph($document);

$graph->relationsFrom('ex:article');       // relations whose subject is ex:article
$graph->relationsTo('ex:writing');         // relations whose object is ex:writing
$graph->relationsReferencing('ex:plan');   // any endpoint, including secondary ones
$graph->generationsOf('ex:article');       // Generation records of an entity
$graph->usagesOf('ex:draft');              // Usage records of an entity
$graph->recordByIdentifier('ex:article');  // O(1) record lookup
$graph->agentsOf('ex:writing');            // agents associated with an activity

ProvGraph::referencedIdentifiers($relation);  // every endpoint of one relation
```

`agentsOf()` returns one `AgentInvolvement` per association, each carrying the agent, the plan the association named, the association's attributes (so `prov:role` survives), and `onBehalfOf`: the chain of agents this one acted on behalf of, walked from the `actedOnBehalfOf` delegations (nearest responsible first, activity-scoped delegations honored, cycles guarded). It reports identifiers and structure only; read an agent's `prov:type` with `recordByIdentifier()` to classify it.

The graph covers the container's own records; flatten a document first to query across bundle boundaries. For type-centric queries (`all Usage records`), `Document::getRecordsByType()` remains the right tool.

Validation
----------

[](#validation)

```
$result = Prov::validate($document);

if (!$result->isValid()) {
    foreach ($result->getViolations() as $violation) {
        echo "[C{$violation->constraintId}] {$violation->message}\n";
    }
}

// Or throw if the document has any violations:
Prov::validate($document)->throwIfInvalid();  // raises ConstraintViolationException
```

Coverage is partial: rules that need transitive graph reasoning over derivation chains aren't implemented, so `isValid() === true` only means no checked rule was violated. Use `ConstraintValidator::implementedConstraints()` or `::unsupportedConstraints()` to see the exact set.

Builder tips
------------

[](#builder-tips)

**Namespaces.** Register namespaces one at a time (`namespace()`, `addNamespace()`) or in bulk from an application-wide registry (`addNamespaces($iterable)`); `DocumentBuilder` also accepts an iterable to preload at construction. Re-registering a prefix with a different URI throws, including the `prov`/`xsd` built-ins, so a typo cannot silently corrupt a binding. `build()` prunes the declarations down to the namespaces your records actually reference, so registering many namespaces up front does not bloat the serialized output; call `keepUnusedNamespaces()` to keep them all. Documents obtained from `Prov::deserialize()` are not affected: they keep every namespace they declared.

**Attributes.** Pass attributes as an associative array: keys are resolved as namespace shorthands, and a list value adds one entry per element (that is how a repeated key is written, since PHP array keys are unique):

```
$builder->entity('ex:e1', [
    'prov:label' => 'My entity',
    'prov:atLocation' => ['ex:rack1', 'ex:rack2'],  // two prov:atLocation values
]);
```

String values stay string literals, with one exception: a `prov:type` value written as a registered shorthand (`'prov:type' => 'ex:Document'`) resolves to a qualified name, because `prov:type` values name types rather than carry text. For every other key, a string like `'workspace:stage'` is stored verbatim; pass a `QualifiedName` object when you mean a reference. `Prov\Attribute\AttributesBuilder` offers the same rules imperatively, useful when attributes accumulate across code paths:

```
$attrs = new AttributesBuilder($namespaceManager)
    ->add('prov:type', 'ex:Document')
    ->addAll('prov:atLocation', $locations)
    ->build();
```

Two `Attributes` bags combine with `$a->merge($b)`: a multimap union that keeps all values under a shared key (the way to promote a single value to several).

**Blank nodes (anonymous records):**

```
$e = $builder->blank();          // _:b1, auto-minted
$builder->entity($e);
$builder->wasGeneratedBy(entity: $e, activity: 'ex:writing');
```

Use `QualifiedName::blankNode('b1')` instead when you control the label.

**Bundles.** `withBundle()` is the recommended form: it builds the bundle eagerly, inline, without breaking the fluent chain:

```
$builder
    ->entity('ex:e1')
    ->withBundle('ex:b1', fn ($b) => $b
        ->entity('ex:e2')
        ->wasGeneratedBy(entity: 'ex:e2', activity: 'ex:a1'))
    ->build();
```

Two alternatives exist for other flows: `bundle()` returns a detached `BundleBuilder` that you drive directly and that is built lazily when the document's `build()` runs, and `addBundle()` attaches an already-built `Bundle` (for example one obtained by deserializing).

`DocumentBuilder::build()` and `BundleBuilder::build()` are single-use; a second call throws `LogicException`.

Learn more
----------

[](#learn-more)

Every public class carries an inline docblock explaining what it's for. The most useful starting points:

- `Prov\Prov`: the facade used in the examples above
- `Prov\Builder\DocumentBuilder`: the full set of record and relation methods
- `Prov\Format`: supported serialization formats
- `Prov\Constraint\ConstraintValidator`: what each PROV-CONSTRAINTS rule checks

Development
-----------

[](#development)

Before submitting a PR, run `composer check` (format, lint, analyze, tests).

See also
--------

[](#see-also)

- [`trungdong/prov`](https://github.com/trungdong/prov): Python implementation of PROV-DM.
- [`lucmoreau/ProvToolbox`](https://github.com/lucmoreau/ProvToolbox): Java toolkit for PROV.

License
-------

[](#license)

This library is made available under the MIT License. Please see [LICENSE](LICENSE) for more information.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance89

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~15 days

Total

5

Last Release

32d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/a96d6457f9940e20de1e11581ed9796156ead0140489e29457604325a831f58d?d=identicon)[amateescu](/maintainers/amateescu)

---

Top Contributors

[![amateescu](https://avatars.githubusercontent.com/u/246655?v=4)](https://github.com/amateescu "amateescu (2 commits)")

---

Tags

data-lineagephpprovprov-dmprov-jsonprovenancew3cw3c-provserializationW3CRDFprovenanceprovprov-dm

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/amateescu-prov/health.svg)

```
[![Health](https://phpackages.com/badges/amateescu-prov/health.svg)](https://phpackages.com/packages/amateescu-prov)
```

###  Alternatives

[opis/closure

A library that can be used to serialize closures (anonymous functions) and arbitrary data.

2.6k234.9M332](/packages/opis-closure)[jms/serializer

Library for (de-)serializing data of any complexity; supports XML, and JSON.

2.3k141.9M937](/packages/jms-serializer)[jms/serializer-bundle

Allows you to easily serialize, and deserialize data of any complexity

1.8k92.4M684](/packages/jms-serializer-bundle)[google/flatbuffers

FlatBuffers for PHP

26.1k148.3k5](/packages/google-flatbuffers)[apache/avro

Apache Avro™ is a data serialization system.

3.3k60.2k4](/packages/apache-avro)[flix-tech/avro-serde-php

A library to serialize and deserialize Avro records making use of the confluent schema registry

674.2M21](/packages/flix-tech-avro-serde-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
