ottosmops/ocfl
Composer 安装命令:
composer require ottosmops/ocfl
包简介
PHP implementation of the Oxford Common File Layout (OCFL) v1.1 storage specification.
关键字:
README 文档
README
ottosmops/ocfl is a PHP library for working with the Oxford Common File
Layout (OCFL) v1.1 — a storage specification for
long-term digital preservation.
It reads, writes, and validates OCFL objects against the local filesystem or any Flysystem v3 backend (S3, Azure, GCS, in-memory, …).
Features
- Domain API — readonly value objects for
Inventory,Version,User,OcflObject,StorageRoot— the whole spec modelled in types. - Read — open an existing object, list versions, resolve logical paths to content paths (respecting forward-delta dedup), stream content, or check out an entire version to a directory.
- Write — create an object, commit new versions with content
addFile/addContents,renameFile,removeFile. Forward-delta dedup and crash-safe staging (.tmp-XXXX→rename) handled automatically. - Storage layouts —
0002-flat-direct-storage-layoutand0004-hashed-n-tuple-storage-layoutout of the box, with a pluggableStorageLayoutinterface for custom extensions. - Validator — rejects all 55 OCFL bad-object fixtures with the correct spec-referenced error codes, accepts all 12 good-object fixtures, and emits 13/13 warn-object advisories.
- Pluggable storage —
LocalFilesystemby default;FlysystemFilesystemadapter for anyleague/flysystemv3 backend (S3, Azure, GCS, …). - CLI —
ocfl validate|info|listfor quick inspection from the shell, with optional--jsonoutput for scripting. - Framework-agnostic — zero required Composer runtime dependencies beyond the PHP standard library. Laravel / Symfony wrappers are easy to build on top.
Requirements
- PHP 8.3 or later
ext-hash,ext-json,ext-mbstring
Optional, for cloud storage:
league/flysystem^3.0 plus the adapter of your choice
Installation
composer require ottosmops/ocfl
Tests run against the official OCFL fixtures. Clone with submodules:
git clone --recurse-submodules https://github.com/ottosmops/ocfl
# or, if already cloned:
git submodule update --init --recursive
Quick start
Creating an object
use Ottosmops\Ocfl\OcflObject; $object = OcflObject::create( path: '/path/to/storage/my-object', id: 'urn:example:my-object', ); $object = $object->newVersion() ->addContents('README.md', "# Hello, OCFL\n") ->addFile('data/report.pdf', '/tmp/report.pdf') ->withMessage('Initial import') ->withUser('Alice', 'mailto:alice@example.com') ->commit(); $object->head(); // "v1" $object->logicalPaths('v1'); // ['README.md', 'data/report.pdf'] $object->readContent('v1', 'README.md'); // "# Hello, OCFL\n"
Committing a new version
$object = OcflObject::open('/path/to/storage/my-object') ->newVersion() ->addContents('CHANGELOG.md', "## v2\n- Added changelog\n") ->renameFile('README.md', 'README.txt') ->withMessage('Docs update') ->withUser('Alice', 'mailto:alice@example.com') ->commit(); $object->head(); // "v2" $object->resolveContentPath('v2', 'README.txt'); // → "v1/content/README.md" (dedup: not re-stored in v2)
Using a storage root with an id-to-path layout
use Ottosmops\Ocfl\Storage\StorageRoot; use Ottosmops\Ocfl\Storage\HashedNTupleStorageLayout; $root = StorageRoot::create( path: '/path/to/storage', layout: new HashedNTupleStorageLayout(), ); $root->createObject('urn:example:foo') ->newVersion() ->addContents('hello.txt', 'hi') ->commit(); // Later, in another process: $root = StorageRoot::open('/path/to/storage'); $object = $root->getObject('urn:example:foo'); $ids = $root->listObjects();
Checking out a version
$object->checkout('/tmp/snapshot-v1', 'v1'); // Materialises the logical state of v1 into /tmp/snapshot-v1, verifying // every content digest during the copy.
Validating an object
use Ottosmops\Ocfl\Validation\ErrorCode; $report = OcflObject::open('/path/to/object')->validate(); $report->isValid(); // bool — no errors $report->hasWarnings(); // bool $report->hasError(ErrorCode::E040); // bool foreach ($report->errors() as $issue) { echo "[{$issue->code->value}] {$issue->message}\n"; }
Cloud storage via Flysystem
Any league/flysystem v3 filesystem can host an OCFL storage root.
use Aws\S3\S3Client; use League\Flysystem\Filesystem as LeagueFilesystem; use League\Flysystem\AwsS3V3\AwsS3V3Adapter; use Ottosmops\Ocfl\Filesystem\FlysystemFilesystem; use Ottosmops\Ocfl\Storage\StorageRoot; use Ottosmops\Ocfl\Storage\HashedNTupleStorageLayout; $client = new S3Client([...]); $league = new LeagueFilesystem(new AwsS3V3Adapter($client, 'my-bucket')); $fs = new FlysystemFilesystem($league); $root = StorageRoot::create('/archive', new HashedNTupleStorageLayout(), $fs); $root->createObject('urn:example:foo') ->newVersion() ->addContents('doc.txt', 'content') ->withUser('Alice', 'mailto:alice@example.com') ->commit();
Content digests are streamed, not buffered — large files never need to be loaded into memory just to hash them.
Command-line usage
A small ocfl binary is shipped in bin/ (Composer installs it into
vendor/bin/ocfl).
# Validate an object; exit 0 if valid, 1 if not vendor/bin/ocfl validate /path/to/object # Print metadata vendor/bin/ocfl info /path/to/object # List all object ids below a storage root vendor/bin/ocfl list /path/to/storage-root # Create, commit, checkout vendor/bin/ocfl create /path/to/object urn:example:foo --digest=sha512 vendor/bin/ocfl commit /path/to/object \ --from=/path/to/staging-dir \ --message='Initial import' \ --user=Alice \ --user-address=mailto:alice@example.com vendor/bin/ocfl checkout /path/to/object /path/to/snapshot --version=v1 # Machine-readable output for any subcommand vendor/bin/ocfl validate --json /path/to/object
ocfl commit --from=<dir> treats the source directory as the canonical
logical state of the next version: every file below <dir> becomes a
logical path, and any file in the previous version that no longer exists
in <dir> is removed. Content dedup is automatic.
Exit codes: 0 success · 1 object invalid · 2 usage error · 3 runtime
error. Colours are emitted by default; pipe through | cat to strip them.
Laravel integration
The core package is framework-agnostic. For a Laravel app, wire it into the container yourself — no second package needed:
// app/Providers/OcflServiceProvider.php use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Storage; use League\Flysystem\Filesystem as LeagueFilesystem; use Ottosmops\Ocfl\Filesystem\FlysystemFilesystem; use Ottosmops\Ocfl\Filesystem\LocalFilesystem; use Ottosmops\Ocfl\Storage\HashedNTupleStorageLayout; use Ottosmops\Ocfl\Storage\StorageRoot; final class OcflServiceProvider extends ServiceProvider { public function register(): void { $this->app->singleton(StorageRoot::class, function () { $disk = Storage::disk(config('ocfl.disk', 'local')); // Laravel's Storage::disk() returns its own Filesystem wrapper; // grab the underlying Flysystem operator and adapt it. $fs = $disk->getDriver() instanceof LeagueFilesystem ? new FlysystemFilesystem($disk->getDriver()) : new LocalFilesystem(); return StorageRoot::open( path: config('ocfl.root', storage_path('ocfl')), fs: $fs, ); }); } }
Then anywhere in the app:
$root = app(StorageRoot::class); $object = $root->getObject('urn:example:foo');
Artisan wrappers for validate / list can shell out to vendor/bin/ocfl
or call Application::run() directly.
Validation status
The ObjectValidator emits OCFL-spec error and warning codes that link
directly to https://ocfl.io/1.1/spec/validation-codes.html.
| Category | Coverage |
|---|---|
| Good-object fixtures | 12 / 12 validate with zero errors |
| Bad-object fixtures | 55 / 55 rejected with the documented error code |
| Warn-object fixtures | 13 / 13 emit the documented advisory |
Implemented error codes: E001 E003 E007 E008 E010 E011 E013 E015 E017 E019 E023 E025 E033 E034 E036 E037 E038 E040 E041 E046 E048 E049 E050 E052 E053 E058 E060 E061 E063 E064 E066 E067 E070 E092 E093 E095 E096 E097 E099 E100 E101 E103 E107.
Implemented warnings: W001 W002 W004 W005 W007 W008 W009 W010 W011 W013.
Development
composer install composer check # pint + phpstan + pest composer test # pest composer test:coverage # pest with coverage (needs xdebug or pcov) composer analyse # phpstan level max composer format # laravel pint composer refactor # rector
CI runs on Ubuntu and macOS against PHP 8.3 and 8.4. Current test suite: 249 tests · 469 assertions · 94 % line coverage; most remaining uncovered lines are host-level I/O failure paths (mkdir-refused, hash_file-on-special-file, etc.) that can't be triggered deterministically in a unit test.
References
- OCFL 1.1 Specification
- OCFL Implementation Notes
- OCFL Validation Codes
- OCFL Community Extensions
- OCFL Fixtures (used as test corpus)
- Related implementations: ocfl-java, ocflcore (Python), rocfl (Rust), gocfl (Go).
License
See LICENSE.
ottosmops/ocfl 适用场景与选型建议
ottosmops/ocfl 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 18 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 20 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「storage」 「repository」 「versioning」 「archiving」 「digital-preservation」 「ocfl」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 ottosmops/ocfl 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 ottosmops/ocfl 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 ottosmops/ocfl 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
A SDK for working with B2 cloud storage.
repository php library
Small library to access Microsoft Windows Azure Blob Storage with a Service or a StreamWrapper.
Versioning behaviour for eloquent models
A decent, standards-compliant, Semantic Versioning (SemVer) parser and library
A Laravel package for managing model drafts.
统计信息
- 总下载量: 18
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 33
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-04-20