Search by

melia / object-storage

melia

File-based object store for PHP: whole object graphs by UUID in plain files, lazy loading, crash-safe writes, locking, unique and search indexes, an SQL-like query language (OQL, experimental), a CLI and a viewer.

Package info

github.com/marvinGitHub/object-storage

pkg:composer/melia/object-storage

Statistics

Installs: 43

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 1

1.0.0-rc.7 2026-11-09 21:39 UTC

This package is auto-updated.

Last update: 2026-11-09 21:40:42 UTC


README

Object Storage

ObjectStorage

Store PHP objects as they are — whole object graphs, by UUID, in plain files. No database, no schema, no mapping.

$uuid = $storage->store($order);          // the order, its customer and all its lines
$order = $storage->load($uuid);           // related objects are loaded lazily when you touch them
echo $order->customer->name;

ObjectStorage is a file-based object store for PHP. It persists your objects including everything they reference, loads related objects only when they are accessed, and keeps the data safe against crashes and concurrent access.

When to use it

  • Prototypes and small to medium applications that want to persist domain objects without designing tables first.
  • Caches and snapshots of computed or fetched data, with a lifetime per object.
  • Tools, CLI applications and background jobs that need durable state without running a database server.
  • Document-like data (configurations, drafts, workflow state) that is mostly loaded and stored as a whole.

It is not a replacement for a database server when you run large ad-hoc queries over many objects: unique and search indexes speed up lookups, and OQL (experimental) answers ad-hoc questions with joins and aggregates. Queries that no index serves scan the stored objects of a class.

Features

Object graphs Stores an object and everything it references; every object gets its own UUID, shared and circular references are kept.
Lazy loading Referenced objects are loaded on first access and then replace their placeholder transparently.
Querying List and count objects per class, filter them with PHP callbacks, refine results step by step.
Indexes Unique indexes (like a UNIQUE constraint) and search indexes with full text, filters, facets and joins (Loupe included).
OQL (experimental) An SQL-like query language with joins, paths into referenced objects, GROUP BY and INSERT/UPDATE/DELETE, in code, the CLI and the viewer.
Lifetime Optional time-to-live per object, e.g. for caches.
Concurrency Shared and exclusive locks per object, based on symfony/lock (files, Redis, Memcached, databases, …).
Durability Crash-safe writes with checksums; interrupted writes are recovered, corrupted data puts the storage into a read-only safe mode.
Caching In-memory cache by default, any PSR-16 cache (APCu, Redis, …) can be plugged in.
Events Hooks for every operation, e.g. for audit logs, indexes or metrics.
Refactoring support Renamed classes can be mapped, unknown classes still load.
Tooling A CLI for inspection and maintenance, and a small web viewer for development.

Requirements

  • PHP 8.5 or newer with ext-json
  • Symfony 8 (symfony/console, symfony/cache, symfony/lock), installed with the package
  • A writable directory for the storage

Installation

composer require melia/object-storage

Quick start

<?php

use melia\ObjectStorage\ObjectStorage;
use melia\ObjectStorage\UUID\AwareInterface;
use melia\ObjectStorage\UUID\AwareTrait;

require __DIR__ . '/vendor/autoload.php';

class Customer implements AwareInterface
{
    use AwareTrait; // lets the storage write the UUID back into the object

    public function __construct(public string $name) {}
}

class Order implements AwareInterface
{
    use AwareTrait;

    /** @var string[] */
    public array $items = [];

    public function __construct(public mixed $customer) {}
}

$storage = new ObjectStorage(__DIR__ . '/var/storage');

// store: the order and the customer are stored as two objects
$order = new Order(new Customer('Ada'));
$order->items = ['keyboard', 'mouse'];
$uuid = $storage->store($order);

// load: the customer is loaded on first access
$order = $storage->load($uuid);
echo $order->customer->name; // Ada

// update and delete
$order->items[] = 'monitor';
$storage->store($order);
$storage->delete($uuid);

Continue with Getting started for the details of storing, loading and identifying objects.

Documentation

Topic What you will find
Getting started Creating a storage, storing, loading, updating and deleting objects, UUIDs
Concepts Object graphs, lazy loading, caching and what is stored on disk
Querying Listing, counting and filtering objects
OQL Querying objects with SQL: SELECT with joins, paths into referenced objects, GROUP BY, aggregates, ORDER BY, dates and NOW(), INSERT/UPDATE/DELETE, ALTER … DROP; in code, the CLI and the viewer. Learn it with OQL by example
Unique indexes Values no two objects of a class may share, like a UNIQUE constraint; finding objects by them
Search indexes Full-text search, filters and facets with exchangeable engines (Loupe included), managed in the viewer
Lifetime Expiring objects, e.g. for caches
Copying and moving Copying and moving objects between storages, in code, the CLI and the viewer
Concurrency Locks, read-modify-write, lock backends, multiple processes
Transactions Storing and deleting several objects together, rolled back on failure and after crashes
Durability and safe mode Crash-safe writes, checksums, recovery and safe mode
Configuration Constructor options, caches, strategy and persistence policies
Events Reacting to storage operations
Serialization Which objects can be stored, hooks, renamed classes
Maintenance and tools CLI, viewer, rebuilding indexes, migrations
Internals For contributors: how loading and storing work, the naming of the methods

Examples

Complete scenarios, each runnable as is:

Versioning and stability

ObjectStorage follows Semantic Versioning from 1.0 on:

  • Covered by the backward compatibility promise: public classes, interfaces and methods, the protected methods of ObjectStorage and of classes meant to be extended (e.g. Strategy\Standard), the CLI commands and their options, and the storage format on disk. A breaking change of these only comes with a new major version.
  • Not covered: everything marked @internal (e.g. the OQL parser, planner and executor, Query\Ast), the web viewer, and the files under tests/ and profiling/. These may change in any release.
  • Experimental: OQL (melia\ObjectStorage\Query\Oql and the classes it returns, the CLI command query, the query console of the viewer) is marked @experimental. Its syntax, semantics and API may still change in a minor release; such changes are listed in the Changelog under Breaking (experimental).
  • Interfaces you implement (StrategyInterface, WriterInterface, File\IO\AdapterInterface, LockAdapterInterface, search engine interfaces) may only get new methods in a major version.
  • Storage format: data written by a 1.x version can be read by every later 1.x version. Where a release changes the format, older data is still read or a migration is shipped (see Upgrading). This includes the data of unique and search indexes and of maintenance tasks, which is stored as objects in internal storages: the class names and properties of these records (e.g. Unique\Claim, Search\Index\Definition, Util\UuidTrie, Util\Maintenance\TaskState) are part of the format, even where the classes are @internal. Search indexes can always be rebuilt from the stored objects.

Changelog and license

Changes are documented in the Changelog, upgrade steps in UPGRADE.md. The library is licensed under the Apache License 2.0.