SleekDB - A NoSQL Database made using PHP

SleekDB is a simple flat file NoSQL like database implemented in PHP without any third-party dependencies that store data in plain JSON files.

It is not designed to handle heavy-load IO operations, it is designed to have a simple solution where all we need a database for managing a few gigabytes of data. You can think of it as a database for low to medium operation loads.

😎 Manage Static Data With Ease

SleekDB works great as the database engine for low to medium traffic websites. It makes simple database effortless.

📝 Your Dynamic Website Become Static

SleekDB will cache all query data by default, in a way that would make any dynamic site as fast as static sites.

🗄 Flat File Database

SleekDB is also a flat file database, but instead of a single file it stores data in multiple JSON files. That allows it to have a better concurrency support comparing to a FlatFile database system. SleekDB can be compared with other flat file database systems because the final query output will be cached and later reused from a single file instead of traversing all the available files.

🥳 You Are Not Alone!

SleekDB is being used with many websites and applications which are in production right now. The core team consists of two developer, they are actively contributing to the development of SleekDB Database.

👋 Say Hi!

Join our Discord channel. We would love to know about you and your feedbacks.

Features

  • Lightweight, faster

    Stores data in plain-text utilizing JSON format, no binary conversion needed to store or fetch the data. Default query cache layer.

  • Schema free data storage

    SleekDB does not require any schema, so you can insert any types of data you want.

  • Query on nested properties

    As it supports schema free data, so you can filter and use conditions on nested properties of the JSON documents!

    If you write this where clause:

    where( 'post.author.role', '=', 'admin' )

    SleekDB will look for data at:

    {
    "post": {
      "author": {
        "role": "admin"
      }
    }
    }
  • Dependency free, only needs PHP to run

    Supports PHP 7+. Requires no third-party plugins or software.

  • Default caching layer

    SleekDB will serve data from cache by default and regenerate cache automatically! Query results will be cached and later reused from a single file instead of traversing all the available files.

  • Rich Conditions and Filters

    Use multiple conditional comparisons, text search, sorting on multiple properties and nested properties. Some useful methods are:

    • where
    • orWhere
    • select
    • except
    • in
    • notIn
    • join
    • like
    • sort
    • skip
    • orderBy
    • update
    • limit
    • search
    • distinct
    • exists
    • first
    • delete
  • Process data on demand

    SleekDB does not require any background process or network protocol in order to process data when you use it in a PHP project. All data for a query will be fetched at runtime within the same PHP process.

  • Runs everywhere

    Runs perfectly on shared-servers or VPS too.

  • Easy to learn and implement

    SleekDB provides a very simple elegant API to handle all of your data.

  • Easily import/export or backup data.

    SleekDB use files to store information. That makes tasks like backup, import and export very easy.

  • Actively maintained

    SleekDB is created by @rakibtg who is using it in various types of applications which are in production right now. Our other contributor and active maintainer is Timucin who is making SleekDB much better in terms of code quality and new features.

  • Support

    For support and other requirements contact.

Installation

To use SleekDB make sure that you have PHP up and running in your system, and SleekDB has write and/or read permission.

Requirements

  • PHP >= 7.0
  • ext-json

Composer Installation (recommended)

To install SleekDB using composer, open a terminal, cd into your project root directory where "composer.json" file lives and run this:

composer require rakibtg/sleekdb

SleekDB should be auto loaded into your app using the composer. Find SleekDB on packagist.

Install Manually (without composer)

  • Download the latest version and extract the ZIP file inside your project directory.
  • Import the SleekDB.php file where you want to use SleekDB.

    Example:

    require_once "../SleekDB/Store.php";

To download older versions please check the releases.

Using dev version (Not recommended)

We do a lot of experiments on the dev branch, which are not properly documented and the API will not be consistence in some cases. Use from dev branch if you have any specific reason or if you want play around with it.

composer require rakibtg/sleekdb dev-dev

Getting Started

Getting started with SleekDB is super easy. We keep data in a "store", which is similar to MySQL "table" or MongoDB "collection". Each store contains multiple documents, which are equivalent to rows in SQL based relational database.

As an example for a blog it can be something like this: posts > documents

Here "posts" is a store and "documents" are a collection of JSON files that contains all the blog posts.

📌 Important Note

With the release of version 2.0 we have updated the internal implementation.
We introduce a new API and the old SleekDB object has to retire!
Although, we are providing downwards compatibility in this version, but will remove the old SleekDB object with version 3.0.
This documentation focus on the new API.

Query Life Cycle

Each query will follow the below steps or execution life cycle.

  1. Store

    The Store class is the first, and in most cases also the only part you need to come in contact with.

  2. Query Builder

    The QueryBuilder class is used to prepare a query. To execute it getQuery method can be used.

  3. Query

    At this step the Query class contains all information needed to execute the query.

  4. Cache

    The Cache class handles everything regarding caching. It will decide when to cache or not to cache.

First Example - Insert and Fetch Data

  1. To begin with, we need a valid "path" where we want to store our data.
    Both absolute and relative paths are supported.

    $dataDir = __DIR__ . "/mydb";
  2. Once we have the data directory, we can initialize the store.
    If the store doesn't exist it will be created automatically.

    $newsStore = new \SleekDB\Store("news", $dataDir);

    Optionally you can pass a configuration array as a third parameter. Read more about configurations.

  3. Inserting your first news article using the insert method. The article has to be an array.

    $article = [
     "title" => "Google Pixel XL",
     "about" => "Google announced a new Pixel!",
     "author" => [
       "avatar" => "profile-12.jpg",
       "name" => "Foo Bar"
     ]
    ];
    $results = $newsStore->insert($article);

    The results variable will contain all the inserted data and with their _id property which will be unique and added automatically.

  4. To find all news articles we will use the findAll method.

    $allNews = $newsStore->findAll();
    
    print_r($allNews);

🎉 Congrats! You just inserted your first data into a SleekDB store and also learned how to fetch them.

Stores

Store is a simple directory where SleekDB will write all your data in JSON documents. "Store" is similar with the idea of "Table" in MySQL or "Collection" in MongoDB. You don't need to create a "store" directory manually.

Your first store

To start working with a store we need to create an object.

Later, we can use that object to work with the data in that store.

  • Creating a store

    use SleekDB\Store;
    $newsStore = new Store('news', $dataDir);
  • Creating an additional store; assuming we are working on a community platform, where need a users store too.

    $userStore = new Store('users', $dataDir);
  • Another store to keep all the posts shared by the user.

    $postStore = new Store('posts', $dataDir);
  • Creating a new user

    $userStore->insert([
      'name' => 'Mike Doe',
      'email' => 'miked@example.com',
      'avatar' => [
        'sm' => "/img-sm.jpg",
        'lg' => "/img-lg.jpg"
      ]
    ]);

In the above example we have created a new user to understand the purpose of a store in SleekDB. In this documentation later we will see more examples on this.

Deleting A Store

To delete a store use the deleteStore() method. It deletes a store and wipes all the data and cache it contains.

Example:

$userStore->deleteStore();

Configurations

SleekDB allows a few configuration options, which are

  • auto_cache
  • cache_lifetime
  • timeout

They are store wide, which means they will be used on every query, if not changed on a query by query base.

Using Custom Configuration

You can pass the configurations array as a third parameter when initializing the Store object:

$configuration = [
  "auto_cache" => true,
  "cache_lifetime" => null,
  "timeout" => 120
];
$newsStore = new \SleekDB\Store("news", $dataDir, $configuration);

Let's get familiar with the available configuration options.

auto_cache

The auto_cache is set to true by default!

This tells SleekDB to use the build in caching system.

To disable build in caching set auto_cache to false in the config array.

Note that you can manually manage caching on a query by query base with methods that SleekDB provides. Available caching method's are:

  • QueryBuilder->regenerateCache()
  • QueryBuilder->useCache()
  • QueryBuilder->disableCache()

cache_lifetime

The cache_lifetime is set to null by default!

Can be an int >= 0, that specifies the lifetime in seconds, or null to define that there is no lifetime.

This specifies the default cache time to live store wide.

If set to null the cache files made with that store instance will have no lifetime and will be regenerated on every insert/update/delete operation.

0 means infinite lifetime.

Note that you can specify the cache lifetime on a query by query base by using the useCache method of the QueryBuilder and pass a lifetime.

  • QueryBuilder->useCache($lifetime)

Note: You will find more details on caching at Cache Management

timeOut

Set timeout value. Default value is 120 seconds.

Insert Data

To insert data first you make a PHP array, and simply insert that array into a store.

Summary

  • insert
  • insertMany

Insert a single document

function insert(array $data): array

Parameters

  1. $data: array

    One document that will be insert into the Store

    • ["name" => "Josh", "age" => 23, "city" => "london"]

Return value

Returns the inserted document as an array including the automatically generated and unique _id property.

Example

// Prepare a PHP array to insert.
$user = [
    'name' => 'Kazi Hasan',
    'products' => [
        'totalSaved' => 19,
        'totalBought' => 27
    ],
    'location' => [
        'town' => 'Nagar',
        'city' => 'Dhaka',
        'country' => 'Bangladesh'
    ]
];
// Insert the data.
$user = $userStore->insert($user);


Insert multiple documents

function insertMany(array $data): array

Parameters

  1. $data: array

    Multiple documents that will be insert into the Store

    • [ ["name" => "Josh", "age" => 23], ["name" => "Mike", "age" => 19], ... ]

Return value

Returns the inserted documents in an array including their automatically generated and unique _id property.

Example

// Prepare users data.
$users = [
    [
        'name' => 'Russell Newman',
        'products' => [
            'totalSaved' => 5,
            'totalBought' => 3
        ],
        'location' => [
            'town' => 'Andreas Ave',
            'city' => 'Maasdriel',
            'country' => 'England'
        ]
    ],
    [
        'name' => 'Willard Bowman',
        'products' => [
            'totalSaved' => 0,
            'totalBought' => 0
        ],
    ],
    [
        'name' => 'Tommy Mendoza',
        'products' => [
            'totalSaved' => 172,
            'totalBought' => 54
        ],
    ],
    [
        'name' => 'Joshua Edwards',
        'phone' => '(382)-450-8197'
    ]
];
// Insert all data.
$users = $userStore->insertMany($users);

Fetch Data

To get data from the store SleekDB provides some simple yet powerful methods.

ℹ️ If you need to make more complex queries look into QueryBuilder.

Summary

  • findAll
  • findById
  • findBy
  • findOneBy

Get all documents

function findAll(): array

Return value

Returns either an array containing all documents of that store or an empty array if there are no documents.

Example

$allNews = $newsStore->findAll();

Example result

[["_id" => 12, "title" => "We love SleekDB"], ["_id" => 14, "title" => "NoSQL with just PHP"], ...]


Get a single document with its _id

With this method SleekDB doesn't traverse through all files. Instead, it accesses the file directly, what makes this method especially fast.

function findById(int $id): array|null

Parameters

  1. $id: int

    The _id of a document located in the store.

Return value

Returns one document or null if document could not be found.

Example

$news = $newsStore->findById(12);

Example result

["_id" => 12, "title" => "SleekDB is the Best", ...]


Get one or multiple documents

function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array|null

Parameters

  1. $criteria: array

    One or multiple where clauses.

    • [["city", "=", "london"], ["age", ">", 18]]
      WHERE city = "london" AND age > 18
  2. $orderBy: array

    Order in which the results will be sort.

    • ["name" => "asc"]
  3. $limit: int

    Limit the result to a specific amount.

  4. $offset: offset

    Skip a specific amount of documents.

Return value

Returns found documents in an array or null if nothing is found.

Example

$news = $newsStore->findBy(["author", "=", "John"], ["title" => "asc"], 10, 20);
// First 20 documents skipped, limited to 10 and ascending sort by title where author is John.

Example result

[ ["_id" => 12, "title" => "Best Database"], ["_id" => 4, "title" => "Why SleekDB"], ...]


Get one document.

function findOneBy(array $criteria): array|null

Parameters

  1. $criteria: array

    One or multiple where clauses. * [["city", "=", "london"], ["age", ">", 18]]
    WHERE city = "london" AND age > 18

Return value

Returns one document or null if nothing is found.

Examples

$news = $newsStore->findOneBy(["author", "=", "Mike"]);
// Returns one news article of the author called Mike

Example result

["_id" => 18, "title" => "SleekDB is super fast", "author" => "Mike"]

Edit Data

To edit a data object you can use the update() method of the Store class.

ℹ️ If you need to make more complex updates look into QueryBuilder and Query.

This method is especially fast because it uses the _id of the given document to update it directly instead of traversing through all documents.

function update(array $updatable): bool;

Note: This method of the Store class updates/overrides entire document/s, not just parts.

Parameters

  1. $updatable: array

    One or multiple documents

    • ["_id" => 12, "title" => "SleekDB rocks!", ...]
    • [ ["_id" => 12, "title" => "SleekDB rocks!", ...], ["_id" => 13, "title" => "Multiple Updates", ...], ... ]

Return value

Returns true on success or false if document with given _id does not exist.

Update one user that we got beforehand

$user = [
    'name' => 'Willard Bowman',
    'products' => [
        'totalSaved' => 0,
        'totalBought' => 0
    ],
];

//store the user
$store->insert($user); // has _id = 1

// retrieve a user
$user = $userStore->findById(1);

// update user
$user["name"] = "Luke Bowman";

$userStore->update( $user ); // updates the user by using his _id

Update multiple users

// retrieve users
$users = $userStore->findBy(["name", "=", "Josh"]);

foreach($users as $key => $user){
    // change the properties of the users
    $user["name"] = "Luke Bowman";

    // push changed user back to the users array
    $users[$key] = $user;
}

// update all users that had the name Josh
$userStore->update( $users );

Delete Data

To delete documents you can use the deleteBy() and deleteById() methods of the Store class.

ℹ️ If you need to make a more complex delete look into QueryBuilder and Query.

Summary

  • deleteBy
  • deleteById

Delete one or multiple documents

function deleteBy(array $criteria, int $returnOption = Query::DELETE_RETURN_BOOL): array|bool|int

Parameters

  1. $criteria: array

    One or multiple where conditions

    • [["name", "=", "John"], ["age", ">", 18]]
      WHERE name = "John" AND age > 18
  2. $returnOption: int

    Different return options provided with constants of the Query class

    • Query::DELETE_RETURN_BOOL (Default)
      Return true or false
    • Query::DELETE_RETURN_RESULTS
      Retrieve deleted files as an array
    • Query::DELETE_RETURN_COUNT
      Returns the amount of deleted documents

Return value

This method returns based on the given return option either boolean, int or array.

Examples

Lets delete all user whose name is "Joshua Edwards"

$userStore->deleteBy(['name', '=', 'Joshua Edwards']);
// Returns true

Lets delete all user whose name is "Joshua Edwards" and retrieve deleted documents.

use SleekDB/Query;
$userStore->deleteBy(['name', '=', 'Joshua Edwards'], Query::DELETE_RETURN_RESULTS);

Example result

[ ["_id" => 12, "name" => "Joshua Edwards"], ["_id" => 14, "name" => "Joshua Edwards"], ... ]


Delete one document with its _id

This method is especially fast because SleekDB uses the _id to directly delete the document and does not traverse through all files.

function deleteById(int $id): bool

Parameters

  1. $id: int

    The _id of a document located in the store.

Return value

Returns true if document does not exist or deletion was successful or false on failure.

Example

$userStore->deleteById(12);
// Returns true

QueryBuilder

The QueryBuilder is used to prepare more complex queries, not to execute the query!. You can create a QueryBuilder with the createQueryBuilder() method of the Store class.

$userQueryBuilder = $userStore->createQueryBuilder();

Summary

  • getQuery
  • where
  • orWhere
  • in
  • notIn
  • select
  • except
  • distinct
  • skip
  • limit
  • orderBy
  • search
  • Cache management
  • join

getQuery()

With the getQuery method of the QueryBuilder class you can retrieve the Query object to execute the query.

function getQuery(): Query

The most important methods of the Query class to execute a query are:

  • fetch()

    Retrieve multiple documents

  • first()

    Retrieve first found document

  • exists()

    Check if a document for given query exist

  • delete()

    Delete all documents that are found with given query

  • update()

    Update all documents that are found with given query

For more details on query execution please visit the Query page.

where()

To filter data we use the where() method of the QueryBuilder object.
If you provide multiple conditions they are connected with an AND.

function where(array $criteria): QueryBuilder

Parameters

  1. $criteria

    One or multiple where conditions

  • [$fieldName, $condition, $value]
  • [ [$fieldName, $condition, $value], [$fieldName, $condition, $value], ... ]

    • $fieldName: string

    The field name argument is the property that we want to check in our data object.

    As our data object is basically a JSON document so it could have nested properties.

    To target nested properties we use a single dot between the property/field name.

    Example: From our above users object if we want to target the "country" property of a user, then we would pass location.country in this argument, because "location" is the parent property of the "country" property in our data object.

    • $condition: string

    To apply the comparison filters we use this argument.

    Allowed conditional operators are:

    • = Match equal against data.

    • != Match not equal against data.

    • > Match greater than against data.

    • >= Match greater equal against data.

    • < Match less than against data.

    • <= Match less equal against data.

    • like Match using wildcards. \ Supported wildcards:

      • % Represents zero or more characters \ Example: bl% finds bl, black, blue, and blob
      • _ Represents a single character \ Example: h_t finds hot, hat, and hit
      • [] Represents any single character within the brackets \ Example: h[oa]t finds hot and hat, but not hit
      • ^ Represents any character not in the brackets \ Example: h[^oa]t finds hit, but not hot and hat
      • - Represents a range of characters \ Example: c[a-b]t finds cat and cbt
    • $value

      Data that will be checked against the property value of the JSON documents.

Examples

To only get the user whose country is equal to "England" we would query like this:

// inline
$users = $userStore
  ->createQueryBuilder()
  ->where( [ "name", "=", "Joshua Edwards" ] )
  ->getQuery()
  ->fetch();

// creating the QueryBuilder
$userQueryBuilder = $userStore->createQueryBuilder();

// preparing the query with the QueryBuilder
$userQueryBuilder->where( [ "name", "=", "Joshua Edwards" ] );

// executing the query
$users = $userQueryBuilder->getQuery()->fetch();

You can also use multiple where conditions.

Retrieve all users that have products.totalSaved > 10 AND products.totalBought > 20.

// inline & using where method multiple times
$users = $userQueryBuilder
  ->where( ["products.totalSaved", ">", 10] )
  ->where( ["products.totalBought", ">", 20] )
  ->getQuery()
  ->fetch();

// inline & using where method once
$users = $userQueryBuilder
  ->where(
    [
      ["products.totalSaved", ">", 10],
      ["products.totalBought", ">", 20]
    ]
  )
  ->getQuery()
  ->fetch();

// retrieve QueryBuilder
$userQueryBuilder = $userStore->createQueryBuilder()

// prepare query
$userQueryBuilder->where(
  [
    ["products.totalSaved", ">", 10],
    ["products.totalBought", ">", 20]
  ]
);

// execute query
$users = $userQueryBuilder->getQuery()->fetch();

orWhere()

orWhere(...) works as the OR condition of SQL. SleekDB supports multiple orWhere as object chain.
If you provide multiple conditions they are connected with an AND.

function orWhere(array $criteria): QueryBuilder

Properties

  1. $criteria

    One or multiple where conditions

  • [$fieldName, $condition, $value]
  • [ [$fieldName, $condition, $value], [$fieldName, $condition, $value], ... ]

    • $fieldName: string

    The field name argument is the property that we want to check in our data object.

    As our data object is basically a JSON document so it could have nested properties.

    To target nested properties we use a single dot between the property/field name.

    Example: From our above users object if we want to target the "country" property of a user, then we would pass location.country in this argument, because "location" is the parent property of the "country" property in our data object.

    • $condition: string

    To apply the comparison filters we use this argument.

    Allowed conditional operators are:

    • = Match equal against data.

    • != Match not equal against data.

    • > Match greater than against data.

    • >= Match greater equal against data.

    • < Match less than against data.

    • <= Match less equal against data.

    • like Match using wildcards. \ Supported wildcards:

      • % Represents zero or more characters \ Example: bl% finds bl, black, blue, and blob
      • _ Represents a single character \ Example: h_t finds hot, hat, and hit
      • [] Represents any single character within the brackets \ Example: h[oa]t finds hot and hat, but not hit
      • ^ Represents any character not in the brackets \ Example: h[^oa]t finds hit, but not hot and hat
      • - Represents a range of characters \ Example: c[a-b]t finds cat and cbt
    • $value

      Data that will be checked against the property value of the JSON documents.

Examples

Retrieve all users that have (products.totalSaved > 10 AND products.totalBought > 20) OR products.shipped = 1

$users = $userQueryBuilder
  ->where(
    [
      ["products.totalSaved", ">", 10],
      ["products.totalBought", ">", 20]
    ]
  )
  ->orWhere( ["products.shipped", "=", 1] )
  ->getQuery()
  ->fetch();

Retrieve all users that have products.totalSaved > 10 OR (products.totalBought > 20 AND products.shipped = 1) OR totalBought = 0

$users = $userQueryBuilder
  ->where( ["products.totalSaved", ">", 10] )
  ->orWhere(
    [
      [ "products.totalBought", ">", 20 ],
      [ "products.shipped", "=", 1 ]
    ]
  )
  ->orWhere( ["products.totalBought", "=", 0] )
  ->getQuery()
  ->fetch();

in()

in(...) works as the IN clause of SQL. SleekDB supports multiple IN as object chain for different fields.

in(string $fieldName, array $values = []): QueryBuilder

Parameters

  1. $fieldName: string

    The field name argument is the property that we want to check in our data object.

    You can also provide have nested properties separated with a dot ".".

  2. $values: array

    This argument takes an array to match documents within its items.

Examples

Retrieve all users that are from the country BD, CA, SE or NA.

$users = $userQueryBuilder
  ->in("country", ["BD", "CA", "SE", "NA"])
  ->getQuery()
  ->fetch();

Retrieve all users that are from country BD, CA, SE, NA and are at the age of 18, 20, 23 or 30.

$users = $userQueryBuilder
  ->in("country", ["BD", "CA", "SE", "NA"])
  ->in("age", [18, 20, 23, 30])
  ->getQuery()
  ->fetch();

notIn()

notIn(...) works as the opposite of in() method.

It will filter out all documents that has particular data items from the given array. SleekDB supports multiple notIn as object chain for different fields.

The notIn() method takes two arguments as in() method, those are:

function notIn(string $fieldName, array $values = []): QueryBuilder

Parameters

  1. $fieldName: string

    The field name argument is the property that we want to check in our data object.

    You can also provide have nested properties separated with a dot ".".

  2. $values: array

    This argument takes an array to match documents within its items.

Examples

Retrieve all users that are not from the coutry IN, KE or OP.

$users = $userQueryBuilder
  ->notIn("country", ["IN", "KE", "OP"])
  ->getQuery()
  ->fetch();

Retrieve all users that are not from the country IN, KE or OP and do not have products.totalSaved 100, 150 or 200.

$users = $userQueryBuilder
  ->notIn("country", ["IN", "KE", "OP"])
  ->notIn("products.totalSaved", [100, 150, 200])
  ->getQuery()
  ->fetch();

select()

With select(...) you can specify specific fields to output, like after the SELECT keyword in SQL. SleekDB supports multiple select() as object chain for multiple fields.

function select(array $fieldNames): QueryBuilder

Parameters

  1. $fieldNames: array

    Specify specific fields to output.

Examples

Retrieve just the name of all users.

$users = $userQueryBuilder
  ->select(['name'])
  ->getQuery()
  ->fetch();
// output: [["_id" => 1, "name" => "Max"], ["_id" => 2, "name" => "Hasan"]]

except()

except(...) works as the opposite of select() method. Use it when you don't want a property in the result. For example, if you don't want the password field in your returned data set.

function except(array $fieldNames): QueryBuilder
  1. $fieldNames: array

    Specify specific fields to exclude from the output.

Examples

Retrieve all information of an user except its _id and name.

$users = $userQueryBuilder
  ->except(["_id", "name"])
  ->getQuery()
  ->fetch();
// output: [["age": 28], ["age": 18]]

distinct()

The distinct method is used to retrieve unique values from the store. It will remove all the duplicate documents while fetching data from a store.

distinct( array|string $fields ): QueryBuilder;

Parameters

  1. $fields: array|string

    Specify one or multiple fields you want to be distinct.

Examples

Retrieve all users, but just the first user if there is another one with the same name.

// providing a string
$distinctUsers = $userQueryBuilder
  ->distinct("name")
  ->getQuery()
  ->fetch();

// providing an array
$distinctUsers = $userQueryBuilder
  ->distinct(["name"])
  ->getQuery()
  ->fetch();

skip()

Skip works as the OFFSET clause of SQL. You can use this to skip a specific amount of documents.

function skip(int $skip = 0): QueryBuilder

Parameters

  1. $skip: int

    The value how many documents should be skipped.

Examples

Retrieve all users except the first 10 found.

$users = $userQueryBuilder
  ->skip(10)
  ->getQuery()
  ->fetch();

limit()

Works as the LIMIT clause of SQL. You can use this to limit the results to a specific amount.

function limit($limit = 0): QueryBuilder

Parameters

  1. $limit: int Limit the amount of values in the result set. Has to be greater than 0.

Examples

Retrieve just the first ten users.

$users = $userQueryBuilder
  ->limit(10)
  ->getQuery()
  ->fetch();

orderBy()

Works as the ORDER BY clause of SQL. With this method you can sort the result. At the moment the result can just be sorted by one field.

function orderBy( array $criteria): QueryBuilder

Parameters

  1. $criteria: array

  • [$fieldName => $order]
    • $fieldName: string

      Name of the field that will be used to sort the result.

    • $order: string

      Either desc for a descending sort or asc for a ascending sort.

Examples

Retrieve all users sorted by their name.

$users = $userQueryBuilder
  ->orderBy(["name" => "asc"])
  ->getQuery()
  ->fetch();

Result

[["_id" => 13, "name" => "Anton"], ["_id" => 2, "name" => "Berta"], ...]

search()

Do a fulltext like search against one or multiple fields.

function search(string|array $fields, string $keyword): QueryBuilder

Parameters

  1. $fields: array|string

    One or multiple fields that will be searched.

  2. $keyword: string Value that will be searched by.

Examples

Find all articles that include the word "SleekDB" in their description.

$articles = $articleQueryBuilder
  ->search("content", "SleekDB")
  ->getQuery()
  ->fetch();

Cache management

The QueryBuilder provides the useCache, disableCache and regenerateCache methods to manage caching on a query by query base.

Please visit the Cache Management page for more details.

join()

This method is used to join two or multiple stores together.

For more details please visit the Join Stores page.

function join(callable $joinedStore, string $dataPropertyName): QueryBuilder

Cache Management

The QueryBuilder class does provide a couple of methods to manage caching on a query by query bases.

By default caching is enabled store wide and does not need to be enabled on a query by query bases. For more information please look into the store configurations.

Create a QueryBuilder:

$userQueryBuilder = $userStore->createQueryBuilder();

Summary

  • useCache
  • disableCache
  • regenerateCache

useCache()

With the useCache method you can activate caching and define the cache lifetime on a query by query bases.

function useCache(int $lifetime = null): QueryBuilder

Parameters

  1. $lifetime: int|null

    The lifetime of the cache.

  • null (Default)
    Cache will not have a lifetime and will be regenerated on every update, delete and insert.
  • int
    Cache lifetime in seconds. 0 means infinite lifetime.

Examples

Retrieve and cache 20 users that are active.

The results in this example will use caching untill any documents gets updated/ deleted or inserted.

// fetch and insert in cache
$users = $userQueryBuilder
    ->where( ['active', '=', 1] )
    ->limit( 20 )
    ->useCache() // Use caching with lifetime = null
    ->getQuery()
    ->fetch();

// retrieve from cache
$users = $userQueryBuilder
    ->where( ['active', '=', 1] )
    ->limit( 20 )
    ->useCache()
    ->getQuery()
    ->fetch();

// insert a new user (cache with no lifetime will be deleted)
$newUser = ["name" => "Max", "active" => 0];
$userStore->insert($newUser);

// fetch and insert in cache
$users = $userQueryBuilder
    ->where( ['active', '=', 1] )
    ->limit( 20 )
    ->useCache()
    ->getQuery()
    ->fetch();

Retrieve and cache result for 2 minutes, 20 users that are active.

// fetch and insert in cache
$users = $userQueryBuilder
    ->where( ['active', '=', 1] )
    ->limit( 20 )
    ->useCache(120) // 2 minutes = 120 seconds
    ->getQuery()
    ->fetch();

Retrieve and cache result forever, 20 users that are active.

// fetch and insert in cache
$users = $userQueryBuilder
    ->where( ['active', '=', 1] )
    ->limit( 20 )
    ->useCache(0) // 0 means infinite caching
    ->getQuery()
    ->fetch();

disableCache()

Disable the build in caching solution on a query by query bases.
By default caching is enabled store wide. If you want to disable caching store instead of disabling it on a query by query bases visit the store configurations page.

function disableCache(): QueryBuilder

Example

Retrieve 20 users that are active and do not use caching.

// fetch and insert in cache
$users = $userQueryBuilder
    ->where( ['active', '=', 1] )
    ->limit( 20 )
    ->disableCache()
    ->getQuery()
    ->fetch();

regenerateCache()

Regenerate the cache of a query regardless of its lifetime.

function regenerateCache(): QueryBuilder

Examples

// cache with infinite lifetime
$users = $userQueryBuilder
    ->where( ['active', '=', 1] )
    ->limit( 20 )
    ->useCache(0)
    ->getQuery()
    ->fetch();

// returns documents not from cache and caches results for 20 seconds
$users = $userQueryBuilder
    ->where( ['active', '=', 1] )
    ->limit( 20 )
    ->useCache(20)
    ->regenerateCache()
    ->getQuery()
    ->fetch();

Join Stores

With SleekDB it is easy to join multiple stores. You can add more than one join as well as nested join methods are also supported!

Quick Example

Query into the "users" store to fetch all users. Each user also will get an additional property called "comments", that contains all comments of the user.

$usersWithComments = $userStore
  ->createQueryBuilder()
  ->join(function($user) use ($commentStore) {
    return $commentStore->findBy(["user", "=", $user["_id"]]);
  }, "comments")
  ->getQuery()
  ->fetch();

Result

[
  [
    "_id" => 1, 
    "name" => "John", 
    "comments" => [
      [
        "_id" => 1,
        "articleId" => 3
        "content" => "I love SleekDB"
      ],
      ...
    ]
  ],
  ...
]

join()

To join stores we use the join() method of the QueryBuilder object.

The join() method takes two arguments, those are:

function join(callable $joinFunction, string $dataPropertyName): QueryBuilder

Parameters

  1. $joinFunction: callable

    This function has to return the result of an executed sub query or prepares a sub query for the join and returns it as a QueryBuilder object.

  2. $dataPropertyName: string

    Name of the new property added to each document.

Examples

To get the users with their comments we would join like this:

use SleekDB\Store;

$userStore = new Store("users", $dataDir);
$commentStore = new Store("comments", $dataDir);

$users = $usersStore
  ->createQueryBuilder()
  ->join(function($user) use ($commentStore){
    // returns result
    return $commentStore->findBy([ "userId", "=", $user["_id"] ]);
  }, "comments")
  ->getQuery()
  ->fetch();

// or
$users = $userStore
  ->createQueryBuilder()
  ->join(function($user) use ($commentStore){
    // returns Querybuilder
    return $commentStore
      ->createQueryBuilder()
      ->where([ "userId", "=", $user["_id"] ]);
  }, "comments")
  ->getQuery()
  ->fetch();

Use multiple join().
Retrieve all users with their comments and their articles.

use SleekDB\Store;

$userStore = new Store("users", $dataDir);
$commentStore = new Store("comments", $dataDir);
$articleStore = new Store("articles", $dataDir);

$users = $userStore
  ->createQueryBuilder()
  ->join(function($user) use ($commentStore) {
    // returns result
    return $commentStore->findBy([ "userId", "=", $user["_id"] ]);
  }, "comments")
  ->join(function($user) use ($articleStore) {
    // returns result
    return $articleStore->findBy([ "author", "=", $user["_id"] ]);
  }, "articles")
  ->getQuery()
  ->fetch();

Use join() within a join sub query.
Retrieve all users with their created articles containing the comments.

use SleekDB\Store;

$userStore = new Store("users", $dataDir);
$commentStore = new Store("comments", $dataDir);
$articleStore = new Store("articles", $dataDir);

$users = $userStore
  ->createQueryBuilder()
  ->join(function($user) use ($articleStore, $commentStore){
    // returns QueryBuilder
    return $articleStore
      ->createQueryBuilder()
      ->where([ "author", "=", $user["_id"] ])
      ->join(function($article) use ($commentStore){
        // returns result
        return $commentStore->findBy("articleId", "=", $article["_id"]);
      }, "comments");

  }, "articles")
  ->getQuery()
  ->fetch();

Query

With the Query object you can execute a query.

To get the Query object use the getQuery method of the QueryBuilder class:

$userQuery = $userQueryBuilder->getQuery();

Summary

  • fetch
  • first
  • exists
  • update
  • delete

fetch()

Execute a query and retrieve an array containing all documents found.

function fetch(): array

Return value

An array containing all documents found or an empty array.

Example

Retrieve all users that are located in london.

$user = $userQueryBuilder
  ->where([ "city", "=", "london" ])
  ->getQuery()
  ->fetch();

Result

[
    [
        "_id" => 1,
        "name" => "John",
        "city" => "london"
    ],
    [
        "_id" => 4,
        "name" => "Max",
        "city" => "london"
    ],
    ...
]

first()

It is more efficient than fetch but one caveat is that the orderBy will not work when using this method to get the very first item.

function first(): array

Return value

Returns the very first document discovered or an empty array if nothing found.

Examples

$user = $userQueryBuilder
  ->where([ "email", "=", "foo@bar.com" ])
  ->getQuery()
  ->first();

Result

[
    "_id" => 1,
    "name" => "John",
    "email" => "foo@bar.com"
]

exists()

It is more efficient than using fetch to check if some data exists or not. For example, you may use exists method to check if a username or email address already exists or not.

function exists(): bool

Return value

Returns true if a document exists and false if no document found.

Example

$userNameExists = $userQueryBuilder
  ->where([ "username", "=", "foobar" ])
  ->getQuery()
  ->exists();

update()

Update one or multiple documents based on a given query.

function update(array $updatable): bool

Parameters

  1. $updatable: array

    An array containing the properties to update.

Return value

Returns true on success and false if no documents found to update.

Example

Set the status of all users that are located in london to VIP.

$userStore
  ->createQueryBuilder()
  ->where([ "city", "=", "london" ])
  ->getQuery()
  ->update([ "status" => "VIP" ]);

delete()

Delete one or multiple documents based on a given query.

function delete(int $returnOption = Query::DELETE_RETURN_BOOL)

Parameters

  1. $returnOption: int

    Different return options provided with constants of the Query class

    • Query::DELETE_RETURN_BOOL (Default)
      Return true or false
    • Query::DELETE_RETURN_RESULTS
      Retrieve deleted files as an array
    • Query::DELETE_RETURN_COUNT
      Returns the amount of deleted documents

Return value

This method returns based on the given return option either boolean, int or array.

Example

Delete all users that are not active.

$result = $userStore
  ->createQueryBuilder()
  ->where([ "active", "=", false ])
  ->getQuery()
  ->delete();
// output: true

Delete all users that are not active and retrieve the amount of deleted users.

use SleekDB\Query;

$result = $userStore
  ->createQueryBuilder()
  ->where([ "active", "=", false ])
  ->getQuery()
  ->delete(Query::DELETE_RETURN_COUNT);
// output: 14

Delete all users that are not active and retrieve the deleted users.

use SleekDB\Query;

$result = $userStore
  ->createQueryBuilder()
  ->where([ "active", "=", false ])
  ->getQuery()
  ->delete(Query::DELETE_RETURN_RESULT);
// output: [ ["_id" => 1, "name" => "Max"], ["_id" => 4, "name" => "John"], ... ]

Advanced

Keeping QueryBuilder object

When you use the createQueryBuilder() method you get a new QueryBuilder object, that means your conditions are reset to default.

You can keep the QueryBuilder object and reuse it to add new conditions.

Example

$userQueryBuilder = $userStore->createQueryBuilder();

$userQueryBuilder->where([ 'products.totalBought', '>', 0 ]);

$userQueryBuilder->where([ 'products.totalSaved', '>', 0 ]);

// fetch all users that have totalBought > 0 and totalSaved > 0
$users = $userQueryBuilder->getQuery()->fetch();

// add new condition
$userQueryBuilder->where([ 'active', '=', true ]);

// fetch all users that have totalBought > 0, totalSaved > 0 and are active
$usersActive = $userQueryBuilder->getQuery()->fetch();

Keeping Query object

If you want to keep the conditions and perform additional operations then you may want to keep the Query object.

Here is an example showing how you may fetch data and then update on the discovered documents without running an additional query:

Example

$userQuery = $userStore->createQueryBuilder()
  ->where('products.totalBought', '>', 0)
  ->where('products.totalSaved', '>', 0)
  ->getQuery();

// Fetch data.
$users = $userQuery->fetch();

// Update matched documents.
$userQuery->update([
  'someRandomData' => '123',
]);

// Fetch data again.
$usersUpdated = $userQuery->fetch();

Cache

With the Cache class you can control Caching in a deeper way.

That said, this class is mainly for internal use and normally that kind of deep cache control is not needed.

⚠️️ Be careful when using the Cache object.

To retrieve the Cache object use the getCache method of the Query class.

$cache = $userQuery->getCache();

Summary

  • getToken
  • delete
  • deleteAll
  • deleteAllWithNoLifetime
  • set
  • get
  • getCachePath
  • setLifetime
  • getLifetime

getToken()

Returns the unique token for the current query, that will be used to save and retrieve a cache file.

function getToken(): string

Return value

The unique token for the current query as a string.

delete()

Deletes the cache file for the current query.

function delete()

deleteAll()

Delete all cache files of current store.

function deleteAll()

deleteAllWithNoLifetime()

Delete all cache files that have no lifetime (null) of current store.

function deleteAllWithNoLifetime()

set()

Set and cache the content for the current query / token.

function set(array $content)

Parameters

  1. $content: array

    The content that will be cached.

get()

Retrieve the content of the cache file for the current query / token.

function get(): array|null

Return value

The content as an array or null if no cache file found.

getCachePath()

Returns the path to the cache directory.

function getCachePath(): string

setLifetime()

Set the lifetime for the current query / token.

function setLifetime(int|null $lifetime): Cache

Parameters

  1. $lifetime: int|null

    • int in seconds. 0 means infinite
    • null no cache lifetime
    • Cache gets deleted on every update / delete / insert.

getLifetime()

Get the lifetime for the current query / token.

function getLifetime(): int|null

Return value

Either int >= 0 in seconds, where 0 means infinite, or null.

Reference

This page contains a brief overview of all the classes and their methods. For more detailed information please visit the other documentation pages.

Store

The Store class is the beginning point and handles everything regarding store configuration. It also provides all methods needed for simple queries.

Create a new Store object. (Internally it creates a new store folder if it doesn't exist)

function __construct(string $storeName, string $dataDir, array $configuration = [])

Returns a new QueryBuilder object.

function createQueryBuilder(): QueryBuilder

Create/Insert a new document in the store.
Returns the inserted document with it's new and unique _id.

function insert(array $data): array

Create/Insert many documents in the store.
Returns the inserted documents with their new and unique _id.

function insertMany(array $data): array

Retrieve all documents of that store.

function findAll(): array

Retrieve one document by its _id. Very fast because it finds the document by its file path.

function findById(int $id): array|null

Retrieve one or multiple documents.

function findBy(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array

Retrieve one document.

function findOneBy(array $criteria): array|null

Update one or multiple documents.

function update(array $updatable): bool

Delete one or multiple documents.

function deleteBy(array $criteria, int $returnOption = Query::DELETE_RETURN_BOOL): bool|array|null

Delete one document by its _id. Very fast because it deletes the document by its file path.

function deleteById(int $id): bool

Deletes a store and wipes all the data and cache it contains.

function deleteStore(): bool

Get the name of the store.

function getStoreName(): string

Get the location (directory path) of the store.

function getDataDirectory(): string

Returns if caching is enabled store wide.

function _getUseCache(): bool

Returns the store wide default cache lifetime.

function getDefaultCacheLifetime(): null|int

Return the last created store object ID.

function getLastInsertedId(): int

Get the path to the store, including store name.

function getStorePath(): string

QueryBuilder

The QueryBuilder class handles everything regarding query creation like for example the where or join methods.

Create a new QueryBuilder object.

function __construct(Store $store)

Returns a new Query object which can be used to execute the query build.

function getQuery(): Query

Select specific fields.

function select(string[] $fieldNames): QueryBuilder

Exclude specific fields.

function except(string[] $fieldNames): QueryBuilder

Add "where" condition to filter data. Can be used multiple times. All additional uses add an "and where" condition.

function where(array $conditions): QueryBuilder

Add or-where conditions to filter data.

function orWhere($conditions): QueryBuilder

Add "in" condition to filter data.

function in(string $fieldName, array $values = []): QueryBuilder

Add "not in" condition to filter data.

function notIn(string $fieldName, array $values = []): QueryBuilder

Set the amount of data record to skip.

function skip(int $skip = 0): QueryBuilder

Set the amount of data record to limit.

function limit(int $limit = 0): QueryBuilder

Set the sort order.

function orderBy(array $criteria): QueryBuilder

Do a fulltext like search against one or more fields.

function search(string|array $field, string $keyword): QueryBuilder

Join current store with another one. Can be used multiple times to join multiple stores.

function join(callable $joinFunction, string $dataPropertyName): QueryBuilder

Return distinct values.

function distinct(string|array $fields = []): QueryBuilder

Use caching for current query

function useCache(int $lifetime = null): QueryBuilder

Disable caching for current query.

function disableCache(): QueryBuilder

This method is used internally. It returns an array containing all properties that are used for the cache token generation.

function _getCacheTokenArray(): array

This method is used internally. It returns an array that contains all information needed to execute an query.

function _getConditionProperties(): array

Re-generate the cache for the query.

function regenerateCache(): QueryBuilder

Get the store object used to create the query builder.

function _getStore(): Store

Query

This class handles everything regarding query execution like fetch / first / exists.

Create a new Query object. (Internally it creates a new Cache object)

function __construct(QueryBuilder $queryBuilder)

Get the Cache object.

function getCache(): Cache

Execute Query and get Results.

function fetch(): array

Check if data is found.

function exists(): bool

Return the first document. (More efficient than fetch but orderBy does not work)

function first(): array

Update one or multiple documents, based on the current query.

function update(array $updatable): bool

Deletes matched documents.

function delete(int $returnOption = Query::DELETE_RETURN_BOOL): bool|array|int

Cache

This class handles everything regarding caching like for example cache deletion.

Create a new Cache object.

function __construct(Query $query, string $storePath)

Set the cache lifetime.

function setLifetime(int|null $lifetime): Cache

Get the cache lifetime.

function getLifetime(): null|int

Get the path to the cache folder.

function getCachePath(): string

Get the cache token.

function getToken(): string

Delete all cache files for current store.

function deleteAll()

Delete all cache files with no lifetime (null) in current store.

function deleteAllWithNoLifetime()

Cache content for current query

function set(array $content)

Returns cached result for current query if found, else null.

function get(): array|null

Delete cache file/s for current query.

function delete()

🎉 Release Notes

📢 Optimizations, new query methods and more control

SleekDB 2.0 comes with so many important optimizations and other features that make it faster and more mature. This is the recommended SleekDB release version for new projects, and if you are using an older version consider upgrading as soon as possible.

Changes from 1.5 to 2.0

  • 🔎 Improving document discovery process

    Added better file locking support with reduced nested loops for finding documents. Added methods that can be used to easily find a document without searching for entire available JSON files of a store.

  • Support for PHP >= 7.0

    The support of PHP 5 is dropped to provide more modern features.

  • ✨ New query methods

    • first()
    • exists()
    • select()
    • except()
    • distinct()
    • join()
    • findAll()
    • findById()
    • findBy()
    • findOneBy()
    • updateBy()
    • deleteBy()
    • deleteById()
  • Improved Code Quality

    We isolated most of the existing logics into different classes to make the codebase easy to understand and easy to maintain.

  • Unit Testing

    There was some concern among developers as it was lacking UNIT Testing, now its added!

  • SleekDB class now deprecated

    We beliefe that downwards compatibility is very important.

    That's why we always try our best to keep SleekDB as downwards compatible as possible and avoid breaking changes.

    Unfortunatelly we had to refactor and rewrite the whole SleekDB project to make it future proof and keep it maintainable. As a consequence the SleekDB class is now deprecated with version 2.0 and will be removed with version 3.0.

    A new era for SleekDB begins!

  • Better Caching Solution

    Data caching has been improved significantly.

    • Add custom expiry of a cache file with lifetime.
    • Ability of query specific caching rules.

Issues

  • Deprecate SleekDB Class (#84)
  • first and exists methods does not use cache (#82)
  • Make where and orWhere just accept one array (#80)
  • Make dataDir required (#79)
  • Add update & delete to Store class #78)
  • Change delete method of Query class to accept return options (#77)
  • Add find methods to new Store class to make the QueryBuilder optional for simple queries. (#75)
  • Allow to query on read-only file system (#67)
  • Use "results" property instead of returning data from methods (#59)
  • Return the first item only (#56)
  • Check if data exists (#54)
  • update return value (#51)
  • delete status (#48)
  • Extend not possible due to private helper methods (#44)
  • JOIN feature (#42)
  • Return distincted value (#41)
  • Suppress Key on fetch (#38)
  • Like Condition (#34)
  • Better code base (#32)
  • OR Condition Query (#31)
  • Possibility of duplicate ID’s (#30)

Contributing

  • Fork SleekDB
  • Create your feature branch git checkout -b feature/my-new-feature
  • Commit your changes git commit -am 'Added some feature'
  • Push to the branch git push origin feature/my-new-feature
  • Create new Pull Request targeting dev branch

Please write details about your PR.

Contact

We would love to see how you are using the database, if you have implemented something or how it is working for you.

What changes will make you more interested.

If you want to submit a bug feel free to create a new issue or email me @ rakibtg [-at-] gmail [-dot-] com

About

Let us know with a tweet if you love or hate SleekDB 😄, or if you have any question mail me or tweet to my twitter handle.

Versions