Search by

OpenAPI 3.2, 3.1 and 3.0 emitters, semantic API diffing and breaking-change detection: the framework-agnostic engine behind Docuccino.

Package info

github.com/docuccino/php-core

Homepage

Issues

pkg:composer/docuccino/core

Statistics

Installs: 3 163

Dependents: 2

Suggesters: 0

Stars: 0

v0.23.4 2026-11-01 15:02 UTC

README

Latest version Downloads PHP version CI License

The framework-agnostic engine behind Docuccino — the document model, canonicalizer, identities, OpenAPI emitters and semantic diff.

Core compiles an application into an OpenAPI 3.2-shaped JSON document that is deterministic and carries, under one reserved x-docuccino member, a stable identity and provenance for every operation, schema and parameter. From it, core emits OpenAPI 3.2 and 3.1, validates against the published JSON Schema, and answers what changed between two documents rather than only what the endpoints are.

It has no framework dependency and no static-analysis dependency. Framework adapters consume it — if you are documenting a Laravel application, you want docuccino/laravel, which pulls this in for you.

Install

composer require docuccino/core

Usage

Emit a canonical OpenAPI 3.2 document from a built document:

use Docuccino\Core\Emit\EmitOptions;
use Docuccino\Core\Emit\OpenApi32Emitter;

$json = (new OpenApi32Emitter())->emit($uirDocument, new EmitOptions());

Compare two documents over their identities, and classify what moved:

use Docuccino\Core\Diff\DocumentDiffer;

$changes = (new DocumentDiffer())->diff($oldDocument, $newDocument);

foreach ($changes->changes as $change) {
    $change->path;                   // 'GET /pets/{id} responses 200 application/json schema.properties.name'
    $change->subject?->toArray();    // ['kind' => 'operation', 'method' => 'GET', 'path' => '/pets/{id}']
}

A change's subject is what it is about — an operation, a component, a page — as members, so changes group without taking path apart, where a dot in a component name reads like a step into it.

Read a document someone else wrote — a full artifact, an export, or any OpenAPI 3.x description, as JSON or YAML — and ask what it is, where it fails its specification, and what it holds:

use Docuccino\Core\Reader\DocumentReader;

$document = (new DocumentReader())->read($bytes, 'application/yaml');

$document->inspect()->capability;    // Capability::Full, ::Identities or ::Plain
$document->inspect()->info->version; // what `info` says: '1.4.0', or null where it states none
$document->validate()->isValid();    // OpenAPI meta-schema, plus the extension schema for a full document
$document->nodes();                  // one DocumentNode per operation, parameter, response and schema
$document->contentHash();
$document->json();                   // the document as read, as JSON: what every node pointer points into
$document->position('/paths');       // SourcePosition: where it is written, line and column, or null

Every finding validate() returns carries its position too, so a CI annotation can point at the line rather than the file, in JSON and YAML alike. Positions are worked out from the text only when asked for.

read() throws UnreadableDocumentException, with a reason you can show whoever uploaded the bytes and a ReadFailure to branch on, when they are not a document it can read — including one that only grows past its own size once walked, through YAML aliases or paths sharing a path item. Where the fault is in one place, such as where JSON stops parsing, it says where. The differ refuses the same way: IncomparableDocumentsException::$failure is DiffFailure::ReferenceExpansion for two documents whose comparison would read their shared nodes far more often than any document needs.

An Arazzo description, 1.0 or 1.1, reads the same way, under the same bounds:

use Docuccino\Core\Reader\ArazzoReader;

$arazzo = (new ArazzoReader())->read($bytes, 'application/yaml');

$arazzo->version();                    // ArazzoVersion::V1_1, or null where no schema ships for it
$arazzo->validate()->findings;         // against the Arazzo schema for that version, each with its position
$arazzo->description()->workflows;     // list<Workflow>, the model declared workflows use too
$arazzo->description()->workflows[0]->outcomes(0); // what the first step does next, on success and failure
$arazzo->positions(['/workflows/0/steps/1']);       // where each is written

Part of Docuccino

Package Role
docuccino/laravel The Laravel adapter: provider, config, commands, viewer, integrations. Start here.
docuccino/core ← you are here Framework-agnostic document model, canonicalizer, identities, emitters, diff.
docuccino/inference-phpstan PHPStan + Larastan type inference. Install as a dev dependency.
docuccino/attributes Dependency-free PHP attribute classes.

Documentation

Full documentation is at docs.docuccino.app. See especially the the Docuccino extension, schema hosting and writing an extension. The versioned JSON Schema is served at https://spec.docuccino.app/uir/2.0/schema.json, with the extension half at https://spec.docuccino.app/uir/2.0/extension.schema.json.

Issues and contributing

This repository is a read-only subtree split of docuccino/docuccino. Open issues and pull requests on the monorepo — commits pushed here are overwritten. See CONTRIBUTING.md.

License

MIT. See LICENSE.

This package redistributes the OpenAPI Initiative's specification JSON Schemas under the Apache License 2.0, unmodified, and validates every document it emits against them. See NOTICE.