diff --git a/.travis.yml b/.travis.yml index 9114ce2..102886d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - 5.6 - 7 - 7.1 + - 7.2 sudo: required dist: precise diff --git a/CHANGELOG.md b/CHANGELOG.md index a71e0a9..856a4e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ Change Log ========== +### 02/24/2019 - 1.0.24 +* Merged [Pull Request](https://github.com/filebase/Filebase/pull/50) Fixed [bug](https://github.com/filebase/Filebase/issues/41) returning unexpected results. + +### 02/24/2019 - 1.0.23 +* Merged [Pull Request](https://github.com/filebase/Filebase/pull/49) Added support for order by multiple columns +* Merged [Pull Request](https://github.com/filebase/Filebase/pull/46) Added ability to query document ids (internal id) +* Added ability to use query `delete()` on all items that match the query (making the custom filter optional) + +### 02/23/2019 - 1.0.22 +* Merged [Pull Request](https://github.com/filebase/Filebase/pull/47) for deleting items with a custom filter. (this adds the `delete()` method on queries.) +* Merged [Pull Request](https://github.com/filebase/Filebase/pull/48) for calling to the Query methods directly on the database class. +* Merged [Pull Request](https://github.com/filebase/Filebase/pull/45) for sorting by update/created at times (ability to fetch `__created_at` and `__updated_at`) + +### 12/26/2018 - 1.0.21 +* Merged [Pull Request](https://github.com/filebase/Filebase/pull/30) for YAML format. + ### 08/16/2018 - 1.0.20 * Fixed #23 – Caching is cleared when deleting/saving documents to prevent cache from being out of sync with document data. diff --git a/README.md b/README.md index cffa27e..78050ca 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Filebase -[![Build Status](https://travis-ci.org/filebase/Filebase.svg?branch=1.0)](https://travis-ci.org/filebase/Filebase) [![Coverage Status](https://coveralls.io/repos/github/filebase/Filebase/badge.svg?branch=1.0)](https://coveralls.io/github/filebase/Filebase?branch=1.0) +[![Build Status](https://travis-ci.org/tmarois/Filebase.svg?branch=1.0)](https://travis-ci.org/tmarois/Filebase) [![Coverage Status](https://coveralls.io/repos/github/tmarois/Filebase/badge.svg?branch=1.0)](https://coveralls.io/github/tmarois/Filebase?branch=1.0) -A Simple but Powerful Flat File Database Storage. No need for MySQL or an expensive SQL server, in fact, you just need your current site or application setup. All database entries are stored in files ([formatted](https://github.com/filebase/Filebase#2-formatting) the way you like). +[Join Discord](https://discord.gg/kywDsDnJ6C) – For support, updates and collaboration. + +A Simple but Powerful Flat File Database Storage with document encryption. No need for MySQL or an expensive SQL server, in fact, you just need your current site or application setup. All database entries are stored in files ([formatted](README.md#2-formatting) the way you like). You can even modify the raw data within the files themselves without ever needing to use the API. And even better you can put all your files in **version control** and pass them to your team without having out-of-sync SQL databases. @@ -10,29 +12,29 @@ Doesn't that sound awesome? With Filebase, you are in complete control. Design your data structure the way you want. Use arrays and objects like you know how in PHP. Update and share your data with others and teams using version control. Just remember, upgrading your web/apache server is a lot less than your database server. -Works with **PHP 5.6** and **PHP 7+** +Works with **PHP 5.6** and **PHP 8+** ### Features Filebase is simple by design, but has enough features for the more advanced. * Key/Value and Array-based Data Storing -* [Querying data](https://github.com/filebase/Filebase#8-queries) -* [Custom filters](https://github.com/filebase/Filebase#7-custom-filters) -* [Caching](https://github.com/filebase/Filebase#9-caching) (queries) -* [Database Backups](https://github.com/filebase/Filebase#10-database-backups) -* [Formatting](https://github.com/filebase/Filebase#2-formatting) (encode/decode) -* [Validation](https://github.com/filebase/Filebase#6-validation-optional) (on save) +* [Querying data](README.md#8-queries) +* [Custom filters](README.md#7-custom-filters) +* [Caching](README.md#9-caching) (queries) +* [Database Backups](README.md#10-database-backups) +* [Formatting](README.md#2-formatting) (encode/decode) +* [Validation](README.md#6-validation-optional) (on save) +* [Encryption](README.md#11-Encryption-Added) * CRUD (method APIs) * File locking (on save) * Intuitive Method Naming - ## Installation Use [Composer](http://getcomposer.org/) to install package. -Use `composer require filebase/filebase` +Run `composer require tmarois/filebase:^1.0` If you do not want to use composer, download the files, and include it within your application, it does not have any dependencies, you will just need to keep it updated with any future releases. @@ -45,7 +47,8 @@ $database = new \Filebase\Database([ ]); // in this example, you would search an exact user name -// It would technically be stored as user_name.json in the directories +// it would technically be stored as user_name.json in the directories +// if user_name.json doesn't exists get will return new empty Document $item = $database->get('kingslayer'); // display property values @@ -67,12 +70,11 @@ if ($database->has('kingslayer')) // do some action } - // Need to find all the users that have a tag for "php" ? -$users = $db->query()->where('tags','IN','php')->results(); +$users = $db->where('tags','IN','php')->results(); // Need to search for all the users who use @yahoo.com email addresses? -$users = $db->query()->where('email','LIKE','@yahoo.com')->results(); +$users = $db->where('email','LIKE','@yahoo.com')->results(); ``` @@ -107,8 +109,8 @@ $db = new \Filebase\Database([ |`dir` |string |current directory |The directory where the database files are stored. | |`backupLocation` |string |current directory (`/backups`) |The directory where the backup zip files will be stored. | |`format` |object |`\Filebase\Format\Json` |The format class used to encode/decode data | -|`validate` |array | |Check [Validation Rules](https://github.com/filebase/Filebase#6-validation-optional) for more details | -|`cache` |bool |true |Stores [query](https://github.com/filebase/Filebase#8-queries) results into cache for faster loading. | +|`validate` |array | |Check [Validation Rules](README.md#6-validation-optional) for more details | +|`cache` |bool |true |Stores [query](README.md#8-queries) results into cache for faster loading. | |`cache_expire` |int |1800 |How long caching will last (in seconds) | |`pretty` |bool |true |Store the data for human readability? Pretty Print | |`safe_filename` |bool |true |Automatically converts the file name to a valid name (added: 1.0.13) | @@ -126,11 +128,17 @@ The Default Format Class: `JSON` \Filebase\Format\Json::class ``` +Additional Format Classes: `Yaml` +```php +\Filebase\Format\Yaml::class +``` ## (3) GET (and methods) After you've loaded up your database config, then you can use the `get()` method to retrieve a single document of data. +If document does not exist, it will create a empty object for you to store data into. You can then call the `save()` method and it will create the document (or update an existing one). + ```php // my user id $userId = '92832711'; @@ -152,7 +160,7 @@ $item = $db->get($userId); |`updatedAt()` | Document was updated (default Y-m-d H:i:s) | |`field()` | You can also use `.` dot delimiter to find values from nested arrays | |`isCache()` | (true/false) if the current document is loaded from cache | -|`filter()` | Refer to the [Custom Filters](https://github.com/filebase/Filebase#7-custom-filters) | +|`filter()` | Refer to the [Custom Filters](README.md#7-custom-filters) | Example: @@ -214,15 +222,15 @@ Here is a list of methods you can use on the database class. |Method|Details| |---|---| |`version()` | Current version of your Filebase library | -|`get($id)` | Refer to [get()](https://github.com/filebase/Filebase#3-get-and-methods) | +|`get($id)` | Refer to [get()](README.md#3-get-and-methods) | |`has($id)` | Check if a record exist returning true/false | |`findAll()` | Returns all documents in database | |`count()` | Number of documents in database | |`flush(true)` | Deletes all documents. | |`flushCache()` | Clears all the cache | |`truncate()` | Deletes all documents. Alias of `flush(true)` | -|`query()` | Refer to the [Queries](https://github.com/filebase/Filebase#8-queries) | -|`backup()` | Refer to the [Backups](https://github.com/filebase/Filebase#10-database-backups) | +|`query()` | Refer to the [Queries](README.md#8-queries) | +|`backup()` | Refer to the [Backups](README.md#10-database-backups) | Examples @@ -327,58 +335,85 @@ Queries allow you to search **multiple documents** and return only the ones that If caching is enabled, queries will use `findAll()` and then cache results for the next run. +> Note: You no longer need to call `query()`, you can now call query methods directly on the database class. + ```php // Simple (equal to) Query // return all the users that are blocked. -$users = $db->query()->where(['status' => 'blocked'])->results(); +$users = $db->where(['status' => 'blocked'])->results(); // Stackable WHERE clauses // return all the users who are blocked, // AND have "php" within the tag array -$users = $db->query() - ->where('status','=','blocked') - ->andWhere('tag','IN','php') - ->results(); +$users = $db->where('status','=','blocked') + ->andWhere('tag','IN','php') + ->results(); // You can also use `.` dot delimiter to use on nested keys -$users = $db->query()->where('status.language.english','=','blocked')->results(); +$users = $db->where('status.language.english','=','blocked')->results(); // Limit Example: Same query as above, except we only want to limit the results to 10 -$users = $db->query()->where('status.language.english','=','blocked')->limit(10)->results(); +$users = $db->where('status.language.english','=','blocked')->limit(10)->results(); // Query LIKE Example: how about find all users that have a gmail account? -$usersWithGmail = $db->query()->where('email','LIKE','@gmail.com')->results(); +$usersWithGmail = $db->where('email','LIKE','@gmail.com')->results(); // OrderBy Example: From the above query, what if you want to order the results by nested array -$usersWithGmail = $db->query() - ->where('email','LIKE','@gmail.com') - ->orderBy('profile.name', 'ASC') - ->results(); +$usersWithGmail = $db->where('email','LIKE','@gmail.com') + ->orderBy('profile.name', 'ASC') + ->results(); // or just order the results by email address +$usersWithGmail = $db->where('email','LIKE','@gmail.com') + ->orderBy('email', 'ASC') + ->results(); + +// OrderBy can be applied multiple times to perform a multi-sort $usersWithGmail = $db->query() ->where('email','LIKE','@gmail.com') + ->orderBy('last_name', 'ASC') ->orderBy('email', 'ASC') ->results(); - // this will return the first user in the list based on ascending order of user name. -$user = $db->query()->orderBy('name', 'ASC')->first(); +$user = $db->orderBy('name','ASC')->first(); // print out the user name echo $user['name']; -// What about regex search? Finds emails within a field -$users = $db->query()->where('email','REGEX','/[a-z\d._%+-]+@[a-z\d.-]+\.[a-z]{2,4}\b/i')->results(); +// You can also order multiple columns as such (stacking) +$orderMultiples = $db->orderBy('field1','ASC') + ->orderBy('field2','DESC') + ->results(); +// What about regex search? Finds emails within a field +$users = $db->where('email','REGEX','/[a-z\d._%+-]+@[a-z\d.-]+\.[a-z]{2,4}\b/i')->results(); // Find all users that have gmail addresses and only returning their name and age fields (excluding the rest) -$users = $db->query()->select('name,age')->where('email','LIKE','@gmail.com')->results(); +$users = $db->select('name,age')->where('email','LIKE','@gmail.com')->results(); // Instead of returning users, how about just count how many users are found. -$totalUsers = $db->query()->where('email','LIKE','@gmail.com')->count(); +$totalUsers = $db->where('email','LIKE','@gmail.com')->count(); + + +// You can delete all documents that match the query (BULK DELETE) +$db->where('name','LIKE','john')->delete(); + +// Delete all items that match query and match custom filter +$db->where('name','LIKE','john')->delete(function($item){ + return ($item->name == 'John' && $item->email == 'some@mail.com'); +}); + + +// GLOBAL VARIABLES + +// ability to sort the results by created at or updated at times +$documents = $db->orderBy('__created_at', 'DESC')->results(); +$documents = $db->orderBy('__updated_at', 'DESC')->results(); +// search for items that match the (internal) id +$documents = $db->where('__id', 'IN', ['id1', 'id2'])->results(); ``` @@ -396,6 +431,7 @@ To run the query use `results()` or if you only want to return the first item us |`orWhere()` | `mixed` | see `where()`, this uses the logical `OR` | |`limit()` | `int` limit, `int` offset | How many documents to return, and offset | |`orderBy()` | `field` , `sort order` | Order documents by a specific field and order by `ASC` or `DESC` | +|`delete()` | `Closure` | Ability to Bulk-delete all items that match | The below **methods execute the query** and return results *(do not try to use them together)* @@ -468,9 +504,46 @@ $database->backup()->rollback(); ``` +# Changelog + +All notable changes to this project will be documented here. + +## [2.0.0] - 2023-01-25 + +## (11) Encryption Added + + - 🌟 Added Encryption mechanism to the document. You can now encrypt and decrypt all your documents by passing an encryption array to the config settings as shown below; + +```php +require_once __DIR__."/vendor/autoload.php"; + +/** + * @settings array $encryption + * @param key_storage_path - The path to where your encryption keys are stored + * @key_name - The name of your key which points to the name of the key file (Store this name in your database) + */ + +$db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'encryption' => array('key_storage_path' => __DIR__.'/encrypter', 'key_name' => 'test') +]); + +$db->flush(true); +$user = $db->get(uniqid()); +$user->name = 'John'; +$user->email = 'john@example.com'; +$user->save(); +$db->where('name','=','John')->andWhere('email','==','john@example.com')->select('email')->results(); +$result_from_cache = $db->where('name','=','John')->andWhere('email','==','john@example.com')->select('email')->results(); +print_r($result_from_cache); + +``` + +The above will encrypt your document when creating and decrypt it when fetching or quering. Please note that its required that you have the extension Sodium installed to use the encryption mechanism. + ## Why Filebase? -I originally built Filebase because I needed more flexibility, control over the database files, how they are stored, query filtration and a design with very intuitive API methods. +Filebase was built for the flexibility to help manage simple data storage without the hassle of a heavy database engine. The concept of Filebase is to provide very intuitive API methods, and make it easy for the developer to maintain and manage (even on a large scale). Inspired by [Flywheel](https://github.com/jamesmoss/flywheel) and [Flinetone](https://github.com/fire015/flintstone). @@ -483,6 +556,8 @@ Versions are as follows: Major.Minor.Patch * Minor: New Features/Changes that breaks compatibility. * Patch: New Features/Fixes that does not break compatibility. +Filebase will work-hard to be **backwards-compatible** when possible. + ## Sites and Users of Filebase @@ -496,9 +571,23 @@ Versions are as follows: Major.Minor.Patch * [Discount Savings](https://discount-savings.com) * [Vivint - Smart Homes](http://smarthomesecurityplans.com/) -*If you are using Filebase on your website, send in a pull request and we will put your site up here.* +*If you are using Filebase – send in a pull request and we will add your project here.* ## Contributions -Accepting contributions and feedback. Send in any issues and pull requests. +Anyone can contribute to Filebase. Please do so by posting issues when you've found something that is unexpected or sending a pull request for improvements. + +## All Contributors + + + + + + +
Adeyeye George
Adeyeye George
+ + +## License + +Filebase is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/composer.json b/composer.json index f423c05..102cc36 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "description": "A Simple but Powerful Flat File Database Storage.", "keywords": ["filebase", "key-value","file-files", "flat", "file", "database", "document", "flat-file", "serverless"], - "name": "filebase/filebase", - "homepage": "https://github.com/filebase/Filebase", + "name": "tmarois/filebase", + "homepage": "https://github.com/tmarois/Filebase", "type": "package", "licence": "MIT", @@ -14,14 +14,20 @@ ], "require": { - "php": ">=5.6" + "php": ">=5.6", + "paragonie/halite": "5" }, "require-dev": { - "satooshi/php-coveralls": "^2.0", - "phpunit/phpunit": "5.*" + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "5.*", + "symfony/yaml": "^3.4" }, + "suggest": { + "symfony/yaml": "Allows Yaml format" + }, + "autoload": { "psr-4": { "Filebase\\": "src/" diff --git a/src/Backup.php b/src/Backup.php index d96872e..52625ed 100644 --- a/src/Backup.php +++ b/src/Backup.php @@ -12,10 +12,6 @@ class Backup */ protected $backupLocation; - - //-------------------------------------------------------------------- - - /** * $config * @@ -24,10 +20,6 @@ class Backup */ protected $config; - - //-------------------------------------------------------------------- - - /** * $database * @@ -35,16 +27,13 @@ class Backup */ protected $database; - - //-------------------------------------------------------------------- - - /** * __construct * */ - public function __construct($backupLocation = '', Database $database) + public function __construct($backupLocation, Database $database) { + $this->backupLocation = $backupLocation; $this->config = $database->getConfig(); $this->database = $database; @@ -63,10 +52,6 @@ public function __construct($backupLocation = '', Database $database) } } - - //-------------------------------------------------------------------- - - /** * save() * @@ -84,10 +69,6 @@ public function create() throw new \Exception('Error backing up database.'); } - - //-------------------------------------------------------------------- - - /** * find() * @@ -109,10 +90,6 @@ public function find() return $backups; } - - //-------------------------------------------------------------------- - - /** * clean() * @@ -124,10 +101,6 @@ public function clean() return array_map('unlink', glob(realpath($this->backupLocation)."/*.zip")); } - - //-------------------------------------------------------------------- - - /** * rollback() * @@ -144,10 +117,6 @@ public function rollback() return $this->extract($restore, $this->config->dir); } - - //-------------------------------------------------------------------- - - /** * extract() * @@ -157,28 +126,21 @@ public function rollback() */ protected function extract($source = '', $target = '') { - if (extension_loaded('zip')) + if (!extension_loaded('zip') && !file_exists($source)) { - if (file_exists($source)) - { - $zip = new \ZipArchive(); - if ($zip->open($source) === TRUE) - { - $zip->extractTo($target); - $zip->close(); - - return true; - } - } + return false; } + $zip = new \ZipArchive(); + if ($zip->open($source) === TRUE) + { + $zip->extractTo($target); + $zip->close(); + return true; + } return false; } - - //-------------------------------------------------------------------- - - /** * zip() * @@ -187,52 +149,45 @@ protected function extract($source = '', $target = '') */ protected function zip($source = '', $target = '') { - if (extension_loaded('zip')) + if (!extension_loaded('zip') || !file_exists($source)) { - if (file_exists($source)) - { - $zip = new \ZipArchive(); - if ($zip->open($target, \ZIPARCHIVE::CREATE)) - { - $source = realpath($source); - if (is_dir($source)) - { - $iterator = new \RecursiveDirectoryIterator($source); - $iterator->setFlags(\RecursiveDirectoryIterator::SKIP_DOTS); - $files = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); - foreach ($files as $file) - { - $file = realpath($file); - - if (preg_match('|'.realpath($this->backupLocation).'|',$file)) - { - continue; - } - - if (is_dir($file)) - { - $zip->addEmptyDir(str_replace($source . '/', '', $file . '/')); - } - else if (is_file($file)) - { - $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file)); - } - } - - } - else if (is_file($source)) - { - $zip->addFromString(basename($source), file_get_contents($source)); - } - } - - return $zip->close(); - } - } - - return false; - } + return false; + } + + $zip = new \ZipArchive(); + if (!$zip->open($target, \ZIPARCHIVE::CREATE)) + { + $zip->addFromString(basename($source), file_get_contents($source)); + } + $source = realpath($source); + if (is_dir($source)) + { + $iterator = new \RecursiveDirectoryIterator($source); + $iterator->setFlags(\RecursiveDirectoryIterator::SKIP_DOTS); + $files = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + foreach ($files as $file) + { + $file = realpath($file); + if (preg_match('|'.realpath($this->backupLocation).'|',$file)) + { + continue; + } + if (is_dir($file)) + { + $zip->addEmptyDir(str_replace($source . '/', '', $file . '/')); + } + else if (is_file($file)) + { + $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file)); + } + } + + } + + return $zip->close(); + + } } diff --git a/src/Cache.php b/src/Cache.php index 47090f3..7eaa710 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -26,9 +26,6 @@ class Cache */ protected $key; - - //-------------------------------------------------------------------- - /** * __construct() * @@ -44,10 +41,6 @@ public function __construct(Database $database) ]); } - - //-------------------------------------------------------------------- - - /** * setKey() * @@ -60,10 +53,6 @@ public function setKey($key) $this->key = md5($key); } - - //-------------------------------------------------------------------- - - /** * getKey() * @@ -73,10 +62,6 @@ public function getKey() return $this->key; } - - //-------------------------------------------------------------------- - - /** * flush() * @@ -86,10 +71,6 @@ public function flush() $this->cache_database->flush(true); } - - //-------------------------------------------------------------------- - - /** * expired() * @@ -106,10 +87,6 @@ public function expired($time) return true; } - - //-------------------------------------------------------------------- - - /** * getDocuments() * @@ -125,10 +102,6 @@ public function getDocuments($documents) return $d; } - - //-------------------------------------------------------------------- - - /** * get() * @@ -155,10 +128,6 @@ public function get() return $this->getDocuments($cache_doc->toArray()); } - - //-------------------------------------------------------------------- - - /** * store() * @@ -173,8 +142,4 @@ public function store($data) return $this->cache_database->get( $this->getKey() )->set($data)->save(); } - - //-------------------------------------------------------------------- - - } diff --git a/src/Config.php b/src/Config.php index 304f931..60baf48 100644 --- a/src/Config.php +++ b/src/Config.php @@ -11,7 +11,6 @@ class Config */ public $dir = __DIR__; - /** * $format * Format Class @@ -19,7 +18,6 @@ class Config */ public $format = \Filebase\Format\Json::class; - /** * $cache * Caching for queries @@ -28,7 +26,6 @@ class Config */ public $cache = true; - /** * $cache_time * When should cache be cleared? @@ -37,7 +34,6 @@ class Config */ public $cache_expires = 1800; - /** * $safe_filename * (if true) Be sure to automatically change the file name if it does not fit validation @@ -47,7 +43,6 @@ class Config */ public $safe_filename = true; - /** * $read_only * (if true) We will not attempt to create the database directory or allow the user to create anything @@ -57,7 +52,6 @@ class Config */ public $read_only = false; - /** * $backupLocation * The location to store backups @@ -66,7 +60,6 @@ class Config */ public $backupLocation = ''; - /** * $pretty * @@ -77,16 +70,18 @@ class Config */ public $pretty = true; - /** * $validate * */ public $validate = []; - - //-------------------------------------------------------------------- - + /** + * $encryption + * + * Contains all encryption options + */ + public $encryption; /** * __construct @@ -110,10 +105,6 @@ public function __construct($config) $this->validateFormatClass(); } - - //-------------------------------------------------------------------- - - /** * format * @@ -138,8 +129,4 @@ protected function validateFormatClass() throw new \Exception('Filebase Error: Format Class must be an instance of Filebase\Format\FormatInterface'); } } - - - //-------------------------------------------------------------------- - } diff --git a/src/Database.php b/src/Database.php index 88f6ed0..7f5ce0f 100644 --- a/src/Database.php +++ b/src/Database.php @@ -1,11 +1,10 @@ getVersion() */ - const VERSION = '1.0.20'; - - - //-------------------------------------------------------------------- + const VERSION = '1.0.24'; /** * $config @@ -28,12 +24,13 @@ class Database * \Filebase\Config */ protected $config; - - /** - * __construct - * - */ + * Database constructor. + * + * @param array $config + * + * @throws FilesystemException + */ public function __construct(array $config = []) { $this->config = new Config($config); @@ -46,19 +43,15 @@ public function __construct(array $config = []) { if (!@mkdir($this->config->dir, 0777, true)) { - throw new Exception(sprintf('`%s` doesn\'t exist and can\'t be created.', $this->config->dir)); + throw new FilesystemException(sprintf('`%s` doesn\'t exist and can\'t be created.', $this->config->dir)); } } else if (!is_writable($this->config->dir)) { - throw new Exception(sprintf('`%s` is not writable.', $this->config->dir)); + throw new FilesystemException(sprintf('`%s` is not writable.', $this->config->dir)); } } - - //-------------------------------------------------------------------- - - /** * version * @@ -71,10 +64,6 @@ public function version() return self::VERSION; } - - //-------------------------------------------------------------------- - - /** * findAll() * @@ -94,31 +83,26 @@ public function findAll($include_documents = true, $data_only = false) $file_location = $this->config->dir.'/'; $all_items = Filesystem::getAllFiles($file_location, $file_extension); - if ($include_documents==true) + if (!$include_documents) { - $items = []; - - foreach($all_items as $a) - { - if ($data_only === true) - { - $items[] = $this->get($a)->getData(); - } - else - { - $items[] = $this->get($a); - } - } - - return $items; + return $all_items; } + $items = []; - return $all_items; - } - - - //-------------------------------------------------------------------- + foreach($all_items as $a) + { + if ($data_only === true) + { + $items[] = $this->get($a)->getData(); + } + else + { + $items[] = $this->get($a); + } + } + return $items; + } /** * get @@ -147,10 +131,6 @@ public function get($id) return $document; } - - //-------------------------------------------------------------------- - - /** * has * @@ -163,15 +143,11 @@ public function get($id) public function has($id) { $format = $this->config->format; - $record = Filesystem::read( $this->config->dir.'/'.Filesystem::validateName($id, $this->config->safe_filename).'.'.$format::getFileExtension() ); + $record = Filesystem::read( $this->config->dir.'/'.Filesystem::validateName($id, $this->config->safe_filename).'.'.$format::getFileExtension(), $this->config->encryption); return $record ? true : false; } - - //-------------------------------------------------------------------- - - /** * backup * @@ -189,10 +165,6 @@ public function backup($location = '') return new Backup($this->config->backupLocation, $this); } - - //-------------------------------------------------------------------- - - /** * set * @@ -215,10 +187,6 @@ public function set(Document $document, $data) return $document; } - - //-------------------------------------------------------------------- - - /** * count * @@ -230,23 +198,17 @@ public function count() return count($this->findAll(false)); } - - //-------------------------------------------------------------------- - - /** - * save - * - * @param $document \Filebase\Document object - * @param mixed $data should be an array, new data to replace all existing data within - * - * @return (bool) true or false if file was saved - */ + * @param Document $document + * @param string $wdata + * @return bool|Document + * @throws SavingException + */ public function save(Document $document, $wdata = '') { if ($this->config->read_only === true) { - throw new Exception("This database is set to be read-only. No modifications can be made."); + throw new SavingException("This database is set to be read-only. No modifications can be made."); } $format = $this->config->format; @@ -263,30 +225,29 @@ public function save(Document $document, $wdata = '') $document->setCreatedAt($created); } - if (!Filesystem::read($file_location) || $created==false) + if (!Filesystem::read($file_location, $this->config->encryption) || $created==false) { $document->setCreatedAt(time()); } $document->setUpdatedAt(time()); - $data = $format::encode( $document->saveAs(), $this->config->pretty ); + try { + $data = $format::encode( $document->saveAs(), $this->config->pretty ); + } catch (EncodingException $e) { + // TODO: add logging + throw new SavingException("Can not encode document.", 0, $e); + } - if (Filesystem::write($file_location, $data)) + if (Filesystem::write($file_location, $data, $this->config->encryption)) { $this->flushCache(); return $document; } - else - { - return false; - } - } - - - //-------------------------------------------------------------------- + return false; + } /** * query @@ -298,26 +259,32 @@ public function query() return new Query($this); } - - //-------------------------------------------------------------------- - - - /** - * read - * - * @param string $name - * @return decoded file data - */ + * Read and return Document from filesystem by name. + * If doesn't exists return new empty Document. + * + * @param $name + * + * @throws Exception|ReadingException + * @return array|null + */ protected function read($name) { $format = $this->config->format; - return $format::decode( Filesystem::read( $this->config->dir.'/'.Filesystem::validateName($name, $this->config->safe_filename).'.'.$format::getFileExtension() ) ); - } + $file = Filesystem::read( + $this->config->dir . '/' + . Filesystem::validateName($name, $this->config->safe_filename) + . '.' . $format::getFileExtension(), + $this->config->encryption + ); - //-------------------------------------------------------------------- + if ($file !== false) { + return $format::decode($file); + } + return null; + } /** * delete @@ -341,9 +308,6 @@ public function delete(Document $document) return $d; } - - //-------------------------------------------------------------------- - /** * truncate * @@ -356,10 +320,6 @@ public function truncate() return $this->flush(true); } - - //-------------------------------------------------------------------- - - /** * flush * @@ -375,33 +335,25 @@ public function flush($confirm = false) throw new Exception("This database is set to be read-only. No modifications can be made."); } - if ($confirm===true) - { - $format = $this->config->format; - $documents = $this->findAll(false); - foreach($documents as $document) - { - Filesystem::delete($this->config->dir.'/'.$document.'.'.$format::getFileExtension()); - } - - if ($this->count() === 0) - { - return true; - } - else - { - throw new Exception("Could not delete all database files in ".$this->config->dir); - } - } - else + if ($confirm!==true) { throw new Exception("Database Flush failed. You must send in TRUE to confirm action."); } - } + $format = $this->config->format; + $documents = $this->findAll(false); + foreach($documents as $document) + { + Filesystem::delete($this->config->dir.'/'.$document.'.'.$format::getFileExtension()); + } - //-------------------------------------------------------------------- + if ($this->count() === 0) + { + return true; + } + throw new Exception("Could not delete all database files in ".$this->config->dir); + } /** * flushCache @@ -417,10 +369,6 @@ public function flushCache() } } - - //-------------------------------------------------------------------- - - /** * toArray * @@ -432,10 +380,6 @@ public function toArray(Document $document) return $this->objectToArray( $document->getData() ); } - - //-------------------------------------------------------------------- - - /** * objectToArray * @@ -456,10 +400,6 @@ public function objectToArray($obj) return $arr; } - - //-------------------------------------------------------------------- - - /** * getConfig * @@ -470,4 +410,23 @@ public function getConfig() return $this->config; } + /** + * __call + * + * Magic method to give us access to query methods on db class + * + */ + public function __call($method,$args) + { + if(method_exists($this,$method)) { + return $this->$method(...$args); + } + + if(method_exists(Query::class,$method)) { + return (new Query($this))->$method(...$args); + } + + throw new \BadMethodCallException("method {$method} not found on 'Database::class' and 'Query::class'"); + } + } diff --git a/src/Document.php b/src/Document.php index 09daa06..0fcf27d 100644 --- a/src/Document.php +++ b/src/Document.php @@ -25,10 +25,6 @@ public function __construct($database) $this->__database = $database; } - - //-------------------------------------------------------------------- - - /** * saveAs * @@ -47,10 +43,6 @@ public function saveAs() return $data; } - - //-------------------------------------------------------------------- - - /** * save() * @@ -66,10 +58,6 @@ public function save($data = '') return $this->__database->save($this, $data); } - - //-------------------------------------------------------------------- - - /** * delete * @@ -82,10 +70,6 @@ public function delete() return $this->__database->delete($this); } - - //-------------------------------------------------------------------- - - /** * set * @@ -95,10 +79,6 @@ public function set($data) return $this->__database->set($this, $data); } - - //-------------------------------------------------------------------- - - /** * toArray * @@ -108,10 +88,6 @@ public function toArray() return $this->__database->toArray($this); } - - //-------------------------------------------------------------------- - - /** * __set * @@ -121,10 +97,6 @@ public function __set($name, $value) $this->data[$name] = $value; } - - //-------------------------------------------------------------------- - - /** * __get * @@ -139,10 +111,6 @@ public function &__get($name) return $this->data[$name]; } - - //-------------------------------------------------------------------- - - /** * __isset * @@ -152,10 +120,6 @@ public function __isset($name) return isset($this->data[$name]); } - - //-------------------------------------------------------------------- - - /** * __unset * @@ -181,10 +145,6 @@ public function filter($field = 'data', $paramOne = '', $paramTwo = '') return $this->customFilter($field, $paramOne, $paramTwo); } - - //-------------------------------------------------------------------- - - /** * customFilter * @@ -235,10 +195,6 @@ public function customFilter($field = 'data', $paramOne = '', $paramTwo = '') } - - //-------------------------------------------------------------------- - - /** * getDatabase * @@ -249,10 +205,6 @@ public function getDatabase() return $this->__database; } - - //-------------------------------------------------------------------- - - /** * getId * @@ -263,10 +215,6 @@ public function getId() return $this->__id; } - - //-------------------------------------------------------------------- - - /** * getData * @@ -277,10 +225,6 @@ public function getData() return $this->data; } - - //-------------------------------------------------------------------- - - /** * setId * @@ -293,10 +237,6 @@ public function setId($id) return $this; } - - //-------------------------------------------------------------------- - - /** * setCache * @@ -309,10 +249,6 @@ public function setFromCache($cache = true) return $this; } - - //-------------------------------------------------------------------- - - /** * isCache * @@ -322,11 +258,6 @@ public function isCache() return $this->__cache; } - - - //-------------------------------------------------------------------- - - /** * createdAt * @@ -350,10 +281,6 @@ public function createdAt($format = 'Y-m-d H:i:s') return $this->__created_at; } - - //-------------------------------------------------------------------- - - /** * updatedAt * @@ -377,10 +304,6 @@ public function updatedAt($format = 'Y-m-d H:i:s') return $this->__updated_at; } - - //-------------------------------------------------------------------- - - /** * setCreatedAt * @@ -393,10 +316,6 @@ public function setCreatedAt($created_at) return $this; } - - //-------------------------------------------------------------------- - - /** * setuUpdatedAt * @@ -409,10 +328,6 @@ public function setUpdatedAt($updated_at) return $this; } - - //-------------------------------------------------------------------- - - /** * field * @@ -429,11 +344,22 @@ public function field($field) $parts = explode('.', $field); $context = $this->data; - if ($field=='data') - { + if ($field=='data') { return $context; } + if ($field == '__created_at') { + return $this->__created_at; + } + + if ($field == '__updated_at') { + return $this->__updated_at; + } + + if ($field == '__id') { + return $this->__id; + } + foreach($parts as $part) { if (trim($part) == '') diff --git a/src/Encrypter.php b/src/Encrypter.php new file mode 100644 index 0000000..9c25075 --- /dev/null +++ b/src/Encrypter.php @@ -0,0 +1,49 @@ +ext = '.'.$ext; + $this->folder = $keyStoragePath.'/keys'; + if(!is_dir($this->folder)){ + mkdir($this->folder, 0750, true); + } + $this->folder = $this->folder.'/'.$keyName.$this->ext; + if (!file_exists($this->folder)) { + KeyFactory::save(KeyFactory::generateEncryptionKey(), $this->folder); + chmod($this->folder, 0444); + } + $this->secretKey = KeyFactory::loadEncryptionKey($this->folder); + } + + public function encryptFile($fileInput, $fileOutput) + { + try { + return File::encrypt($fileInput, $fileOutput, $this->secretKey); + } catch (\Throwable $e) { + throw new \Exception($e->getMessage()); + } + } + + public function decryptFile($fileInput, $fileOutput) + { + try { + return File::decrypt($fileInput, $fileOutput, $this->secretKey); + } catch (\Throwable $e) { + throw new \Exception($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/src/Filesystem.php b/src/Filesystem.php index 2e37950..8c07cf9 100644 --- a/src/Filesystem.php +++ b/src/Filesystem.php @@ -1,5 +1,5 @@ -decryptFile($path, $tmp); + $path = $tmp; + } + } + } $file = fopen($path, 'r'); $contents = fread($file, filesize($path)); fclose($file); + if(file_exists($tmp)){ + unlink($tmp); + } + return $contents; } - - - //-------------------------------------------------------------------- - - /** * Writes data to the filesystem. * * @param string $path The absolute file path to write to * @param string $contents The contents of the file to write + * @param array $encryption containing encryption options * * @return boolean Returns true if write was successful, false if not. */ - public static function write($path, $contents) + public static function write($path, $contents, $encryption = null) { $fp = fopen($path, 'w+'); @@ -50,13 +64,25 @@ public static function write($path, $contents) flock($fp, LOCK_UN); fclose($fp); + if(is_array($encryption) and !empty($encryption)){ + $validate = self::validate_crypt_keys($encryption); + if($validate){ + //Check if none passed is empty + if(!empty($encryption['key_storage_path']) and !empty($encryption['key_name'])){ + $tmp = tmpfile(); + $encrypter = new Encrypter($encryption['key_storage_path'], $encryption['key_name']); + $tmp = stream_get_meta_data($tmp)['uri']; + $encrypter->encryptFile($path, $tmp); + @unlink($path); + rename($tmp, $path); + @unlink($tmp); + } + } + } + return $result !== false; } - - //-------------------------------------------------------------------- - - /** * delete * @@ -66,13 +92,13 @@ public static function write($path, $contents) */ public static function delete($path) { + if (!file_exists($path)) { + return true; + } + return unlink($path); } - - //-------------------------------------------------------------------- - - /** * Validates the name of the file to ensure it can be stored in the * filesystem. @@ -103,16 +129,12 @@ public static function validateName($name, $safe_filename) return $name; } - - //-------------------------------------------------------------------- - - /** * Get an array containing the path of all files in this repository * * @return array An array, item is a file */ - public static function getAllFiles($path = '',$ext = 'json') + public static function getAllFiles($path = '', $ext = 'json') { $files = []; $_files = glob($path.'*.'.$ext); @@ -124,7 +146,12 @@ public static function getAllFiles($path = '',$ext = 'json') return $files; } - - //-------------------------------------------------------------------- - + private static function validate_crypt_keys(array $sentKeys){ + $allowedKeys = array('key_storage_path', 'key_name'); + $notAllowedKeys = array_diff(array_keys($sentKeys), $allowedKeys); + if($notAllowedKeys) { + throw new \Exception("One or more keys are not allowed ".json_encode($notAllowedKeys)); + } + return true; + } } diff --git a/src/Filesystem/FilesystemException.php b/src/Filesystem/FilesystemException.php new file mode 100644 index 0000000..f901aba --- /dev/null +++ b/src/Filesystem/FilesystemException.php @@ -0,0 +1,6 @@ +inputData = $inputData; + } + + public function getInputData() + { + return $this->inputData; + } +} + diff --git a/src/Format/Json.php b/src/Format/Json.php index 98071a9..6642208 100644 --- a/src/Format/Json.php +++ b/src/Format/Json.php @@ -3,47 +3,58 @@ class Json implements FormatInterface { - /** - * getFileExtension - * - */ + * @return string + */ public static function getFileExtension() { return 'json'; } - - //-------------------------------------------------------------------- - - /** - * encode - * - */ + * @param array $data + * @param bool $pretty + * @return string + * @throws FormatException + */ public static function encode($data = [], $pretty = true) { - $p = 1; - if ($pretty==true) $p = JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES; - - return json_encode($data, $p); + $options = 0; + if ($pretty == true) { + $options = JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE; + } + + $encoded = json_encode($data, $options); + if ($encoded === false) { + throw new EncodingException( + "json_encode: '" . json_last_error_msg() . "'", + 0, + null, + $data + ); + } + + return $encoded; } - - //-------------------------------------------------------------------- - - /** - * decode - * - */ + * @param $data + * @return mixed + * @throws FormatException + */ public static function decode($data) { - return json_decode($data, 1); + $decoded = json_decode($data, true); + + if ($data !== false && $decoded === null) { + throw new DecodingException( + "json_decode: '" . json_last_error_msg() . "'", + 0, + null, + $data + ); + } + + return $decoded; } - - - //-------------------------------------------------------------------- - - } diff --git a/src/Format/Yaml.php b/src/Format/Yaml.php new file mode 100644 index 0000000..10793ea --- /dev/null +++ b/src/Format/Yaml.php @@ -0,0 +1,37 @@ +predicates[$logic][] = $arg; } - - //-------------------------------------------------------------------- - /** * formatWhere * @@ -94,10 +87,6 @@ protected function formatWhere($key, $value) return [$key,'==',$value]; } - - //-------------------------------------------------------------------- - - /** * get * @@ -106,8 +95,4 @@ public function get() { return array_filter($this->predicates); } - - - //-------------------------------------------------------------------- - } diff --git a/src/Query.php b/src/Query.php index 62869ac..3712a29 100644 --- a/src/Query.php +++ b/src/Query.php @@ -7,9 +7,8 @@ class Query extends QueryLogic protected $fields = []; protected $limit = 0; protected $offset = 0; - protected $sortBy = 'ASC'; - protected $orderBy = ''; - + protected $sortBy = ['ASC']; + protected $orderBy = ['']; /** * $documents @@ -17,10 +16,6 @@ class Query extends QueryLogic */ protected $documents = []; - - //-------------------------------------------------------------------- - - /** * ->select() * @@ -42,7 +37,6 @@ public function select($fields) return $this; } - /** * ->where() * @@ -54,10 +48,6 @@ public function where(...$arg) return $this; } - - //-------------------------------------------------------------------- - - /** * ->andWhere() * @@ -69,10 +59,6 @@ public function andWhere(...$arg) return $this; } - - //-------------------------------------------------------------------- - - /** * ->orWhere() * @@ -84,10 +70,6 @@ public function orWhere(...$arg) return $this; } - - //-------------------------------------------------------------------- - - /** * ->limit() * @@ -106,26 +88,24 @@ public function limit($limit, $offset = 0) return $this; } - - //-------------------------------------------------------------------- - - /** * ->orderBy() * */ - public function orderBy($field, $sort) + public function orderBy($field, $sort = 'ASC') { - $this->orderBy = $field; - $this->sortBy = $sort; + if (count($this->orderBy) == 1 && $this->orderBy[0] == '') { + // Just set the initial index + $this->orderBy[0] = $field; + $this->sortBy[0] = strtoupper($sort); + } else { + $this->orderBy[] = $field; + $this->sortBy[] = strtoupper($sort); + } return $this; } - - //-------------------------------------------------------------------- - - /** * addPredicate * @@ -135,10 +115,6 @@ protected function addPredicate($logic,$arg) $this->predicate->add($logic, $arg); } - - //-------------------------------------------------------------------- - - /** * ->getDocuments() * @@ -149,10 +125,6 @@ public function getDocuments() return $this->documents; } - - //-------------------------------------------------------------------- - - /** * ->results() * @@ -169,10 +141,6 @@ public function results( $data_only = true ) return $this->resultDocuments(); } - - //-------------------------------------------------------------------- - - /** * ->resultDocuments() * @@ -182,10 +150,6 @@ public function resultDocuments() return parent::run()->getDocuments(); } - - //-------------------------------------------------------------------- - - /** * ->first() * @@ -204,10 +168,6 @@ public function first( $data_only = true ) return current($results); } - - //-------------------------------------------------------------------- - - /** * ->last() * @@ -226,9 +186,6 @@ public function last( $data_only = true ) return end($results); } - //-------------------------------------------------------------------- - - /** * ->count() * @@ -241,11 +198,6 @@ public function count() return count($results); } - - //-------------------------------------------------------------------- - - - /** * toArray * @@ -267,7 +219,31 @@ public function toArray() return $docs; } + /** + * delete + * + * The ability to delete items using queries + * + * Delete by condition or delete all within clause + * + * @return void + */ + public function delete($input = null) + { + $items = $this->resultDocuments(); + $condition = $input; + foreach($items as $item) + { + if (is_object($input)) { + $condition = $input($item); - //-------------------------------------------------------------------- - + if ($condition) { + $item->delete(); + } + } + else { + $item->delete(); + } + } + } } diff --git a/src/QueryLogic.php b/src/QueryLogic.php index 764e190..0d97c4f 100644 --- a/src/QueryLogic.php +++ b/src/QueryLogic.php @@ -1,5 +1,6 @@ predicate->get(); + + if ($this->cache===false) + { + $this->documents = $this->database->findAll(true,false); + return $this; + } + + $this->cache->setKey(json_encode($predicates)); - //-------------------------------------------------------------------- + if ($cached_documents = $this->cache->get()) + { + $this->documents = $cached_documents; + $this->sort(); + $this->offsetLimit(); + return $this; + } + $this->documents = $this->database->findAll(true,false); + return $this; + } /** * run @@ -65,22 +85,7 @@ public function run() $predicates = 'findAll'; } - if ($this->cache !== false) - { - $this->cache->setKey(json_encode($predicates)); - - if ($cached_documents = $this->cache->get()) - { - $this->documents = $cached_documents; - - $this->sort(); - $this->offsetLimit(); - - return $this; - } - } - - $this->documents = $this->database->findAll(true,false); + $this->loadDocuments(); if ($predicates !== 'findAll') { @@ -121,10 +126,6 @@ public function run() return $this; } - - //-------------------------------------------------------------------- - - /** * filter * @@ -166,10 +167,6 @@ protected function filter($documents, $predicates) return $results; } - - //-------------------------------------------------------------------- - - /** * offsetLimit * @@ -182,51 +179,21 @@ protected function offsetLimit() } } - - //-------------------------------------------------------------------- - - /** * sort * */ protected function sort() { - $orderBy = $this->orderBy; - $sortBy = $this->sortBy; - - if ($orderBy=='') + if ($this->orderBy[0] == '') { return false; } - usort($this->documents, function($a, $b) use ($orderBy, $sortBy) { - - $propA = $a->field($orderBy); - $propB = $b->field($orderBy); - - - if (strnatcasecmp($propB, $propA) == strnatcasecmp($propA, $propB)) { - return 0; - } - - if ($sortBy == 'DESC') - { - return (strnatcasecmp($propB, $propA) < strnatcasecmp($propA, $propB)) ? -1 : 1; - } - else - { - return (strnatcasecmp($propA, $propB) < strnatcasecmp($propB, $propA)) ? -1 : 1; - } - - }); - + $sortlogic = new SortLogic($this->orderBy, $this->sortBy, 0); + usort($this->documents, [$sortlogic, 'sort']); } - - //-------------------------------------------------------------------- - - /** * match * @@ -273,8 +240,4 @@ public function match($document, $field, $operator, $value) } - - //-------------------------------------------------------------------- - - } diff --git a/src/SortLogic.php b/src/SortLogic.php new file mode 100644 index 0000000..ef417e3 --- /dev/null +++ b/src/SortLogic.php @@ -0,0 +1,79 @@ +orderBy = $orderBy; + $this->sortDirection = $sortDirection; + $this->index = $index; + } + + /** + * Sorting callback + * + * @param Document $docA + * @param Document $docB + * @return return int (-1, 0, 1) + */ + public function sort($docA, $docB) + { + $propA = $docA->field($this->orderBy[$this->index]); + $propB = $docB->field($this->orderBy[$this->index]); + + if (strnatcasecmp($propA, $propB) == 0) + { + if (!isset($this->orderBy[$this->index + 1])) + { + return 0; + } + + // If they match and there are multiple orderBys, go deeper (recurse) + $sortlogic = new self($this->orderBy, $this->sortDirection, $this->index + 1); + return $sortlogic->sort($docA, $docB); + } + + if ($this->sortDirection[$this->index] == 'DESC') + { + return strnatcasecmp($propB, $propA); + } + else + { + return strnatcasecmp($propA, $propB); + } + } +} diff --git a/src/Validate.php b/src/Validate.php index 15d2b59..5d29ae9 100644 --- a/src/Validate.php +++ b/src/Validate.php @@ -19,10 +19,6 @@ public static function valid(Document $object) return true; } - - //-------------------------------------------------------------------- - - /** * getValidateRules * @@ -34,10 +30,6 @@ public static function getValidateRules(Document $object) return $object->getDatabase()->getConfig()->validate; } - - //-------------------------------------------------------------------- - - /** * validateLoop * @@ -62,10 +54,6 @@ protected static function validateLoop($document,$object,$rules) } } - - //-------------------------------------------------------------------- - - /** * validateRules * @@ -104,10 +92,6 @@ protected static function validateRules($document,$key,$rules,$object) return $object; } - - //-------------------------------------------------------------------- - - /** * checkType * @@ -152,9 +136,4 @@ protected static function checkType($variable, $type) return false; } - - - //-------------------------------------------------------------------- - - } diff --git a/tests/BackupYamlTest.php b/tests/BackupYamlTest.php new file mode 100644 index 0000000..4d945c5 --- /dev/null +++ b/tests/BackupYamlTest.php @@ -0,0 +1,176 @@ + __DIR__.'/databases/mydatabasetobackup', + 'backupLocation' => __DIR__.'/databases/storage/backups', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->backup(); + + $this->assertEquals(true, true); + } + + + public function testBackupLocationDefault() + { + + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/mydatabasetobackup', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->backup(); + + $this->assertEquals(true, true); + } + + + public function testBackupCreate() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/mydatabasetobackup', + 'backupLocation' => __DIR__.'/databases/storage/backups', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 25; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->save(); + } + + $file = $db->backup()->create(); + + $db->flush(true); + + $this->assertRegExp('/[0-9]+\.zip$/',$file); + } + + + public function testBackupFind() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/mydatabasetobackup', + 'backupLocation' => __DIR__.'/databases/storage/backups', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 25; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->save(); + } + + $db->backup()->create(); + + $backups = $db->backup()->find(); + + $this->assertInternalType('array',$backups); + $this->assertNotEmpty($backups); + } + + + public function testBackupFindSort() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/mydatabasetobackup', + 'backupLocation' => __DIR__.'/databases/storage/backups', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 25; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->save(); + } + + $db->backup()->create(); + $db->backup()->create(); + $last = str_replace('.zip','',$db->backup()->create()); + + $backups = $db->backup()->find(); + $backupCurrent = current($backups); + + $lastBackup = str_replace('.zip','',basename($backupCurrent)); + + $db->flush(true); + + $this->assertEquals($last,$lastBackup); + } + + + public function testBackupCleanup() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/mydatabasetobackup', + 'backupLocation' => __DIR__.'/databases/storage/backups', + 'format' => \Filebase\Format\Yaml::class + ]); + + $backupBefore = $db->backup()->find(); + + $db->backup()->clean(); + $backupAfter = $db->backup()->find(); + + $this->assertInternalType('array',$backupBefore); + $this->assertNotEmpty($backupBefore); + + $this->assertInternalType('array',$backupAfter); + $this->assertEmpty($backupAfter); + } + + + public function testBackupRestore() + { + $db1 = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/backupdb', + 'backupLocation' => __DIR__.'/databases/storage/backupdb', + 'format' => \Filebase\Format\Yaml::class + ]); + + for ($x = 1; $x <= 25; $x++) + { + $user = $db1->get(uniqid()); + $user->name = 'John'; + $user->save(); + } + + $db1->backup()->create(); + + $items1 = $db1->count(); + + $db2 = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/backupdb2', + 'backupLocation' => __DIR__.'/databases/storage/backupdb', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db2->backup()->rollback(); + $db2->backup()->clean(); + + $items2 = $db2->count(); + + $db1->flush(true); + $db2->flush(true); + + $this->assertEquals($items1,$items2); + + } + + +} diff --git a/tests/DatabaseTest.php b/tests/DatabaseTest.php index ddd6d7c..e187e71 100644 --- a/tests/DatabaseTest.php +++ b/tests/DatabaseTest.php @@ -1,6 +1,8 @@ flush(true); } + public function testDatabaseSavingNotEncodableDocument() + { + $this->expectException(SavingException::class); + + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases' + ]); + + $doc = $db->get("testDatabaseSavingNotEncodableDocument"); + + // insert invalid utf-8 characters + $doc->testProp = "\xB1\x31"; + + $doc->save(); + } + public function test_Call_Queryclass_methods_on_database_without_query_method() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/saved', + 'cache' => true + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->email = 'john@example.com'; + $user->save(); + } + + $results = $db->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); + $result_from_cache = $db->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); + + $this->assertEquals(10, count($results)); + $this->assertEquals(true, ($result_from_cache[0]->isCache())); + + $id = $result_from_cache[0]->getId(); + $id2 = $result_from_cache[1]->getId(); + + // Change the name + $result_from_cache[0]->name = 'Tim'; + $result_from_cache[0]->save(); + + $results = $db + ->where('name','=','John') + ->andWhere('email','==','john@example.com') + ->resultDocuments(); + + $this->assertEquals($id2, $results[0]->getId()); + $this->assertEquals('John', $results[0]->name); + + $db->flush(true); + } + public function test_must_return_exception_on_non_exist_method() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/saved', + 'cache' => true + ]); + + $this->expectException(\BadMethodCallException::class); + $results = $db->none('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); + } + /** + * based on issue #41 + * results() returns document instead of array #41 + */ + public function test_must_return_array_on_select_an_culomn_from_cache() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/saved', + 'cache' => true + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->email = 'john@example.com'; + $user->save(); + } + + $db->where('name','=','John')->andWhere('email','==','john@example.com')->select('email')->results(); + $result_from_cache = $db->where('name','=','John')->andWhere('email','==','john@example.com')->select('email')->results(); + + $this->assertCount(10,$result_from_cache); + $this->assertEquals(['email'=>'john@example.com'],$result_from_cache[0]); + $this->assertInternalType('array', $result_from_cache[0]); + $this->assertInternalType('string', $result_from_cache[0]['email']); + $db->flush(true); + } } diff --git a/tests/DocumentTest.php b/tests/DocumentTest.php index 45dc7f3..4f2856b 100644 --- a/tests/DocumentTest.php +++ b/tests/DocumentTest.php @@ -518,6 +518,31 @@ public function testFieldMethod() $db->flush(true); } + public function testFieldId() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases' + ]); + + $db->flush(true); + + $db->get('vegetables')->set(['broccoli'=>'27'])->save(); + + $expected = time(); + $actual = $db->get('vegetables')->field('__created_at'); + $this->assertEquals($expected, $actual); + + $actual = $db->get('vegetables')->field('__updated_at'); + $this->assertEquals($expected, $actual); + + $db->get('weather')->set(['cityname'=>'condition1'])->save(); + + $actual = $db->get('weather')->field('__id'); + $this->assertEquals('weather', $actual); + + $db->flush(true); + } + public function testNestedFieldMethod() { diff --git a/tests/DocumentYamlTest.php b/tests/DocumentYamlTest.php new file mode 100644 index 0000000..713c43d --- /dev/null +++ b/tests/DocumentYamlTest.php @@ -0,0 +1,601 @@ + __DIR__.'/databases', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + // save data + $doc = $db->get('test_save')->save(['key'=>'value']); + + // get saved data (put into array) + $val = $db->get('test_save'); + + // should equal... + $this->assertEquals('value', $val->key); + + #$db->flush(true); + } + + + //-------------------------------------------------------------------- + + + + + /** + * testDoesNotExist() + * + * TEST CASE: + * - Save document with data + * - Get the document + * - Check that the data is there and the document exist + * + */ + public function testDoesNotExist() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + // get saved data (put into array) + $doc = $db->get('doesexist')->save(['key'=>'value']); + + $this->assertEquals(true, $db->has('doesexist')); + + $this->assertEquals(false, $db->has('doesnotexist')); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + + + /** + * testSetIdGetId() + * + * TEST CASE: + * - Set and Get Id + * + */ + public function testSetIdGetId() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/data_rename', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + // save data + $doc = $db->get('name_1')->save(['key'=>'value']); + $this->assertEquals('name_1', $doc->getId()); + + // delete existing doc so its not duplicated + // object still exist, but file has been removed + $doc->delete(); + $this->assertEquals('name_1', $doc->getId()); + + // change id and save (new file is created) + $doc->setId('name_2')->save(); + $this->assertEquals('name_2', $doc->getId()); + } + + + //-------------------------------------------------------------------- + + + /** + * testSetValue() + * + * TEST CASE: + * - Using the set method, set the value in object ( DO NOT SAVE ) + * - Check that the properties are in the object (matching) + * + */ + public function testSetValue() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + // FIRST TEST + // use the set() method + $test1 = $db->get('test1')->set(['key'=>'value']); + + $this->assertEquals('value', $test1->key); + + + // SECOND TEST: + // use the property setter + $test2 = $db->get('test2'); + $test2->key = 'value'; + + $this->assertEquals('value', $test2->key); + + + // THIRD TEST (null test) + $test3 = $db->get('test3'); + + $this->assertEquals(null, $test3->key); + + } + + + //-------------------------------------------------------------------- + + + /** + * testIssetUnsetUnknown() + * + * TEST CASE: + * - Check if property isset + * - Unset property and see if it now returns null + * + */ + public function testIssetUnset() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $test = $db->get('test2'); + $test->key = 'value'; + + $this->assertEquals('value', $test->key); + + $this->assertEquals(1, isset($test->key)); + + unset($test->key); + + $this->assertEquals(null, ($test->key)); + + } + + + //-------------------------------------------------------------------- + + + public function testArraySetValueSave() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $test = $db->get('test'); + + $this->assertEquals('value', $test->key); + + $db->flush(true); + } + + + public function testPropertySetValueSave() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $test = $db->get('test'); + $test->key = 'value'; + $test->save(); + + $test = $db->get('test'); + + $this->assertEquals('value', $test->key); + + $db->flush(true); + } + + + public function testToArray() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $test = $db->get('test')->toArray(); + + $this->assertEquals('value', $test['key']); + + $db->flush(true); + } + + + public function testDelete() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $test = $db->get('test')->delete(); + + $this->assertEquals(true, $test); + + $db->flush(true); + } + + + public function testGetId() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $test = $db->get('test'); + + $this->assertEquals('test', $test->getId()); + + $db->flush(true); + } + + + public function testSetId() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $test = $db->get('test')->setId('newid'); + + $this->assertEquals('newid', $test->getId()); + + $db->flush(true); + } + + + + // DATE TESTS + //-------------------------------------------------------------------- + + public function testDates() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $createdAt = strtotime($db->get('test')->createdAt()); + $updatedAt = strtotime($db->get('test')->updatedAt()); + + $this->assertEquals(date('Y-m-d'), date('Y-m-d',$createdAt)); + $this->assertEquals(date('Y-m-d'), date('Y-m-d',$updatedAt)); + + $db->flush(true); + } + + + public function testFormatDates() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $createdAt = $db->get('test')->createdAt('Y-m-d'); + $updatedAt = $db->get('test')->updatedAt('Y-m-d'); + + $this->assertEquals(date('Y-m-d'), $createdAt); + $this->assertEquals(date('Y-m-d'), $updatedAt); + + $db->flush(true); + } + + + public function testNoFormatDates() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $createdAt = $db->get('test')->createdAt(false); + $updatedAt = $db->get('test')->updatedAt(false); + + $this->assertEquals(date('Y-m-d'), date('Y-m-d',$createdAt)); + $this->assertEquals(date('Y-m-d'), date('Y-m-d',$updatedAt)); + + $db->flush(true); + } + + + public function testMissingUpdatedDate() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('test')->set(['key'=>'value'])->save(); + + $setUpdatedAt = $db->get('test')->setUpdatedAt(null); + $setCreatedAt = $db->get('test')->setCreatedAt(null); + + $this->assertEquals(date('Y-m-d'), $setCreatedAt->updatedAt('Y-m-d')); + $this->assertEquals(date('Y-m-d'), $setUpdatedAt->updatedAt('Y-m-d')); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + public function testCustomFilter() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $u = []; + $u[] = [ + 'email' => 'email@email.com', + 'status' => 'blocked' + ]; + + $u[] = [ + 'email' => 'notblocked@email.com', + 'status' => 'enabled' + ]; + + $db->get('users')->set($u)->save(); + + $users = $db->get('users')->customFilter('data',function($item) { + return (($item['status']=='blocked') ? $item['email'] : false); + }); + + $this->assertEquals(1, count($users)); + $this->assertEquals('email@email.com', $users[0]); + + $db->flush(true); + } + + + public function testCustomFilterParam() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $u = []; + $u[] = [ + 'email' => 'email@email.com', + 'status' => 'blocked' + ]; + + $u[] = [ + 'email' => 'notblocked@email.com', + 'status' => 'enabled' + ]; + + $db->get('users')->set($u)->save(); + + $users = $db->get('users')->customFilter('data','enabled',function($item, $status) { + return (($item['status']==$status) ? $item['email'] : false); + }); + + $this->assertEquals(1, count($users)); + $this->assertEquals('notblocked@email.com', $users[0]); + + $db->flush(true); + } + + + + public function testCustomFilterParamIndex() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $u = []; + $u[] = [ + 'email' => 'enabled-email@email.com', + 'id' => '123', + 'status' => 'deactive' + ]; + + $u[] = [ + 'email' => 'enabled-email@email.com', + 'id' => '321', + 'status' => 'enabled' + ]; + + $db->get('users_test_custom')->save($u); + + $users = $db->get('users_test_custom')->filter('data','enabled',function($item, $status) { + return (($item['status']==$status) ? $item : false); + }); + + + $this->assertEquals(1, count($users)); + $this->assertEquals('enabled-email@email.com', $users[0]['email']); + + $db->flush(true); + } + + + public function testCustomFilterEmpty() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('customfilter_test')->set(['email'=>'time'])->save(); + + $users = $db->get('customfilter_test')->customFilter('email',function($item) { + return (($item['status']=='blocked') ? $item['email'] : false); + }); + + // should be empty array + $this->assertEquals([],$users); + + $db->flush(true); + } + + + public function testFieldMethod() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('user_test_email_1')->set(['email'=>'example@example.com'])->save(); + + $f = $db->get('user_test_email_1')->field('email'); + + $this->assertEquals('example@example.com', $f); + + $db->flush(true); + } + + + public function testNestedFieldMethod() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $db->get('user_test_email_2')->set([ + 'profile' => [ + 'email' => 'example@example.com' + ] + ])->save(); + + $f = $db->get('user_test_email_2')->field('profile.email'); + + $this->assertEquals('example@example.com', $f); + + $db->flush(true); + } + + + public function testBadNameException() + { + $this->expectException(\Exception::class); + + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases', + 'safe_filename' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $file = $db->get('^*bad_@name%$1#'); + + $db->flush(true); + } + + + public function testBadNameReplacement() + { + $badName = 'ti^@%mo!!~th*y-m_?a(ro%)is.&'; + $newName = Filesystem::validateName($badName, true); + + $this->assertEquals('timothy-m_arois', $newName); + } + + + public function testBadNameReplacementLong() + { + $badName = '1234567890123456789012345678901234567890123456789012345678901234'; + $newName = Filesystem::validateName($badName, true); + + $this->assertEquals(63, (strlen($newName)) ); + $this->assertEquals('123456789012345678901234567890123456789012345678901234567890123', $newName); + } + +} diff --git a/tests/FormatJsonTest.php b/tests/FormatJsonTest.php new file mode 100644 index 0000000..08e08fb --- /dev/null +++ b/tests/FormatJsonTest.php @@ -0,0 +1,41 @@ + 'timothy-m_arois', + 'email' => 'email@email.com' + ]; + + $json = Json::encode($data, false); + $testData = Json::decode($json); + + $this->assertEquals($data, $testData); + } + + public function testFormatJsonEncodeThrowsExceptionIfNotEncodable() + { + $this->expectException(EncodingException::class); + + $data = [ + 'invalid' => chr(193) + ]; + + Json::encode($data); + } + + public function testFormatJsonDecodeThrowsExceptionOnNotValidJson() + { + $this->expectException(DecodingException::class); + + $json = '{ invalid: "json"'; + + Json::decode($json); + } +} \ No newline at end of file diff --git a/tests/FormatYamlTest.php b/tests/FormatYamlTest.php new file mode 100644 index 0000000..b571c53 --- /dev/null +++ b/tests/FormatYamlTest.php @@ -0,0 +1,22 @@ + 'timothy-m_arois', + 'email' => 'email@email.com' + ]; + + $Yaml = Yaml::encode($data, false); + $testData = Yaml::decode($Yaml); + + $this->assertEquals($data, $testData); + } + +} \ No newline at end of file diff --git a/tests/QueryTest.php b/tests/QueryTest.php index c5a1db4..1589e4a 100644 --- a/tests/QueryTest.php +++ b/tests/QueryTest.php @@ -498,6 +498,98 @@ public function testSorting() } + public function prepareMultiOrderTestData() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__ . '/databases/users_orderbymult', + 'cache' => false + ]); + + $db->flush(true); + + $companies = ['Google'=>['CA', 150], 'Apple'=>['CA', 180], 'Microsoft'=>['WA', 120], 'Amex'=>['DC', 20], 'Hooli'=>['CA', 150], 'Amazon'=>['PA', 140]]; + + foreach ($companies as $company => $data) { + $doc = $db->get(uniqid()); + $doc->name = $company; + $doc->location = $data[0]; + $doc->rank['reviews'] = $data[1]; + $doc->status = 'enabled'; + $doc->save(); + } + + return $db; + } + + public function testMultiOrderbyOneOnly() + { + $db = $this->prepareMultiOrderTestData(); + + $test1 = $db->query()->orderBy('name', 'asc')->results(); + $actual = array_map(function($doc) { + return $doc['name']; + }, $test1); + $expected = ['Amazon', 'Amex', 'Apple', 'Google', 'Hooli', 'Microsoft']; + $this->assertEquals($expected, $actual); + + $db->flush(true); + } + + public function testMultiOrderbyTwoAscAsc() + { + $db = $this->prepareMultiOrderTestData(); + + $test1 = $db->query()->orderBy('location', 'ASC')->orderBy('name', 'ASC')->results(); + $actual = array_map(function($doc) { + return $doc['name']; + }, $test1); + $expected = ['Apple', 'Google', 'Hooli', 'Amex', 'Amazon', 'Microsoft']; + $this->assertEquals($expected, $actual); + + $db->flush(true); + } + + public function testMultiOrderbyTwoDescAsc() + { + $db = $this->prepareMultiOrderTestData(); + + $test2 = $db->query()->orderBy('location', 'desc')->orderBy('name', 'ASC')->results(); + $actual = array_map(function($doc) { + return $doc['name']; + }, $test2); + $expected = ['Microsoft', 'Amazon', 'Amex', 'Apple', 'Google', 'Hooli']; + $this->assertEquals($expected, $actual); + + $db->flush(true); + } + + public function testMultiOrderbyTwoAscDesc() + { + $db = $this->prepareMultiOrderTestData(); + + $test3 = $db->query()->orderBy('location', 'ASC')->orderBy('name', 'DESC')->results(); + $actual = array_map(function($doc) { + return $doc['name']; + }, $test3); + $expected = ['Hooli', 'Google', 'Apple', 'Amex', 'Amazon', 'Microsoft']; + $this->assertEquals($expected, $actual); + + $db->flush(true); + } + + public function testMultiOrderbyThree() + { + $db = $this->prepareMultiOrderTestData(); + + $test4 = $db->query()->orderBy('location', 'ASC')->orderBy('rank.reviews', 'ASC')->orderBy('name')->results(); + $actual = array_map(function($doc) { + return $doc['name']; + }, $test4); + $expected = ['Google', 'Hooli', 'Apple', 'Amex', 'Amazon', 'Microsoft']; + $this->assertEquals($expected, $actual); + + $db->flush(true); + } //-------------------------------------------------------------------- @@ -1045,4 +1137,128 @@ public function testQueryFromCacheAfterSave() $db->flush(true); } + + public function testDeleteWhereMatchItemsWithCustomFilter() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/deletefilter', + ]); + + $db->flush(true); + + $a=0; + while($a < 15) + { + $user=$db->get($a."username"); + $user->name = $a.'John'; + $user->email = 'john@example.com'; + $user->tags = ['php','developer','html5']; + + $user->save(); + $a++; + } + + $actual = $db->results(); + $this->assertCount(15,$actual); + + $r = $db->where('name','LIKE','john')->resultDocuments(); + $this->assertInstanceOf(Document::class, $r[0]); + + $db->where('name','LIKE','john')->delete(function($item){ + return $item->name=='0John'; + }); + + $actual = $db->where('name','LIKE','john')->resultDocuments(); + $this->assertCount(14,$actual); + + $db->where('name','LIKE','john')->delete(); + + $checkAgain = $db->where('name','LIKE','john')->resultDocuments(); + $this->assertCount(0,$checkAgain); + + $db->flush(true); + } + + + public function testSortByTimes() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/_testsort' + ]); + + $db->flush(true); + + // Create some docs with time in between to get different timestamps + $doc = $db->get('record1')->set(['name'=>'a'])->save(); + sleep(1); + $doc = $db->get('record2')->set(['name'=>'b'])->save(); + sleep(1); + $doc = $db->get('record3')->set(['name'=>'c'])->save(); + + $documents = $db->query()->orderBy('__created_at', 'DESC')->results(); + $expected = [ + ['name' => 'c'], + ['name' => 'b'], + ['name' => 'a'], + ]; + $this->assertEquals($expected, $documents); + + $documents = $db->query()->orderBy('__created_at', 'ASC')->results(); + $expected = [ + ['name' => 'a'], + ['name' => 'b'], + ['name' => 'c'], + ]; + $this->assertEquals($expected, $documents); + + $documents = $db->query()->orderBy('__updated_at', 'DESC')->results(); + $expected = [ + ['name' => 'c'], + ['name' => 'b'], + ['name' => 'a'], + ]; + $this->assertEquals($expected, $documents); + + $documents = $db->query()->orderBy('__updated_at', 'ASC')->results(); + $expected = [ + ['name' => 'a'], + ['name' => 'b'], + ['name' => 'c'], + ]; + $this->assertEquals($expected, $documents); + + $db->flush(true); + } + + /** + * testWhereInUsingDocId + * + * TEST CASE: + * - Testing the where "IN" operator when applied to the document ids to fetch + */ + public function testWhereInUsingDocId() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false + ]); + + $db->flush(true); + + $user1 = $db->get('obj1')->save(['name' => 'Bob']); + $user2 = $db->get('obj2')->save(['name' => 'Jenny']); + $user3 = $db->get('obj3')->save(['name' => 'Cyrus']); + + // Make sure it works with just one + $test1 = $db->query()->where('__id', '=', 'obj1')->first(); + $expected = ['name' => 'Bob']; + $this->assertEquals($expected, $test1); + + // Make sure it works with a list + $test2 = $db->query()->where('__id', 'IN', ['obj2', 'obj3'])->results(); + $expected = [['name' => 'Jenny'], ['name' => 'Cyrus']]; + $this->assertEquals($expected, $test2); + + $db->flush(true); + } } diff --git a/tests/QueryYamlTest.php b/tests/QueryYamlTest.php new file mode 100644 index 0000000..29efa27 --- /dev/null +++ b/tests/QueryYamlTest.php @@ -0,0 +1,1068 @@ + ["name" => "Roy"] ]) + * + * Comparisons used "=", "==", "===" + * + */ + public function testWhereCountAllEqualCompare() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + // FIRST TEST + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->contact['email'] = 'john@john.com'; + $user->save(); + } + + $count = $db->count(); + + // standard matches + $query1 = $db->query()->where('name','=','John')->results(); + $query2 = $db->query()->where('name','==','John')->results(); + $query3 = $db->query()->where('name','===','John')->results(); + $query4 = $db->query()->where(['name' => 'John'])->results(); + + // testing nested level + $query5 = $db->query()->where('contact.email','=','john@john.com')->results(); + + $this->assertEquals($count, count($query1)); + $this->assertEquals($count, count($query2)); + $this->assertEquals($count, count($query3)); + $this->assertEquals($count, count($query4)); + $this->assertEquals($count, count($query5)); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testWhereCountAllNotEqualCompare() + * + * TEST CASE: + * - Creates 10 items in database with ["name" = "John"] + * - Counts the total items in the database + * + * FIRST TEST: + * - Compares the number of items in db to the number items the query found + * - Should match "10" + * + * SECOND TEST: + * - Secondary Tests to find items that DO NOT match "John" + * - Should match "0" + * + * Comparisons used "!=", "!==", "NOT" + * + */ + public function testWhereCountAllNotEqualCompare() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->save(); + } + + $count = $db->count(); + + $query1 = $db->query()->where('name','!=','Max')->results(); + $query2 = $db->query()->where('name','!==','Smith')->results(); + $query3 = $db->query()->where('name','NOT','Jason')->results(); + + $query4 = $db->query()->where('name','!=','John')->results(); + $query5 = $db->query()->where('name','!==','John')->results(); + $query6 = $db->query()->where('name','NOT','John')->results(); + + $this->assertEquals($count, count($query1)); + $this->assertEquals($count, count($query2)); + $this->assertEquals($count, count($query3)); + + $this->assertEquals(0, count($query4)); + $this->assertEquals(0, count($query5)); + $this->assertEquals(0, count($query6)); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + + /** + * testWhereCountAllGreaterLessCompare() + * + * TEST CASE: + * - Creates 10 items in database with ["pages" = 5] + * - Counts the total items in the database + * + * FIRST TEST: Greater Than + * - Should match "10" + * + * SECOND TEST: Less Than + * - Should match "10" + * + * THIRD TEST: Less/Greater than "no match" + * - Should match "0" + * + * Comparisons used ">=", ">", "<=", "<" + * + */ + public function testWhereCountAllGreaterLessCompare() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->index = $x; + $user->index2 = mt_rand(1,2); + $user->pages = 5; + $user->save(); + } + + $count = $db->count(); + + $queryIndex = $db->query() + ->where('pages','>','4') + ->where('index','=','1') + ->where('index2','=','2') + ->results(); + + // print_r($queryIndex); + + // FIRST TEST + $query1 = $db->query()->where('pages','>','4')->results(); + $query2 = $db->query()->where('pages','>=','5')->results(); + + // SECOND TEST + $query3 = $db->query()->where('pages','<','6')->results(); + $query4 = $db->query()->where('pages','<=','5')->results(); + + // THIRD TEST + $query5 = $db->query()->where('pages','>','5')->results(); + $query6 = $db->query()->where('pages','<','5')->results(); + + $this->assertEquals($count, count($query1)); + $this->assertEquals($count, count($query2)); + $this->assertEquals($count, count($query3)); + $this->assertEquals($count, count($query4)); + $this->assertEquals(0, count($query5)); + $this->assertEquals(0, count($query6)); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testWhereLike() + * + * TEST CASE: + * - Creates a bunch of items with the same information + * - Creates one item with different info (finding the needle) + * + * Comparisons used "LIKE", "NOT LIKE", "==" + * + */ + public function testWhereLike() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_like', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John Ellot'; + $user->email = 'johnellot@example.com'; + $user->save(); + } + + // the needle + $user = $db->get(uniqid()); + $user->name = 'Timothy Marois'; + $user->email = 'timothymarois@email.com'; + $user->save(); + + $count = $db->count(); + + // should return exact match + $query1 = $db->query()->where('name','==','Timothy Marois')->results(); + // this should fail to find anything + $query2 = $db->query()->where('name','==','timothy marois')->results(); + + // this should find match with regex loose expression + $query3 = $db->query()->where('name','LIKE','timothy marois')->results(); + // this should find match by looking for loose expression on "timothy" + $query4 = $db->query()->where('name','LIKE','timothy')->results(); + // this should find all teh users that have an email address using "@email.com" + $query5 = $db->query()->where('email','LIKE','@email.com')->results(); + // this should return 1 as its looking at only the emails not like "@example.com" + $query6 = $db->query()->where('email','NOT LIKE','@example.com')->results(); + + $this->assertEquals(1, count($query1)); + $this->assertEquals(0, count($query2)); + $this->assertEquals(1, count($query3)); + $this->assertEquals(1, count($query4)); + $this->assertEquals(1, count($query5)); + $this->assertEquals(1, count($query6)); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testWhereRegex() + * + * TEST CASE: + * - Testing the use of regex + * + * Comparisons used "REGEX" + * + */ + public function testWhereRegex() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_regex', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John Ellot'; + $user->email = 'johnellot@example.com'; + $user->save(); + } + + // the needle (with bad email) + $user = $db->get(uniqid()); + $user->name = 'Leo Ash'; + $user->email = 'example@emailcom'; + $user->save(); + + $count = $db->count(); + + // this should find match with regex loose expression + $query1 = $db->query()->where('name','REGEX','/leo/i')->results(); + $query2 = $db->query()->where('name','REGEX','/leo\sash/i')->results(); + + // finds all the emails in a field + $query3 = $db->query()->where('email','REGEX','/[a-z\d._%+-]+@[a-z\d.-]+\.[a-z]{2,4}\b/i')->results(); + + $this->assertEquals(1, count($query1)); + $this->assertEquals(1, count($query2)); + $this->assertEquals(10, count($query3)); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testLimitOffset() + * + * TEST CASE: + * - Creates 6 company profiles + * - Queries them and limits the results + * + * + */ + public function testLimitOffset() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_orderby', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $companies = ['Google'=>150, 'Apple'=>150, 'Microsoft'=>150, 'Amex'=>150, 'Hooli'=>20, 'Amazon'=>10]; + + foreach($companies as $company=>$rank) + { + $user = $db->get(uniqid()); + $user->name = $company; + $user->rank = $rank; + $user->save(); + } + + // test that it limits the results to "2" (total query pulls "5") + $test1 = $db->query()->where('rank','=',150)->limit(2)->results(); + + // test the offset, no limit, should be 3 (total query pulls "5") + $test2 = $db->query()->where('rank','=',150)->limit(0,1)->results(); + + // test that the offset takes off the first array (should return "apple", not "google") + $test3 = $db->query()->where('rank','=',150)->limit(1,1)->results(); + + $this->assertEquals(2, (count($test1))); + $this->assertEquals(3, (count($test2))); + $this->assertEquals('Apple', $test3[0]['name']); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + /** + * testSorting() + * + * TEST CASE: + * - Creates 6 company profiles + * - Sorts them by DESC/ASC + * + * + */ + /*public function testSelectQuery() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_select', + 'cache' => false, 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $users = [ + 'JR MM'=> + [ + 'about'=>'this is a long about me section', + 'email'=>'jrmm@email.com', + 'profile' => [ + 'website' => 'jr.com' + ] + ], + 'Tim'=> + [ + 'about'=>'this is a section about tim', + 'email'=>'timothymarois@email.com', + 'profile' => [ + 'website' => 'timothymarois.com' + ] + ] + ]; + foreach($users as $name=>$info) + { + $user = $db->get(uniqid()); + $user->name = $name; + $user->about = $info['about']; + $user->email = $info['email']; + $user->profile = $info['profile']; + $user->save(); + } + + // return the "name" (selecting only 1 item) + $test1 = $db->query()->select('name')->results(); + $this->assertEquals(['JR MM','Tim'], [$test1[0]['name'],$test1[1]['name']]); + // count how many items are in array (should be only 1 each) + $this->assertEquals([1,1], [count($test1[0]),count($test1[1])]); + + // return the "name" (selecting only 1 item) + $test2 = $db->query()->select('name,email')->first(); + $this->assertEquals(['JR MM','jrmm@email.com'], [$test2['name'],$test2['email']]); + + // select using arrays instead of strings + $test3 = $db->query()->select(['name','email'])->first(); + $this->assertEquals(['JR MM','jrmm@email.com'], [$test3['name'],$test3['email']]); + + // return the "name" (selecting only 1 item) + // currently DOES not work with nested.. + // $test3 = $db->query()->select('name,profile.website')->first(); + + // print_r($test3); + // $this->assertEquals(['JR MM','jrmm@email.com'], [$test2['name'],$test2['email']]); + }*/ + + + /** + * testSorting() + * + * TEST CASE: + * - Creates 6 company profiles + * - Sorts them by DESC/ASC + * + * + */ + public function testSorting() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_orderby', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $companies = ['Google'=>150, 'Apple'=>180, 'Microsoft'=>120, 'Amex'=>20, 'Hooli'=>50, 'Amazon'=>140]; + + foreach($companies as $company=>$rank) + { + $user = $db->get(uniqid()); + $user->name = $company; + $user->rank['reviews'] = $rank; + $user->status = 'enabled'; + $user->save(); + } + + // test that they are ordered by name ASC (check first, second, and last) + $test1 = $db->query()->where('status','=','enabled')->orderBy('name', 'ASC')->results(); + $this->assertEquals(['first'=>'Amazon','second'=>'Amex','last'=>'Microsoft'], ['first'=>$test1[0]['name'],'second'=>$test1[1]['name'],'last'=>$test1[5]['name']]); + + // test that they are ordered by name ASC (check first, second, and last) + $test2 = $db->query()->where('status','=','enabled')->limit(3)->orderBy('name', 'ASC')->results(); + $this->assertEquals(['Amazon','Amex','Apple'], [$test2[0]['name'],$test2[1]['name'],$test2[2]['name']]); + + // test that they are ordered by name DESC (check first, second, and last) + $test3 = $db->query()->where('status','=','enabled')->limit(3)->orderBy('name', 'DESC')->results(); + $this->assertEquals(['Microsoft','Hooli','Google'], [$test3[0]['name'],$test3[1]['name'],$test3[2]['name']]); + + // test that they are ordered by rank nested [reviews] DESC + $test4 = $db->query()->where('status','=','enabled')->limit(3)->orderBy('rank.reviews', 'DESC')->results(); + $this->assertEquals(['Apple','Google','Amazon'], [$test4[0]['name'],$test4[1]['name'],$test4[2]['name']]); + + // test that they are ordered by rank nested [reviews] ASC + $test5 = $db->query()->where('status','=','enabled')->limit(3)->orderBy('rank.reviews', 'ASC')->results(); + $this->assertEquals(['Amex','Hooli','Microsoft'], [$test5[0]['name'],$test5[1]['name'],$test5[2]['name']]); + + $db->flush(true); + + $companies = ['Google 9', 'Google 3', 'Google 10', 'Google 1', 'Google 2', 'Google 7']; + + foreach($companies as $company) + { + $user = $db->get(uniqid()); + $user->name = $company; + $user->save(); + } + + // order the results ASC (but inject numbers into strings) + $test6 = $db->query()->limit(3)->orderBy('name', 'ASC')->results(); + $this->assertEquals(['Google 1','Google 2','Google 3'], [$test6[0]['name'],$test6[1]['name'],$test6[2]['name']]); + + // order the results DESC (but inject numbers into strings) + $test6 = $db->query()->limit(3)->orderBy('name', 'DESC')->results(); + $this->assertEquals(['Google 10','Google 9','Google 7'], [$test6[0]['name'],$test6[1]['name'],$test6[2]['name']]); + + $db->flush(true); + + } + + + //-------------------------------------------------------------------- + + + /** + * testWhereIn() + * + * TEST CASE: + * - Testing the Where "IN" operator + * + * + */ + public function testWhereIn() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $companies = [ + 'Google'=>[ + 'tags' => [ + 'search','display' + ] + ], + 'Facebook'=>[ + 'tags' => [ + 'social','network' + ] + ], + 'Microsoft'=>[ + 'tags' => [ + 'windows','xbox','search' + ] + ] + ]; + + foreach($companies as $company=>$tags) + { + $user = $db->get(uniqid()); + $user->name = $company; + $user->tags = $tags['tags']; + $user->save(); + } + + + // test that they are ordered by name ASC (check first, second, and last) + $test1 = $db->query()->where('tags','IN','display')->first(); + $test2 = $db->query()->where('tags','IN','network')->first(); + $test3 = $db->query()->where('tags','IN','windows')->first(); + $test4 = $db->query()->where('tags','IN','search')->results(); + + // testing the object return boolean argument + $test5 = $db->query()->where('tags','IN','search')->results(false); + $test6 = $db->query()->where('tags','IN','search')->results(true); + + // make sure the results equal the right names + $this->assertEquals('Google', $test1['name']); + $this->assertEquals('Facebook', $test2['name']); + $this->assertEquals('Microsoft', $test3['name']); + + // check if the method createdAt() exists or not based on the argument boolean + $this->assertEquals(true, method_exists($test5[0], 'createdAt')); + $this->assertEquals(false, method_exists($test6[0], 'createdAt')); + + // check if the results = 2 + $this->assertEquals(2, count($test4)); + + // this will test the IN clause if name matches one of these + $test3 = $db->query()->where('name','IN',['Google','Facebook'])->results(); + + $this->assertEquals(2, count($test3)); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testWithoutWhere() + * + * TEST CASE: + * - Run queries without using Where() + * - This will run a findAll() + * + * + */ + public function testWithoutWhere() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $companies = [ + 'Google'=>[ + 'site' => 'google.com', + 'type' => 'search' + ], + 'Yahoo'=>[ + 'site' => 'yahoo.com', + 'type' => 'search' + ], + 'Facebook'=>[ + 'site' => 'facebook.com', + 'type' => 'social' + ] + ]; + + foreach($companies as $company=>$options) + { + $user = $db->get(uniqid()); + $user->name = $company; + $user->type = $options['type']; + $user->site = $options['site']; + $user->save(); + } + + + // test that they are ordered by name ASC (check first, second, and last) + $test1 = $db->query()->orderBy('name', 'DESC')->first(); + $test2 = $db->query()->orderBy('name', 'DESC')->first( false ); + $test3 = $db->query()->orderBy('name', 'DESC')->first( true ); + + $this->assertEquals(true, method_exists($test2, 'createdAt')); + $this->assertEquals(false, method_exists($test3, 'createdAt')); + + $this->assertEquals('Yahoo', $test1['name']); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testingBadOperator() + * + * BAD TEST CASE: + * - Tries to run a query with an operator that does not exist. + * + */ + public function testingBadOperator() + { + $this->expectException(\InvalidArgumentException::class); + + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + // FIRST TEST + $db->flush(true); + + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->save(); + + // standard matches + $query = $db->query()->where('name','&','John')->results(); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testingBadField() + * + * BAD TEST CASE: + * - Tries to run a query with a blank field + * + */ + public function testingBadField() + { + $this->expectException(\InvalidArgumentException::class); + + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + // FIRST TEST + $db->flush(true); + + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->save(); + + // standard matches + $query = $db->query()->where('','=','John')->results(); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testingMissingQueryArguments() + * + * BAD TEST CASE: + * - Tries to run a query with just a string arg + * + */ + public function testingMissingQueryArguments() + { + $this->expectException(\InvalidArgumentException::class); + + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->save(); + + // standard matches + $query = $db->query()->where('John')->results(); + + $db->flush(true); + } + + + + //-------------------------------------------------------------------- + + + /** + * testUserNameQuery() + * + * + */ + public function testUserNameQuery() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_names', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John '.$x; + $user->contact['email'] = 'john@john.com'; + $user->save(); + } + + $userAccountFirst = $db->query()->orderBy('name', 'DESC')->first(); + $userAccountLast = $db->query()->orderBy('name', 'DESC')->last(); + + // testing "first" method and sorting + $this->assertEquals('John 10', $userAccountFirst['name']); + + // testing "last" method and sorting + $this->assertEquals('John 1', $userAccountLast['name']); + + $db->flush(true); + } + + + //-------------------------------------------------------------------- + + + /** + * testQueryCount() + * + * + */ + public function testQueryCount() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_qcounter', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John '.$x; + $user->contact['email'] = 'john@john.com'; + $user->save(); + } + + $userCount1 = $db->query()->count(); + $userCount2 = $db->query()->where('name','==','Nothing')->count(); + $userCount3 = $db->query()->where('name','==','John 2')->count(); + + // find users that have the number 1 in their name (should be 2) + // John 1 and John 10 + $userCount4 = $db->query()->where('name','REGEX','/1/')->count(); + + // should = 10 documents + $this->assertEquals(10, $userCount1); + $this->assertEquals(0, $userCount2); + $this->assertEquals(1, $userCount3); + $this->assertEquals(2, $userCount4); + + $db->flush(true); + } + + + + //-------------------------------------------------------------------- + + + + + + public function testWhereQueryWhereCount() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->email = 'john@example.com'; + $user->criteria = [ + 'label' => 'lead' + ]; + + $user->save(); + } + + $results = $db->query() + ->where('name','=','John') + ->andWhere('email','==','john@example.com') + ->results(); + + $this->assertEquals($db->count(), count($results)); + + $db->flush(true); + $db->flushCache(); + } + + + public function testWhereQueryFindNameCount() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_2', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + + if ($x < 6) + { + $user->name = 'John'; + } + else + { + $user->name = 'Max'; + } + + $user->save(); + } + + $results = $db->query() + ->where('name','=','John') + ->results(); + + $this->assertEquals(5, count($results)); + + #$db->flush(true); + $db->flushCache(); + } + + + + public function testOrWhereQueryCount() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'cache' => false, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + + if ($x < 6) + { + $user->name = 'John'; + } + else + { + $user->name = 'Max'; + } + + $user->save(); + } + + $results = $db->query() + ->where('name','=','John') + ->orWhere('name','=','Max') + ->results(); + + $this->assertEquals($db->count(), count($results)); + + $db->flush(true); + $db->flushCache(); + } + + + public function testWhereQueryFromCache() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/users_1', + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->email = 'john@example.com'; + $user->save(); + } + + $results = $db->query() + ->where('name','=','John') + ->andWhere('email','==','john@example.com') + ->resultDocuments(); + + $result_from_cache = $db->query() + ->where('name','=','John') + ->andWhere('email','==','john@example.com') + ->resultDocuments(); + + $this->assertEquals(10, count($results)); + $this->assertEquals(true, ($result_from_cache[0]->isCache())); + + $db->flush(true); + } + + + public function testQueryFromCacheAfterDelete() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/deleted', + 'cache' => true, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->email = 'john@example.com'; + $user->save(); + } + + $results = $db->query()->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); + $result_from_cache = $db->query()->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); + + $this->assertEquals(10, count($results)); + $this->assertEquals(true, ($result_from_cache[0]->isCache())); + + $id = $result_from_cache[0]->getId(); + $id2 = $result_from_cache[1]->getId(); + + // delete the file + $result_from_cache[0]->delete(); + + $results = $db->query() + ->where('name','=','John') + ->andWhere('email','==','john@example.com') + ->resultDocuments(); + + $this->assertEquals($id2, $results[0]->getId()); + + $db->flush(true); + } + + + + public function testQueryFromCacheAfterSave() + { + $db = new \Filebase\Database([ + 'dir' => __DIR__.'/databases/saved', + 'cache' => true, + 'format' => \Filebase\Format\Yaml::class + ]); + + $db->flush(true); + + for ($x = 1; $x <= 10; $x++) + { + $user = $db->get(uniqid()); + $user->name = 'John'; + $user->email = 'john@example.com'; + $user->save(); + } + + $results = $db->query()->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); + $result_from_cache = $db->query()->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); + + $this->assertEquals(10, count($results)); + $this->assertEquals(true, ($result_from_cache[0]->isCache())); + + $id = $result_from_cache[0]->getId(); + $id2 = $result_from_cache[1]->getId(); + + // Change the name + $result_from_cache[0]->name = 'Tim'; + $result_from_cache[0]->save(); + + $results = $db->query() + ->where('name','=','John') + ->andWhere('email','==','john@example.com') + ->resultDocuments(); + + $this->assertEquals($id2, $results[0]->getId()); + $this->assertEquals('John', $results[0]->name); + + $db->flush(true); + } + +}