定制 ecourty/okf 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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

PHP CI

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-catalog repository — see okf/SPEC.md for 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

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-12

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固