ecourty/okf
Composer 安装命令:
composer require ecourty/okf
包简介
A PHP parser and writer for the Open Knowledge Format (OKF) — Markdown bundles with YAML frontmatter for versionable, human-readable knowledge documentation.
关键字:
README 文档
README
A typed PHP parser and writer for the Open Knowledge Format (OKF) — directories of Markdown files with YAML frontmatter for documenting knowledge as versionable, human- and agent-readable text.
OKF is specified by Google Cloud's
knowledge-catalogrepository — seeokf/SPEC.mdfor the full spec (currently version 0.1, draft). This library is an independent PHP implementation of that spec, not an official Google product.
Requirements
- PHP 8.3+
Installation
composer require ecourty/okf
Quick Start
The Okf facade is the simplest way to use the library — read a bundle from disk, walk its concepts, and write it back:
use Ecourty\Okf\Okf; $okf = new Okf(); $bundle = $okf->read('/path/to/bundle'); foreach ($bundle->getConcepts() as $concept) { echo $concept->getId() . ': ' . $concept->getFrontmatter()->getTitle() . "\n"; } $orders = $bundle->getConcept('tables/orders'); echo $orders->getBody(); $okf->write($bundle, '/path/to/output');
Reading a Single Concept
$concept = $bundle->getConcept('tables/orders'); // throws ConceptNotFoundException if missing $frontmatter = $concept->getFrontmatter(); $frontmatter->getType(); // 'BigQuery Table' $frontmatter->getTitle(); // 'Customer Orders' $frontmatter->getTags(); // ['sales', 'orders', 'revenue'] $frontmatter->getExtra(); // ['owner_team' => 'data-platform', ...] — producer-defined keys, preserved as-is $concept->getBody(); // the Markdown body, as a string
Reading index.md and log.md
index.md and log.md are reserved filenames — the library exposes their raw Markdown body directly rather than parsing their loosely-conventional structure, so nothing is ever silently dropped:
$rootIndex = $bundle->getIndex(); // index.md at the bundle root, or null if absent $tablesIndex = $bundle->getIndex('tables'); // tables/index.md, or null if absent $rootIndex?->getOkfVersion(); // the declared OKF version, if any (root index.md only) $rootIndex?->getBody(); // raw Markdown $log = $bundle->getLog(); // log.md at the bundle root, or null if absent $log?->getBody();
Lazy Iteration
For large bundles, iterate concepts one at a time instead of building the whole Bundle aggregate in memory:
foreach ($okf->iterateConcepts('/path/to/bundle') as $concept) { // ... }
Modifying and Writing a Concept
All models are immutable — "modifying" a concept means constructing a new one:
use Ecourty\Okf\Model\ConceptDocument; use Ecourty\Okf\Model\Frontmatter; $original = $bundle->getConcept('tables/orders'); $updated = new ConceptDocument( id: $original->getId(), path: $original->getPath(), frontmatter: new Frontmatter( type: $original->getFrontmatter()->getType(), title: $original->getFrontmatter()->getTitle(), tags: [...$original->getFrontmatter()->getTags(), 'reviewed'], extra: $original->getFrontmatter()->getExtra(), ), body: $original->getBody(), ); $okf->getWriter()->writeConcept('/path/to/bundle', $updated);
Creating a New Concept
use Ecourty\Okf\Model\ConceptDocument; use Ecourty\Okf\Model\Frontmatter; $concept = new ConceptDocument( id: 'metrics/daily-active-users', path: 'metrics/daily-active-users.md', frontmatter: new Frontmatter( type: 'Metric', title: 'Daily Active Users', description: 'Count of distinct users who performed at least one action in a day.', tags: ['growth', 'engagement'], ), body: "# Definition\n\nCounted from the `events` table, deduplicated by `user_id` per UTC day.\n", ); $okf->getWriter()->writeConcept('/path/to/bundle', $concept);
Validation
Neither reading nor writing ever validates — a concept missing type parses and writes back out without error, matching the OKF spec's permissive consumption model. Validation is a separate, explicit, opt-in step:
use Ecourty\Okf\Validator\ConceptDocumentValidator; use Ecourty\Okf\Validator\ValidationMode; $validator = new ConceptDocumentValidator(); // Lenient (default): only `type` is required, per OKF SPEC.md §9. $violations = $validator->getViolations($concept, ValidationMode::Lenient); // Strict: `type`, `title`, `description` and `timestamp` are all required. $violations = $validator->getViolations($concept, ValidationMode::Strict); // Or throw directly: $validator->validate($concept, ValidationMode::Strict); // throws InvalidDocumentException
Linting a whole bundle:
foreach ($bundle->getConcepts() as $concept) { $violations = $validator->getViolations($concept, ValidationMode::Lenient); foreach ($violations as $violation) { echo "{$concept->getId()}: {$violation}\n"; } }
Unknown Keys and Round-Trip Fidelity
Producer-defined frontmatter keys — and known keys whose value doesn't match the expected type (e.g. tags given as a scalar instead of a list) — are preserved rather than dropped, so a read → write cycle never silently loses data:
$frontmatter = $concept->getFrontmatter(); $frontmatter->getExtra(); // every key not covered by the OKF spec, as given in the source YAML $frontmatter->toArray(); // known + extra keys, ready to round-trip back to YAML
Error Handling
All exceptions extend OkfException (a RuntimeException) and carry structured context:
use Ecourty\Okf\Exception\BundleNotFoundException; use Ecourty\Okf\Exception\ConceptNotFoundException; use Ecourty\Okf\Exception\FrontmatterParseException; use Ecourty\Okf\Exception\InvalidDocumentException; try { $bundle = $okf->read('/path/to/bundle'); $concept = $bundle->getConcept('tables/missing'); } catch (BundleNotFoundException $e) { echo $e->getPath(); } catch (ConceptNotFoundException $e) { echo $e->getConceptId(); } catch (FrontmatterParseException $e) { echo $e->getSource() . ': ' . $e->getReason(); // e.g. unterminated "---" block } catch (InvalidDocumentException $e) { echo $e->getConceptId() . ': ' . implode(', ', $e->getViolations()); }
Custom Filesystem
BundleParser/BundleWriter read and write through a single FilesystemInterface seam (exists/read/write/listFiles), defaulting to LocalFilesystem. Provide your own implementation to read from a zip archive, an in-memory store, a remote object store, etc.:
use Ecourty\Okf\Filesystem\FilesystemInterface; use Ecourty\Okf\Okf; use Ecourty\Okf\Parser\BundleParser; use Ecourty\Okf\Writer\BundleWriter; final class MyFilesystem implements FilesystemInterface { // exists(), read(), write(), listFiles() } $filesystem = new MyFilesystem(); $okf = new Okf( parser: new BundleParser($filesystem), writer: new BundleWriter($filesystem), );
Examples
See the examples/ directory for runnable scripts:
php examples/01-read-and-modify.php php examples/02-validate-bundle.php php examples/03-create-new-concept.php
Development
composer install # Run all tests composer test # Run specific test suites composer test-unit composer test-integration # Static analysis (PHPStan, level max) composer phpstan # Code style (PHP CS Fixer) composer cs-fix # fix composer cs-check # dry-run check # Full QA pipeline (PHPStan + CS check + tests) composer qa
License
This library is released under the MIT License.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-12