I've always been a fan of simplicity. And the simplest method to store data, and the first one that comes into mind when you are a newbie in programming, is the file system. The file API (read, write, delete, etc.) is simple, and the access is pretty fast (especially on SSDs). Also, you can easily list files, sync them and archive them. It's just beautiful and works for huge files too.
A couple of years ago, cloud services like AWS S3 and Google Cloud Storage emerged. They offered the same simple API. The files are called "objects" and are identified by keys, rather than directory names and file names. The object can have metadata, can be versioned and automatically deleted, and can be stored on multiple machines without changing the API. The only missing thing is searching for objects (the only way to search is by prefix).
I wanted all these features in my PHP apps locally and in production. And I wanted them available through a simple API. Simple API allows improvements in the backend to be made in the future. One of them could be using Memcached for speeding up some things.
So I've made Object Storage - a simple to use PHP library for storing objects as files.
Here are some of the ...
Highlights
- Basic operations: set, get, delete, append, duplicate, rename and search.
- Objects can have metadata. You can add multiple name-value pairs.
- Atomic operations. A write operation ("set" for example) locks the object until it's done and other write operations cannot interfere with the one currently running.
- Transactions. You can execute multiple actions on multiple objects that will be applied atomically. This is achieved by locking all files that need modification, prior execution.
Examples
$storage = new ObjectStorage('data/');
When you construct the Object Storage client, you provide the directory where the object will be saved. In that directory will be created three more directories:
- objects/ - stores the objects as files
- metadata/ - storage the objects metadata
- temp/ - stores the library internal data
Setting data
$storage->set([
'key' => 'users/1',
'body' => '{"name":"John Smith","email":"john@example.com"}',
'metadata.lastAccessTime' => (string) time()
]);
Getting data
$result = $storage->get([
'key' => 'users/1',
'result' => ['body', 'metadata']
]);
/*
If the object exists $result will be:
Array
(
[body] => {"name":"John Smith","email":"john@example.com"}
[metadata.lastAccessTime] => 1234567890
)
If the object does not exists $result will empty array.
*/
Appending data
$storage->append([
'key' => 'visits/ip.log',
'body' => "123.123.123.123\n"
]);
Duplicating data
$storage->duplicate([
'sourceKey' => 'users/1',
'targetKey' => 'users/2'
]);
Renaming data
$storage->rename([
'sourceKey' => 'users/2',
'targetKey' => 'users/3'
]);
Deleting data
$storage->delete([
'key' => 'users/3'
]);
Searching for data
$result = $storage->search([
'where' => [
['key', ['users/1']]
],
'result' => ['key', 'body']
]);
/*
The result is array of arrays containing the matching objects
Array
(
[0] => Array
(
[key] => users/1
[body] => {"name":"John Smith","email":"john@example.com"}
)
)
*/
// You can also use regular expresions
$result = $storage->search([
'where' => [
['key', '^users\/', 'regexp']
],
'result' => ['key', 'body']
]);
Multiple operations
$result = $storage->execute(
[
[
'command' => 'set',
'key' => 'users/9',
'body' => '{"name":"John Smith","email":"john@example.com"}'
],
[
'command' => 'append',
'key' => 'emails.log',
'body' => 'john@example.com'
],
[
'command' => 'set',
'key' => 'emails/' . md5('john@example.com'),
'body' => '9'
],
[
'command' => 'get',
'key' => 'users/9',
'result' => ['body']
],
]
);
/*
The $result variable will be:
Array
(
[0] => true // the result of the first operation
[1] => true // the result of the second operation
[2] => true // the result of the third operation
[3] => Array // the result of the fourth operation
(
[body] => {"name":"John Smith","email":"john@example.com"}
)
)
*/
The source and the latest release are available at GitHub.