Skip to content
johnSSGPublic

About

A NoSQL JSON Document Storage Class in PHP

Resources

Stars

12 stars

Watchers

4 watching

Forks

Latest commit

 

History

37 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

SimpleDB

SimpleDB is a small PHP class that provides a straightforward API for storing and retrieving JSON documents. Each record is stored as an individual .json file, and each storage folder acts like a table or collection.

It is intended for small applications and simple datasets that do not require the indexing, transactions, or scaling features of a relational database.

Requirements

  • PHP 5.5 or newer
  • A writable storage directory
  • A cryptographically secure random source provided by random_bytes() or OpenSSL

A currently supported PHP release is strongly recommended.

Folder structure

The default structure after running setup and creating a collection is:

PROJECT ROOT
├── models
│   └── Simple_DB.php
└── json
    ├── .htaccess
    └── COLLECTIONS
        └── RECORDS.json

When Simple_DB.php is stored in models/, the class uses the json/ directory at the same level as models/. It creates the JSON root and selected collection directory when necessary. The PHP process must have permission to create and write files there.

One-time setup

Signature

public static function setup(
    $baseDirectory = null,
    $replaceHtaccess = false
);

setup() prepares the shared JSON storage root before any collections are created. It is a static method, so call it on Simple_DB rather than on a database instance.

Default setup

After including or autoloading the class, run:

$jsonRoot = Simple_DB::setup();

This creates the default json/ directory and, when one does not already exist, writes json/.htaccess containing:

# Generated by SimpleDB.
Require all denied

The returned value is the resolved absolute path to the JSON root. Setup only needs to be run during installation or deployment, not before every request.

Then construct collections normally:

$simpleDB = new Simple_DB('car');

The constructor creates the collection directory because setup() does not know which collection names the application will use.

Parameters

Parameter Default Description
$baseDirectory null JSON root directory. null selects json/ beside the models/ directory.
$replaceHtaccess false When true, deliberately replaces an existing regular .htaccess file with SimpleDB's Apache rule.

Custom storage root

Pass the same custom root to setup and the constructor when storing data somewhere else:

$jsonRoot = Simple_DB::setup(
    '/absolute/path/to/json'
);

$simpleDB = new Simple_DB('car', $jsonRoot);

This creates the root if necessary, creates its .htaccess, and stores car records in /absolute/path/to/json/car/.

Existing .htaccess files

For safety, setup preserves an existing .htaccess without changing or validating its rules. To deliberately replace it with SimpleDB's Apache 2.4 rule, pass true as the second argument:

Simple_DB::setup(
    '/absolute/path/to/json',
    true
);

Replacing the file removes all existing rules from it. The routine refuses to read through or replace a symbolic-link .htaccess and throws RuntimeException when setup cannot be completed. Only use replacement when discarding the existing rules is intentional.

Return value and exceptions

On success, setup() returns the resolved absolute JSON root path as a string. It throws:

  • InvalidArgumentException when the base-directory argument is invalid.
  • RuntimeException when it cannot create or resolve the directory, encounters an unsafe .htaccess target, or cannot write the protection file.

The generated .htaccess rule applies to Apache 2.4. Nginx, IIS, and other web servers ignore it and require equivalent server configuration.

Using the constructor without setup

The constructor can still be used without calling setup():

$simpleDB = new Simple_DB('car');

This creates json/car/, but it does not create .htaccess. Run setup() first when the storage root needs Apache protection. Collection names may contain letters, numbers, underscores, and hyphens.

Custom storage location

Pass a JSON root directory as the optional second constructor argument:

$simpleDB = new Simple_DB(
    'car',
    '/absolute/path/to/json'
);

The custom path is the JSON root, not the individual collection directory. The example above stores records in /absolute/path/to/json/car/.

The constructor throws InvalidArgumentException for an invalid collection name or path argument and RuntimeException when it cannot create or resolve the storage directories.

Basic usage

The four primary methods resemble common HTTP operations:

$simpleDB->get($id);
$simpleDB->post($content);
$simpleDB->put($id, $content);
$simpleDB->delete($id);

get()

Retrieve every record in the collection:

$cars = $simpleDB->get();

This returns an associative array keyed by document ID. Each value is a decoded object. It returns false when no readable records exist.

Retrieve one record by ID:

$car = $simpleDB->get($id);

This returns the decoded record or false when the ID is invalid, missing, or unreadable. The .json extension is optional when supplying an ID.

post()

Create a record:

$id = $simpleDB->post([
    'manufacturer' => 'Honda',
    'model' => 'Civic',
    'color' => 'blue'
]);

post() accepts a string, array, or object. The content is stored as a JSON object. On success it returns a cryptographically random 32-character hexadecimal ID; otherwise it returns false.

The generated filename is ID.json.

put()

Replace an existing record:

$updatedCar = $simpleDB->put($id, [
    'manufacturer' => 'Honda',
    'model' => 'Civic',
    'color' => 'red'
]);

put() only updates an existing document. It does not create records with custom IDs. It returns the updated content as an object or false when the ID does not exist, the ID is invalid, JSON encoding fails, or the record cannot be written.

delete()

Delete a record:

$deleted = $simpleDB->delete($id);

The ID is required. The method returns true when the record is deleted and false when it does not exist or cannot be deleted.

Querying records

query() finds records whose stored fields match all supplied criteria. It accepts a URL query string, associative array, or object.

Query string

$cars = $simpleDB->query(
    'color=blue&manufacturer=Honda'
);

Associative array

$cars = $simpleDB->query([
    'color' => 'blue',
    'manufacturer' => 'Honda'
]);

Object

$cars = $simpleDB->query((object) [
    'color' => 'blue',
    'manufacturer' => 'Honda'
]);

Nested criteria

Nested arrays and objects are supported:

$cars = $simpleDB->query([
    'manufacturer' => 'Honda',
    'specifications' => [
        'doors' => 4,
        'transmission' => 'automatic'
    ]
]);

Queries use these rules:

  • Every supplied field must match.
  • Field names must match actual fields; arbitrary text is not searched.
  • Scalar values are compared as strings without regard to letter case.
  • Partial string matches are not performed.
  • Nested criteria are matched recursively.

The result is an associative array keyed by document ID. query() returns false when no records match or the criteria are invalid.

Additional methods

returnSingleId()

Return the first document ID from a result array:

$cars = $simpleDB->query(['model' => 'Civic']);
$carId = $simpleDB->returnSingleId($cars);

This returns the first key or false when the supplied value is not a non-empty array. It is intended for queries expected to return one record.

For multiple records, use their array keys:

$cars = $simpleDB->query(['color' => 'blue']);

if ($cars !== false) {
    foreach ($cars as $carId => $car) {
        // $carId is the document ID.
    }
}

timestamp()

Return a record's last-modified time as a Unix timestamp:

$time = $simpleDB->timestamp($id);

The method returns an integer timestamp or false when the ID is invalid, missing, or unreadable.

getJSON()

Retrieve one record as its literal JSON string:

$json = $simpleDB->getJSON($id);

Calling getJSON() without an ID returns an associative array in which every document ID maps to its raw JSON string. It returns false when no readable records exist.

getFiles()

List the valid JSON record filenames in the current collection:

$files = $simpleDB->getFiles();

The method returns a sorted array of filenames. Non-JSON files, invalid record names, directories, symbolic links, and internal lock files are excluded.

getLastError()

Methods normally report operational failures by returning false. Retrieve a description immediately afterward with:

$car = $simpleDB->get($id);

if ($car === false) {
    $message = $simpleDB->getLastError();
}

getLastError() returns the most recent error message or null. Operations that access or modify storage clear the previous error before they begin.

Concurrency and file safety

SimpleDB uses a collection-level lock and atomic file replacement to prevent cooperating PHP requests from reading partially written records or corrupting files during simultaneous operations. It also validates collection names and record IDs and rejects record-file symbolic links.

These protections do not turn the class into a transactional database. If the application requires transactions, indexes, relationships, large datasets, or heavy concurrent traffic, use SQLite or a database server instead.

Protecting stored data

The safest arrangement is to place the JSON root outside the web server's public document directory:

$simpleDB = new Simple_DB(
    'car',
    '/srv/my-application-data/json'
);

If the json/ directory must be inside an Apache 2.4 document root, place this in json/.htaccess:

Require all denied

The one-time Simple_DB::setup() routine creates this file automatically when one does not already exist.

This only works when Apache is configured to honor .htaccess overrides for that directory. The older Order, Allow, and Deny directives are deprecated. Other web servers, including Nginx and IIS, require equivalent server-specific configuration.

Do not rely on unpredictable filenames to protect sensitive data.

Migration notes

Compared with the original implementation:

  • Newly created IDs are secure 32-character hexadecimal strings.
  • put() updates existing records only.
  • query() accepts query strings, arrays, and objects.
  • Queries support nested fields and perform exact field comparisons.
  • Writes are locked and published atomically.
  • Invalid IDs and symbolic-link records are rejected.
  • getJSON() correctly returns its result.
  • getLastError() provides failure details.
  • setup() can create the JSON root and Apache protection file.

Existing IDs created by the original uniqid() implementation remain valid as long as they contain only letters, numbers, underscores, or hyphens.

License

SimpleDB is distributed under the MIT License included in Simple_DB.php.

About

A NoSQL JSON Document Storage Class in PHP

Resources

Stars

12 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages