JsonSQL PHP Class

A comprehensive guide to using the `JsonSQL` PHP class, now with advanced querying, built-in file encryption, robust data sanitization, and key management features.

Download JsonSQL.php

JSON File Structure

The `JsonSQL` class expects your JSON data to be structured as a top-level object, where each key represents an "entity" (or "table"). The value associated with each entity key must be an array of objects. Each object within these arrays represents a "row" or "item" and is expected to have a unique 'id' field.

Example JSON Structure (data.json):
{
  "users": [
    {"id": "60c72b2f7d2f9", "name": "Alice Wonderland", "email": "alice@example.com", "age": 30, "status": "active"},
    {"id": "60c72b2f7d30a", "name": "Bob The Builder", "email": "bob@example.com", "age": 25, "status": "inactive"}
  ],
  "products": [
    {"id": "60c72b2f7d31b", "name": "Laptop Pro", "price": 1200.00, "stock": 50},
    {"id": "60c72b2f7d32c", "name": "Wireless Mouse", "price": 25.50, "stock": 200}
  ]
}

Installation

  1. Download the File: Use the download button at the top of this page to get the JsonSQL.php file.
  2. Include in Your Project: Place the file in your project directory and include it in your PHP script using require_once.
require_once 'JsonSQL.php';

Basic Usage: Instantiation

To begin, create an instance of the `JsonSQL` class by providing the path to your JSON data file. If the file doesn't exist, it will be created automatically.

new JsonSQL(string $filename)
Example:
require_once 'JsonSQL.php';

try {
    // If 'my_app_data.json' does not exist in the current directory, it will be created.
    $db = new JsonSQL('my_app_data.json');
    echo "JsonSQL instance ready.";
} catch (Exception $e) {
    die("Error initializing JsonSQL: " . $e->getMessage());
}

Encryption & Keys

The class includes robust methods for encrypting your entire JSON file and managing the keys used for encryption and decryption. This is essential for protecting sensitive data at rest.

key(string $input, string $hash = null, string $mode = 'generate'): array|bool

This is the primary method for handling password keys. It can operate in two modes:

  • 'generate': Takes a plain-text password ($input) and returns an array containing the original key and its securely hashed version. You should store the returned hash.
  • 'verify': Takes a plain-text password ($input) and a stored hash ($hash) to verify if they match. Returns true or false.
Example (Generating a key and hash):
$password = "MySuperSecretPassword123";
$keyData = $db->key($password, null, 'generate');

// $keyData['key'] is your original password "MySuperSecretPassword123"
// $keyData['hash'] is the secure hash you should store in a database or config file
echo "Store this hash: " . $keyData['hash'];
Example (Verifying a key against a hash):
$storedHash = '...the hash you stored from the generate step...';
$loginPassword = "MySuperSecretPassword123"; // From user input

if ($db->key($loginPassword, $storedHash, 'verify')) {
    echo "Password is correct!";
} else {
    echo "Incorrect password.";
}
encryptFile(string $key): bool

Encrypts the entire contents of the current JSON file using the provided $key. It saves the encrypted output to a new file named data.enc in the same directory.

The original unencrypted JSON file is not deleted by this method. You should handle its deletion securely after confirming the encryption was successful.
Example:
$encryptionKey = "MySuperSecretPassword123"; // Use a strong, securely managed key

try {
    $db->encryptFile($encryptionKey);
    echo "The file has been encrypted to data.enc.";
    // You might want to delete the original file now, e.g., unlink('my_app_data.json');
} catch (Exception $e) {
    echo "Encryption failed: " . $e->getMessage();
}
decryptFile(string $key): bool

Reads the data.enc file, decrypts its contents using the provided $key, and writes the plain-text JSON back to its original filename. The data.enc file is automatically deleted upon successful decryption.

Example:
$decryptionKey = "MySuperSecretPassword123"; // The same key used for encryption

try {
    $db->decryptFile($decryptionKey);
    echo "The file has been decrypted successfully.";
} catch (Exception $e) {
    echo "Decryption failed: " . $e->getMessage();
}

Core Operations (CRUD)

These methods allow for creating, reading, updating, and deleting records. All data passed to create() and update() is automatically sanitized to prevent XSS attacks.

create(string $entity, array $item, bool $descending = true): array

Adds a new item to a specified entity.

Example:
$newProduct = [
    "name" => "Wireless Keyboard",
    "price" => 79.99,
    "stock" => 150
];
$createdProduct = $db->create('products', $newProduct);
echo "Created product with ID: " . $createdProduct['id'];
// Let's save this ID for other examples: $productId = $createdProduct['id'];
getAll(string $entity): array

Retrieves all items from a specified entity.

Example:
$allProducts = $db->getAll('products');
echo "Total products: " . count($allProducts);
getById(string $entity, string $id): ?array

Retrieves a single item by its unique id.

Example:
// Using the ID from the 'create' example
$product = $db->getById('products', $productId);
if ($product) {
    echo "Found product: " . $product['name'];
}
getLimited(string $entity, int $limit = 25, ...): array

Retrieves a limited number of items, ideal for pagination.

Example:
// Get the first 10 users, sorted by name alphabetically
$usersPage = $db->getLimited('users', 10, 0, 'name', 'asc');
print_r($usersPage);
countRows(string $entity): int

Counts the total number of items in an entity.

Example:
$userCount = $db->countRows('users');
echo "There are " . $userCount . " registered users.";
update(string $entity, string $id, array $updatedItem): ?array

Updates an existing item identified by its id.

Example:
// Update the price of the product created earlier
$updateData = ['price' => 75.50];
$updatedProduct = $db->update('products', $productId, $updateData);
if ($updatedProduct) {
    echo "Updated price for " . $updatedProduct['name'] . " to $" . $updatedProduct['price'];
}
delete(string $entity, string $id): ?array

Deletes an item from an entity using its id.

Example:
// Delete the product we created and updated
$deletedProduct = $db->delete('products', $productId);
if ($deletedProduct) {
    echo "Successfully deleted product: " . $deletedProduct['name'];
}

Advanced Querying (where())

The where() method provides a powerful way to filter data based on various comparison operators, mimicking SQL's WHERE clause.

where(string $entity, string $field, string $operator, mixed $value = null): array
OperatorDescription
=, ==Equals (loose comparison).
<>, !=Not equals (loose comparison).
>, <, >=, <=Greater/Less than (numeric comparison).
is_nullField value is null or the field does not exist.
is_not_nullField exists and its value is not null.
is_emptyValue is empty ("", 0, null, etc.) and not false.
is_not_emptyField exists and its value is not empty.
Example:
// Find all products with stock greater than 100
$highStockProducts = $db->where('products', 'stock', '>', 100);
print_r($highStockProducts);

// Find all users who are not active
$inactiveUsers = $db->where('users', 'status', '!=', 'active');
print_r($inactiveUsers);

Searching

search(string $entity, mixed $value, array|bool $fields = false): ?array

Searches for items with an exact match for a value.

Example:
// Find a user by their exact email address
$user = $db->search('users', 'alice@example.com', ['email']);
if ($user) {
    echo "Found user with matching email: " . $user[0]['name'];
}
isLike(string $entity, string $pattern): bool

Checks if any item contains a substring. Returns true or false.

Example:
if ($db->isLike('products', 'Laptop')) {
    echo "The database contains products with 'Laptop' in their data.";
}
orContain(string $entity, string $pattern): bool

Alias for isLike().

Example:
if ($db->orContain('users', 'Wonderland')) {
    echo "A user with 'Wonderland' in their name exists.";
}
isBetween(string $entity, string $field, mixed $lowerBound, mixed $upperBound): ?array

Finds items where a field's value falls within a given range (inclusive).

Example:
// Find products with stock between 10 and 50 units
$lowStockProducts = $db->isBetween('products', 'stock', 10, 50);
print_r($lowStockProducts);

Sorting (sort())

sort(string $entity, string $field, string $order = 'asc'): array

Sorts the items within an entity based on a field. This method returns the sorted data but does not persist the sort order to the JSON file.

Example:
// Get all users, sorted by age from oldest to youngest
$usersByAge = $db->sort('users', 'age', 'desc');
print_r($usersByAge);

Aggregation

calculate(string $entity, string $operator, string $field): float|int|null

A convenient wrapper for calculations. Supported operators: 'sum', 'average' or 'avg', 'max', 'min'.

Example:
// Find the average price of all products
$avgPrice = $db->calculate('products', 'avg', 'price');
echo "The average product price is: $" . number_format($avgPrice, 2);
countIf(string $entity, string $field, mixed $value): int

Counts how many items match a specific value in a given field.

Example:
// Count how many users have an 'active' status
$activeUserCount = $db->countIf('users', 'status', 'active');
echo "Number of active users: " . $activeUserCount;

File Management

createFile(string $filename, array $initialData = []): bool

Explicitly creates a new, separate JSON file. Throws an exception if the file already exists.

Example:
try {
    $initialConfig = ['settings' => ['theme' => 'dark', 'notifications' => true]];
    $db->createFile('config.json', $initialConfig);
    echo "Successfully created config.json";
} catch (Exception $e) {
    echo "Could not create file: " . $e->getMessage();
}
getFileSize(): string

Retrieves the size of the current JSON data file in a human-readable format.

Example:
echo "Current database file size: " . $db->getFileSize();

Data Import/Export

export(string $entity, string $csvFilename, array|bool $selectedFields = false): void

Exports data from an entity to a CSV file.

Example:
// Export just the name and stock of all products to a CSV file.
try {
    $db->export('products', 'inventory.csv', ['name', 'stock']);
    echo "Inventory list exported.";
} catch (Exception $e) {
    echo "Export failed: " . $e->getMessage();
}
import(string $entity, string $csvFilename, array|bool $selectedFields = false): void

Imports data from a CSV file into an entity. The CSV must have a header row.

Example:
// First, create a CSV file named 'new_users.csv' in the same directory
// with the following content:
/*
name,email,age,status
"David Chen",david@example.com,42,"active"
"Eva Green",eva@example.com,29,"inactive"
*/

try {
    $db->import('users', 'new_users.csv');
    echo "New users imported successfully.";
} catch (Exception $e) {
    echo "Import failed: " . $e->getMessage();
}
exportToSqlite(string $entity, string $dbPath, string $tableName): void

Exports data to a table in a SQLite database file. All fields are created as TEXT.

Example:
// Create a SQLite backup of the users table
try {
    $db->exportToSqlite('users', 'backup.sqlite', 'archived_users');
    echo "Users table backed up to SQLite.";
} catch (Exception $e) {
    echo "SQLite export failed: " . $e->getMessage();
}

Security Considerations

  • File Encryption: Use the encryptFile() method to protect sensitive data at rest. Manage your encryption keys securely and never hard-code them in your scripts.
  • Automatic Data Sanitization: All string data passed to create() and update() is automatically sanitized via the internal cleanData() method, which uses htmlspecialchars() and removes script tags to prevent XSS attacks.
  • SQL Injection: Table names and data values are sanitized and handled with prepared statements during SQLite export to prevent SQL injection.

Error Handling

The class uses PHP's Exception mechanism for error handling. It's crucial to wrap your calls in try-catch blocks to gracefully handle potential issues.

try {
    // Attempt to decrypt a file with the wrong key
    $db->decryptFile("this-is-the-wrong-key");
} catch (Exception $e) {
    // Handle the error
    error_log("Decryption failed: " . $e->getMessage());
    die("Could not access the data. Please contact support.");
}

Limitations

  • Performance: Not suitable for very large datasets or high-traffic sites as operations involve reading/writing the entire JSON file. Encryption and decryption add further performance overhead.
  • Concurrency: Lacks built-in file locking, which can lead to data corruption with simultaneous write requests.
  • Complex Queries: Does not support JOINs, subqueries, or complex transactions.