├── tests ├── bin │ └── coveralls.phar ├── FormatYamlTest.php ├── FormatJsonTest.php ├── BackupTest.php ├── BackupYamlTest.php ├── ValidationTest.php ├── DatabaseTest.php ├── DocumentTest.php ├── DocumentYamlTest.php └── QueryYamlTest.php ├── src ├── Format │ ├── DecodingException.php │ ├── EncodingException.php │ ├── FormatInterface.php │ ├── FormatException.php │ ├── Yaml.php │ └── Json.php ├── Filesystem │ ├── FilesystemException.php │ ├── ReadingException.php │ └── SavingException.php ├── SortLogic.php ├── Predicate.php ├── Filesystem.php ├── Cache.php ├── Config.php ├── Validate.php ├── Backup.php ├── Query.php ├── QueryLogic.php ├── Document.php └── Database.php ├── .travis.yml ├── phpunit.xml.dist ├── composer.json ├── LICENSE ├── phpunit5 ├── .gitignore ├── CHANGELOG.md └── README.md /tests/bin/coveralls.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmarois/Filebase/HEAD/tests/bin/coveralls.phar -------------------------------------------------------------------------------- /src/Format/DecodingException.php: -------------------------------------------------------------------------------- 1 | inputData = $inputData; 11 | } 12 | 13 | public function getInputData() 14 | { 15 | return $this->inputData; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /tests/FormatYamlTest.php: -------------------------------------------------------------------------------- 1 | 'timothy-m_arois', 13 | 'email' => 'email@email.com' 14 | ]; 15 | 16 | $Yaml = Yaml::encode($data, false); 17 | $testData = Yaml::decode($Yaml); 18 | 19 | $this->assertEquals($data, $testData); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | ./tests 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "A Simple but Powerful Flat File Database Storage.", 3 | "keywords": ["filebase", "key-value","file-files", "flat", "file", "database", "document", "flat-file", "serverless"], 4 | "name": "tmarois/filebase", 5 | "homepage": "https://github.com/tmarois/Filebase", 6 | "type": "package", 7 | "licence": "MIT", 8 | 9 | "authors": [ 10 | { 11 | "name": "Timothy Marois", 12 | "email": "timothymarois@gmail.com" 13 | } 14 | ], 15 | 16 | "require": { 17 | "php": ">=5.6" 18 | }, 19 | 20 | "require-dev": { 21 | "php-coveralls/php-coveralls": "^2.0", 22 | "phpunit/phpunit": "5.*", 23 | "symfony/yaml": "^3.4" 24 | }, 25 | 26 | "suggest": { 27 | "symfony/yaml": "Allows Yaml format" 28 | }, 29 | 30 | "autoload": { 31 | "psr-4": { 32 | "Filebase\\": "src/" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Format/Yaml.php: -------------------------------------------------------------------------------- 1 | 'timothy-m_arois', 13 | 'email' => 'email@email.com' 14 | ]; 15 | 16 | $json = Json::encode($data, false); 17 | $testData = Json::decode($json); 18 | 19 | $this->assertEquals($data, $testData); 20 | } 21 | 22 | public function testFormatJsonEncodeThrowsExceptionIfNotEncodable() 23 | { 24 | $this->expectException(EncodingException::class); 25 | 26 | $data = [ 27 | 'invalid' => chr(193) 28 | ]; 29 | 30 | Json::encode($data); 31 | } 32 | 33 | public function testFormatJsonDecodeThrowsExceptionOnNotValidJson() 34 | { 35 | $this->expectException(DecodingException::class); 36 | 37 | $json = '{ invalid: "json"'; 38 | 39 | Json::decode($json); 40 | } 41 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Timothy Marois 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /phpunit5: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | if (version_compare('5.6.0', PHP_VERSION, '>')) { 13 | fwrite( 14 | STDERR, 15 | sprintf( 16 | 'This version of PHPUnit is supported on PHP 5.6 and PHP 7.0.' . PHP_EOL . 17 | 'You are using PHP %s (%s).' . PHP_EOL, 18 | PHP_VERSION, 19 | PHP_BINARY 20 | ) 21 | ); 22 | 23 | die(1); 24 | } 25 | 26 | if (!ini_get('date.timezone')) { 27 | ini_set('date.timezone', 'UTC'); 28 | } 29 | 30 | foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/../vendor/autoload.php', __DIR__ . '/vendor/autoload.php') as $file) { 31 | if (file_exists($file)) { 32 | define('PHPUNIT_COMPOSER_INSTALL', $file); 33 | 34 | break; 35 | } 36 | } 37 | 38 | unset($file); 39 | 40 | if (!defined('PHPUNIT_COMPOSER_INSTALL')) { 41 | fwrite(STDERR, 42 | 'You need to set up the project dependencies using the following commands:' . PHP_EOL . 43 | 'wget http://getcomposer.org/composer.phar' . PHP_EOL . 44 | 'php composer.phar install' . PHP_EOL 45 | ); 46 | 47 | die(1); 48 | } 49 | 50 | require PHPUNIT_COMPOSER_INSTALL; 51 | 52 | PHPUnit_TextUI_Command::main(); 53 | -------------------------------------------------------------------------------- /src/Format/Json.php: -------------------------------------------------------------------------------- 1 | orderBy = $orderBy; 42 | $this->sortDirection = $sortDirection; 43 | $this->index = $index; 44 | } 45 | 46 | /** 47 | * Sorting callback 48 | * 49 | * @param Document $docA 50 | * @param Document $docB 51 | * @return return int (-1, 0, 1) 52 | */ 53 | public function sort($docA, $docB) 54 | { 55 | $propA = $docA->field($this->orderBy[$this->index]); 56 | $propB = $docB->field($this->orderBy[$this->index]); 57 | 58 | if (strnatcasecmp($propA, $propB) == 0) 59 | { 60 | if (!isset($this->orderBy[$this->index + 1])) 61 | { 62 | return 0; 63 | } 64 | 65 | // If they match and there are multiple orderBys, go deeper (recurse) 66 | $sortlogic = new self($this->orderBy, $this->sortDirection, $this->index + 1); 67 | return $sortlogic->sort($docA, $docB); 68 | } 69 | 70 | if ($this->sortDirection[$this->index] == 'DESC') 71 | { 72 | return strnatcasecmp($propB, $propA); 73 | } 74 | else 75 | { 76 | return strnatcasecmp($propA, $propB); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Predicate.php: -------------------------------------------------------------------------------- 1 | ', 19 | '<', 20 | '>=', 21 | '<=', 22 | 'IN', 23 | 'NOT', 24 | 'LIKE', 25 | 'NOT LIKE', 26 | 'REGEX' 27 | ]; 28 | 29 | 30 | /** 31 | * $predicates 32 | * 33 | * Query clauses 34 | */ 35 | protected $predicates = []; 36 | 37 | /** 38 | * add 39 | * 40 | */ 41 | public function add($logic,$arg) 42 | { 43 | if (!is_array($arg)) 44 | { 45 | throw new \InvalidArgumentException('Predicate Error: argument passed must be type of array'); 46 | } 47 | 48 | if (count($arg) == 1) 49 | { 50 | if (isset($arg[0]) && is_array($arg[0])) 51 | { 52 | foreach($arg[0] as $key => $value) 53 | { 54 | if ($value == '') continue; 55 | 56 | $arg = $this->formatWhere($key, $value); 57 | } 58 | } 59 | } 60 | 61 | if (count($arg) != 3) 62 | { 63 | throw new \InvalidArgumentException('Predicate Error: Must have 3 arguments passed - '.count($arg).' given'); 64 | } 65 | 66 | if (!in_array($arg[1], $this->allowed_operators)) 67 | { 68 | throw new \InvalidArgumentException('Predicate Error: Unknown Operator '.$arg[1]); 69 | } 70 | 71 | $arg[0] = trim($arg[0]); 72 | 73 | if ($arg[0] == '') 74 | { 75 | throw new \InvalidArgumentException('Field name can not be empty'); 76 | } 77 | 78 | $this->predicates[$logic][] = $arg; 79 | } 80 | 81 | /** 82 | * formatWhere 83 | * 84 | */ 85 | protected function formatWhere($key, $value) 86 | { 87 | return [$key,'==',$value]; 88 | } 89 | 90 | /** 91 | * get 92 | * 93 | */ 94 | public function get() 95 | { 96 | return array_filter($this->predicates); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Filesystem.php: -------------------------------------------------------------------------------- 1 | database = $database; 36 | 37 | $this->cache_database = new \Filebase\Database([ 38 | 'dir' => $this->database->getConfig()->dir.'/__cache', 39 | 'cache' => false, 40 | 'pretty' => false 41 | ]); 42 | } 43 | 44 | /** 45 | * setKey() 46 | * 47 | * This key is used to identify the cache 48 | * and know how to call the cache again 49 | * 50 | */ 51 | public function setKey($key) 52 | { 53 | $this->key = md5($key); 54 | } 55 | 56 | /** 57 | * getKey() 58 | * 59 | */ 60 | public function getKey() 61 | { 62 | return $this->key; 63 | } 64 | 65 | /** 66 | * flush() 67 | * 68 | */ 69 | public function flush() 70 | { 71 | $this->cache_database->flush(true); 72 | } 73 | 74 | /** 75 | * expired() 76 | * 77 | * @param $time (date format) 78 | * @return bool (true/false) 79 | */ 80 | public function expired($time) 81 | { 82 | if ( (strtotime($time)+$this->database->getConfig()->cache_expires) > time() ) 83 | { 84 | return false; 85 | } 86 | 87 | return true; 88 | } 89 | 90 | /** 91 | * getDocuments() 92 | * 93 | */ 94 | public function getDocuments($documents) 95 | { 96 | $d = []; 97 | foreach($documents as $document) 98 | { 99 | $d[] = $this->database->get($document)->setFromCache(true); 100 | } 101 | 102 | return $d; 103 | } 104 | 105 | /** 106 | * get() 107 | * 108 | */ 109 | public function get() 110 | { 111 | if (!$this->getKey()) 112 | { 113 | throw new \Exception('You must supply a cache key using setKey to get cache data.'); 114 | } 115 | 116 | $cache_doc = $this->cache_database->get( $this->getKey() ); 117 | 118 | if (!$cache_doc->toArray()) 119 | { 120 | return false; 121 | } 122 | 123 | if ( $this->expired( $cache_doc->updatedAt() ) ) 124 | { 125 | return false; 126 | } 127 | 128 | return $this->getDocuments($cache_doc->toArray()); 129 | } 130 | 131 | /** 132 | * store() 133 | * 134 | */ 135 | public function store($data) 136 | { 137 | if (!$this->getKey()) 138 | { 139 | throw new \Exception('You must supply a cache key using setKey to store cache data.'); 140 | } 141 | 142 | return $this->cache_database->get( $this->getKey() )->set($data)->save(); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | $value) 88 | { 89 | $this->{$key} = $value; 90 | } 91 | 92 | // if "backupLocation" is not set, let's set one automatically 93 | if (!isset($config['backupLocation'])) 94 | { 95 | $this->backupLocation = $this->dir.'/backups'; 96 | } 97 | 98 | $this->validateFormatClass(); 99 | } 100 | 101 | /** 102 | * format 103 | * 104 | * kind of a quick fix since we are using static methods, 105 | * currently need to instantiate teh class to check instanceof why?? 106 | * 107 | * Checks the format of the database being accessed 108 | */ 109 | protected function validateFormatClass() 110 | { 111 | if (!class_exists($this->format)) 112 | { 113 | throw new \Exception('Filebase Error: Missing format class in config.'); 114 | } 115 | 116 | // instantiate the format class 117 | $format_class = new $this->format; 118 | 119 | // check now if that class is part of our interface 120 | if (!$format_class instanceof \Filebase\Format\FormatInterface) 121 | { 122 | throw new \Exception('Filebase Error: Format Class must be an instance of Filebase\Format\FormatInterface'); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Validate.php: -------------------------------------------------------------------------------- 1 | toArray(); 16 | 17 | self::validateLoop($document,$object,self::getValidateRules($object)); 18 | 19 | return true; 20 | } 21 | 22 | /** 23 | * getValidateRules 24 | * 25 | * @param \Filebase\Document 26 | * @return database->config 27 | */ 28 | public static function getValidateRules(Document $object) 29 | { 30 | return $object->getDatabase()->getConfig()->validate; 31 | } 32 | 33 | /** 34 | * validateLoop 35 | * 36 | * Loops over the document and finds invaild data 37 | * Throws an exception if found, otherwise returns nothing 38 | * 39 | * @param array (of document data) 40 | * @return vold 41 | */ 42 | protected static function validateLoop($document,$object,$rules) 43 | { 44 | foreach($rules as $key => $rule) 45 | { 46 | if ( (!isset($rule['valid.type']) ) && isset($document[$key])) 47 | { 48 | self::validateLoop($document[$key],$object,$rules[$key]); 49 | 50 | continue; 51 | } 52 | 53 | self::validateRules($document,$key,$rules[$key],$object); 54 | } 55 | } 56 | 57 | /** 58 | * validateRules 59 | * 60 | * Checks "valid.type" 61 | * Checks "valid.requred" 62 | * 63 | * Throws exception error if matches are not met. 64 | * 65 | * @return \Filebase\Document Object 66 | */ 67 | protected static function validateRules($document,$key,$rules,$object) 68 | { 69 | // checks variable type 70 | if (isset($document[$key],$rules['valid.type'])) 71 | { 72 | if (!in_array($rules['valid.type'],['string','str','int','integer','arr','array'])) 73 | { 74 | throw new \Exception('Validation Failed: Invaild Property Type "'.$rules['valid.type'].'"'); 75 | } 76 | 77 | if (!self::checkType($document[$key],$rules['valid.type'])) 78 | { 79 | throw new \Exception('Validation Failed setting variable on '.$object->getId().' - ['.$key.'] does not match type "'.$rules['valid.type'].'"'); 80 | } 81 | } 82 | 83 | // check if variable is required 84 | if (isset($rules['valid.required']) && $rules['valid.required']===true) 85 | { 86 | if (!isset($document[$key])) 87 | { 88 | throw new \Exception('Validation Failed setting variable on '.$object->getId().' - ['.$key.'] is required'); 89 | } 90 | } 91 | 92 | return $object; 93 | } 94 | 95 | /** 96 | * checkType 97 | * 98 | * Checks type of variable and sees if it matches 99 | * 100 | * @return boolean (true or false) 101 | */ 102 | protected static function checkType($variable, $type) 103 | { 104 | switch($type) 105 | { 106 | case 'string': 107 | case 'str': 108 | if (is_string($variable)) 109 | { 110 | return true; 111 | } 112 | 113 | break; 114 | 115 | case 'integer': 116 | case 'int': 117 | if (is_integer($variable)) 118 | { 119 | return true; 120 | } 121 | 122 | break; 123 | 124 | case 'array': 125 | case 'arr': 126 | if (is_array($variable)) 127 | { 128 | return true; 129 | } 130 | 131 | break; 132 | 133 | default: 134 | return false; 135 | } 136 | 137 | return false; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/BackupTest.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/databases/mydatabasetobackup', 10 | 'backupLocation' => __DIR__.'/databases/storage/backups' 11 | ]); 12 | 13 | $db->backup(); 14 | 15 | $this->assertEquals(true, true); 16 | } 17 | 18 | 19 | public function testBackupLocationDefault() 20 | { 21 | 22 | $db = new \Filebase\Database([ 23 | 'dir' => __DIR__.'/databases/mydatabasetobackup' 24 | ]); 25 | 26 | $db->backup(); 27 | 28 | $this->assertEquals(true, true); 29 | } 30 | 31 | 32 | public function testBackupCreate() 33 | { 34 | $db = new \Filebase\Database([ 35 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 36 | 'backupLocation' => __DIR__.'/databases/storage/backups' 37 | ]); 38 | 39 | $db->flush(true); 40 | 41 | for ($x = 1; $x <= 25; $x++) 42 | { 43 | $user = $db->get(uniqid()); 44 | $user->name = 'John'; 45 | $user->save(); 46 | } 47 | 48 | $file = $db->backup()->create(); 49 | 50 | $db->flush(true); 51 | 52 | $this->assertRegExp('/[0-9]+\.zip$/',$file); 53 | } 54 | 55 | 56 | public function testBackupFind() 57 | { 58 | $db = new \Filebase\Database([ 59 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 60 | 'backupLocation' => __DIR__.'/databases/storage/backups' 61 | ]); 62 | 63 | $db->flush(true); 64 | 65 | for ($x = 1; $x <= 25; $x++) 66 | { 67 | $user = $db->get(uniqid()); 68 | $user->name = 'John'; 69 | $user->save(); 70 | } 71 | 72 | $db->backup()->create(); 73 | 74 | $backups = $db->backup()->find(); 75 | 76 | $this->assertInternalType('array',$backups); 77 | $this->assertNotEmpty($backups); 78 | } 79 | 80 | 81 | public function testBackupFindSort() 82 | { 83 | $db = new \Filebase\Database([ 84 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 85 | 'backupLocation' => __DIR__.'/databases/storage/backups' 86 | ]); 87 | 88 | $db->flush(true); 89 | 90 | for ($x = 1; $x <= 25; $x++) 91 | { 92 | $user = $db->get(uniqid()); 93 | $user->name = 'John'; 94 | $user->save(); 95 | } 96 | 97 | $db->backup()->create(); 98 | $db->backup()->create(); 99 | $last = str_replace('.zip','',$db->backup()->create()); 100 | 101 | $backups = $db->backup()->find(); 102 | $backupCurrent = current($backups); 103 | 104 | $lastBackup = str_replace('.zip','',basename($backupCurrent)); 105 | 106 | $db->flush(true); 107 | 108 | $this->assertEquals($last,$lastBackup); 109 | } 110 | 111 | 112 | public function testBackupCleanup() 113 | { 114 | $db = new \Filebase\Database([ 115 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 116 | 'backupLocation' => __DIR__.'/databases/storage/backups' 117 | ]); 118 | 119 | $backupBefore = $db->backup()->find(); 120 | 121 | $db->backup()->clean(); 122 | $backupAfter = $db->backup()->find(); 123 | 124 | $this->assertInternalType('array',$backupBefore); 125 | $this->assertNotEmpty($backupBefore); 126 | 127 | $this->assertInternalType('array',$backupAfter); 128 | $this->assertEmpty($backupAfter); 129 | } 130 | 131 | 132 | public function testBackupRestore() 133 | { 134 | $db1 = new \Filebase\Database([ 135 | 'dir' => __DIR__.'/databases/backupdb', 136 | 'backupLocation' => __DIR__.'/databases/storage/backupdb' 137 | ]); 138 | 139 | for ($x = 1; $x <= 25; $x++) 140 | { 141 | $user = $db1->get(uniqid()); 142 | $user->name = 'John'; 143 | $user->save(); 144 | } 145 | 146 | $db1->backup()->create(); 147 | 148 | $items1 = $db1->count(); 149 | 150 | $db2 = new \Filebase\Database([ 151 | 'dir' => __DIR__.'/databases/backupdb2', 152 | 'backupLocation' => __DIR__.'/databases/storage/backupdb' 153 | ]); 154 | 155 | $db2->backup()->rollback(); 156 | $db2->backup()->clean(); 157 | 158 | $items2 = $db2->count(); 159 | 160 | $db1->flush(true); 161 | $db2->flush(true); 162 | 163 | $this->assertEquals($items1,$items2); 164 | 165 | } 166 | 167 | 168 | } 169 | -------------------------------------------------------------------------------- /tests/BackupYamlTest.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/databases/mydatabasetobackup', 10 | 'backupLocation' => __DIR__.'/databases/storage/backups', 11 | 'format' => \Filebase\Format\Yaml::class 12 | ]); 13 | 14 | $db->backup(); 15 | 16 | $this->assertEquals(true, true); 17 | } 18 | 19 | 20 | public function testBackupLocationDefault() 21 | { 22 | 23 | $db = new \Filebase\Database([ 24 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 25 | 'format' => \Filebase\Format\Yaml::class 26 | ]); 27 | 28 | $db->backup(); 29 | 30 | $this->assertEquals(true, true); 31 | } 32 | 33 | 34 | public function testBackupCreate() 35 | { 36 | $db = new \Filebase\Database([ 37 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 38 | 'backupLocation' => __DIR__.'/databases/storage/backups', 39 | 'format' => \Filebase\Format\Yaml::class 40 | ]); 41 | 42 | $db->flush(true); 43 | 44 | for ($x = 1; $x <= 25; $x++) 45 | { 46 | $user = $db->get(uniqid()); 47 | $user->name = 'John'; 48 | $user->save(); 49 | } 50 | 51 | $file = $db->backup()->create(); 52 | 53 | $db->flush(true); 54 | 55 | $this->assertRegExp('/[0-9]+\.zip$/',$file); 56 | } 57 | 58 | 59 | public function testBackupFind() 60 | { 61 | $db = new \Filebase\Database([ 62 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 63 | 'backupLocation' => __DIR__.'/databases/storage/backups', 64 | 'format' => \Filebase\Format\Yaml::class 65 | ]); 66 | 67 | $db->flush(true); 68 | 69 | for ($x = 1; $x <= 25; $x++) 70 | { 71 | $user = $db->get(uniqid()); 72 | $user->name = 'John'; 73 | $user->save(); 74 | } 75 | 76 | $db->backup()->create(); 77 | 78 | $backups = $db->backup()->find(); 79 | 80 | $this->assertInternalType('array',$backups); 81 | $this->assertNotEmpty($backups); 82 | } 83 | 84 | 85 | public function testBackupFindSort() 86 | { 87 | $db = new \Filebase\Database([ 88 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 89 | 'backupLocation' => __DIR__.'/databases/storage/backups', 90 | 'format' => \Filebase\Format\Yaml::class 91 | ]); 92 | 93 | $db->flush(true); 94 | 95 | for ($x = 1; $x <= 25; $x++) 96 | { 97 | $user = $db->get(uniqid()); 98 | $user->name = 'John'; 99 | $user->save(); 100 | } 101 | 102 | $db->backup()->create(); 103 | $db->backup()->create(); 104 | $last = str_replace('.zip','',$db->backup()->create()); 105 | 106 | $backups = $db->backup()->find(); 107 | $backupCurrent = current($backups); 108 | 109 | $lastBackup = str_replace('.zip','',basename($backupCurrent)); 110 | 111 | $db->flush(true); 112 | 113 | $this->assertEquals($last,$lastBackup); 114 | } 115 | 116 | 117 | public function testBackupCleanup() 118 | { 119 | $db = new \Filebase\Database([ 120 | 'dir' => __DIR__.'/databases/mydatabasetobackup', 121 | 'backupLocation' => __DIR__.'/databases/storage/backups', 122 | 'format' => \Filebase\Format\Yaml::class 123 | ]); 124 | 125 | $backupBefore = $db->backup()->find(); 126 | 127 | $db->backup()->clean(); 128 | $backupAfter = $db->backup()->find(); 129 | 130 | $this->assertInternalType('array',$backupBefore); 131 | $this->assertNotEmpty($backupBefore); 132 | 133 | $this->assertInternalType('array',$backupAfter); 134 | $this->assertEmpty($backupAfter); 135 | } 136 | 137 | 138 | public function testBackupRestore() 139 | { 140 | $db1 = new \Filebase\Database([ 141 | 'dir' => __DIR__.'/databases/backupdb', 142 | 'backupLocation' => __DIR__.'/databases/storage/backupdb', 143 | 'format' => \Filebase\Format\Yaml::class 144 | ]); 145 | 146 | for ($x = 1; $x <= 25; $x++) 147 | { 148 | $user = $db1->get(uniqid()); 149 | $user->name = 'John'; 150 | $user->save(); 151 | } 152 | 153 | $db1->backup()->create(); 154 | 155 | $items1 = $db1->count(); 156 | 157 | $db2 = new \Filebase\Database([ 158 | 'dir' => __DIR__.'/databases/backupdb2', 159 | 'backupLocation' => __DIR__.'/databases/storage/backupdb', 160 | 'format' => \Filebase\Format\Yaml::class 161 | ]); 162 | 163 | $db2->backup()->rollback(); 164 | $db2->backup()->clean(); 165 | 166 | $items2 = $db2->count(); 167 | 168 | $db1->flush(true); 169 | $db2->flush(true); 170 | 171 | $this->assertEquals($items1,$items2); 172 | 173 | } 174 | 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/Backup.php: -------------------------------------------------------------------------------- 1 | backupLocation = $backupLocation; 37 | $this->config = $database->getConfig(); 38 | $this->database = $database; 39 | 40 | // Check directory and create it if it doesn't exist 41 | if (!is_dir($this->backupLocation)) 42 | { 43 | if (!@mkdir($this->backupLocation, 0777, true)) 44 | { 45 | throw new \Exception(sprintf('`%s` doesn\'t exist and can\'t be created.', $this->backupLocation)); 46 | } 47 | } 48 | else if (!is_writable($this->backupLocation)) 49 | { 50 | throw new \Exception(sprintf('`%s` is not writable.', $this->backupLocation)); 51 | } 52 | } 53 | 54 | /** 55 | * save() 56 | * 57 | */ 58 | public function create() 59 | { 60 | $backupFile = $this->backupLocation.'/'.time().'.zip'; 61 | 62 | if ($results = $this->zip($this->config->dir, $backupFile)) 63 | { 64 | $basename = basename($backupFile); 65 | return $basename; 66 | } 67 | 68 | throw new \Exception('Error backing up database.'); 69 | } 70 | 71 | /** 72 | * find() 73 | * 74 | * Returns an array of all the backups currently available 75 | * 76 | */ 77 | public function find() 78 | { 79 | $backups = []; 80 | $files = glob(realpath($this->backupLocation)."/*.zip"); 81 | foreach($files as $file) 82 | { 83 | $basename = str_replace('.zip','',basename($file)); 84 | $backups[$basename] = $this->backupLocation.'/'.$basename.'.zip'; 85 | } 86 | 87 | krsort($backups); 88 | 89 | return $backups; 90 | } 91 | 92 | /** 93 | * clean() 94 | * 95 | * Clears and deletes all backups (zip files only) 96 | * 97 | */ 98 | public function clean() 99 | { 100 | return array_map('unlink', glob(realpath($this->backupLocation)."/*.zip")); 101 | } 102 | 103 | /** 104 | * rollback() 105 | * 106 | * Rollback database to the last backup available 107 | * 108 | */ 109 | public function rollback() 110 | { 111 | $backups = $this->find(); 112 | $restore = current($backups); 113 | 114 | $this->database->truncate(); 115 | 116 | return $this->extract($restore, $this->config->dir); 117 | } 118 | 119 | /** 120 | * extract() 121 | * 122 | * @param string $source (zip location) 123 | * @param string $target (unload files to location) 124 | * @return bool 125 | */ 126 | protected function extract($source = '', $target = '') 127 | { 128 | if (!extension_loaded('zip') && !file_exists($source)) 129 | { 130 | return false; 131 | } 132 | $zip = new \ZipArchive(); 133 | if ($zip->open($source) === TRUE) 134 | { 135 | $zip->extractTo($target); 136 | $zip->close(); 137 | 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | /** 144 | * zip() 145 | * 146 | * Prevents the zip from zipping up the storage diretories 147 | * 148 | */ 149 | protected function zip($source = '', $target = '') 150 | { 151 | if (!extension_loaded('zip') || !file_exists($source)) 152 | { 153 | return false; 154 | } 155 | 156 | $zip = new \ZipArchive(); 157 | if (!$zip->open($target, \ZIPARCHIVE::CREATE)) 158 | { 159 | $zip->addFromString(basename($source), file_get_contents($source)); 160 | } 161 | $source = realpath($source); 162 | if (is_dir($source)) 163 | { 164 | $iterator = new \RecursiveDirectoryIterator($source); 165 | $iterator->setFlags(\RecursiveDirectoryIterator::SKIP_DOTS); 166 | $files = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); 167 | foreach ($files as $file) 168 | { 169 | $file = realpath($file); 170 | 171 | if (preg_match('|'.realpath($this->backupLocation).'|',$file)) 172 | { 173 | continue; 174 | } 175 | 176 | if (is_dir($file)) 177 | { 178 | $zip->addEmptyDir(str_replace($source . '/', '', $file . '/')); 179 | } 180 | else if (is_file($file)) 181 | { 182 | $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file)); 183 | } 184 | } 185 | 186 | } 187 | 188 | return $zip->close(); 189 | 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/Query.php: -------------------------------------------------------------------------------- 1 | select() 21 | * 22 | * Set the selected fields you wish to return from each document 23 | * 24 | */ 25 | public function select($fields) 26 | { 27 | if (is_string($fields)) 28 | { 29 | $fields = explode(',',trim($fields)); 30 | } 31 | 32 | if (is_array($fields)) 33 | { 34 | $this->fields = $fields; 35 | } 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * ->where() 42 | * 43 | */ 44 | public function where(...$arg) 45 | { 46 | $this->addPredicate('and', $arg); 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * ->andWhere() 53 | * 54 | */ 55 | public function andWhere(...$arg) 56 | { 57 | $this->addPredicate('and', $arg); 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * ->orWhere() 64 | * 65 | */ 66 | public function orWhere(...$arg) 67 | { 68 | $this->addPredicate('or', $arg); 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * ->limit() 75 | * 76 | */ 77 | public function limit($limit, $offset = 0) 78 | { 79 | $this->limit = (int) $limit; 80 | 81 | if ($this->limit === 0) 82 | { 83 | $this->limit = 9999999; 84 | } 85 | 86 | $this->offset = (int) $offset; 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * ->orderBy() 93 | * 94 | */ 95 | public function orderBy($field, $sort = 'ASC') 96 | { 97 | if (count($this->orderBy) == 1 && $this->orderBy[0] == '') { 98 | // Just set the initial index 99 | $this->orderBy[0] = $field; 100 | $this->sortBy[0] = strtoupper($sort); 101 | } else { 102 | $this->orderBy[] = $field; 103 | $this->sortBy[] = strtoupper($sort); 104 | } 105 | 106 | return $this; 107 | } 108 | 109 | /** 110 | * addPredicate 111 | * 112 | */ 113 | protected function addPredicate($logic,$arg) 114 | { 115 | $this->predicate->add($logic, $arg); 116 | } 117 | 118 | /** 119 | * ->getDocuments() 120 | * 121 | * 122 | */ 123 | public function getDocuments() 124 | { 125 | return $this->documents; 126 | } 127 | 128 | /** 129 | * ->results() 130 | * 131 | * @param bool $data_only - default:true (if true only return the documents data not the full object) 132 | * 133 | */ 134 | public function results( $data_only = true ) 135 | { 136 | if ($data_only === true && empty($this->fields)) 137 | { 138 | return parent::run()->toArray(); 139 | } 140 | 141 | return $this->resultDocuments(); 142 | } 143 | 144 | /** 145 | * ->resultDocuments() 146 | * 147 | */ 148 | public function resultDocuments() 149 | { 150 | return parent::run()->getDocuments(); 151 | } 152 | 153 | /** 154 | * ->first() 155 | * 156 | * @param bool $data_only - default:true (if true only return the documents data not the full object) 157 | * 158 | */ 159 | public function first( $data_only = true ) 160 | { 161 | if ($data_only === true && empty($this->fields)) 162 | { 163 | $results = parent::run()->toArray(); 164 | return current($results); 165 | } 166 | 167 | $results = parent::run()->getDocuments(); 168 | return current($results); 169 | } 170 | 171 | /** 172 | * ->last() 173 | * 174 | * @param bool $data_only - default:true (if true only return the documents data not the full object) 175 | * 176 | */ 177 | public function last( $data_only = true ) 178 | { 179 | if ($data_only === true && empty($this->fields)) 180 | { 181 | $results = parent::run()->toArray(); 182 | return end($results); 183 | } 184 | 185 | $results = parent::run()->getDocuments(); 186 | return end($results); 187 | } 188 | 189 | /** 190 | * ->count() 191 | * 192 | * Count and return the number of documents in array 193 | * 194 | */ 195 | public function count() 196 | { 197 | $results = parent::run()->getDocuments(); 198 | return count($results); 199 | } 200 | 201 | /** 202 | * toArray 203 | * 204 | * @param \Filebase\Document 205 | * @return array 206 | */ 207 | public function toArray() 208 | { 209 | $docs = []; 210 | 211 | if (!empty($this->documents)) 212 | { 213 | foreach($this->documents as $document) 214 | { 215 | $docs[] = (array) $document->getData(); 216 | } 217 | } 218 | 219 | return $docs; 220 | } 221 | 222 | /** 223 | * delete 224 | * 225 | * The ability to delete items using queries 226 | * 227 | * Delete by condition or delete all within clause 228 | * 229 | * @return void 230 | */ 231 | public function delete($input = null) 232 | { 233 | $items = $this->resultDocuments(); 234 | $condition = $input; 235 | foreach($items as $item) 236 | { 237 | if (is_object($input)) { 238 | $condition = $input($item); 239 | 240 | if ($condition) { 241 | $item->delete(); 242 | } 243 | } 244 | else { 245 | $item->delete(); 246 | } 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | ### 02/24/2019 - 1.0.24 5 | * Merged [Pull Request](https://github.com/filebase/Filebase/pull/50) Fixed [bug](https://github.com/filebase/Filebase/issues/41) returning unexpected results. 6 | 7 | ### 02/24/2019 - 1.0.23 8 | * Merged [Pull Request](https://github.com/filebase/Filebase/pull/49) Added support for order by multiple columns 9 | * Merged [Pull Request](https://github.com/filebase/Filebase/pull/46) Added ability to query document ids (internal id) 10 | * Added ability to use query `delete()` on all items that match the query (making the custom filter optional) 11 | 12 | ### 02/23/2019 - 1.0.22 13 | * Merged [Pull Request](https://github.com/filebase/Filebase/pull/47) for deleting items with a custom filter. (this adds the `delete()` method on queries.) 14 | * Merged [Pull Request](https://github.com/filebase/Filebase/pull/48) for calling to the Query methods directly on the database class. 15 | * 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`) 16 | 17 | ### 12/26/2018 - 1.0.21 18 | * Merged [Pull Request](https://github.com/filebase/Filebase/pull/30) for YAML format. 19 | 20 | ### 08/16/2018 - 1.0.20 21 | * Fixed #23 – Caching is cleared when deleting/saving documents to prevent cache from being out of sync with document data. 22 | 23 | ### 08/13/2018 - 1.0.19 24 | * Added #21 for checking database record exists, added `has()` method on database. 25 | 26 | ### 07/14/2018 - 1.0.18 27 | * Fixed #17 for PHP 5.6 28 | * Replaced the spaceship operators with a php 5.6 alternative. 29 | * Removed the php 7 type hinting. 30 | * Added php >= 5.6 as the new requirement for install. 31 | 32 | ### 07/06/2018 - 1.0.17 33 | * Fixed #19 the `AND` query logic. (previously the `where()` query would only used the last one in the chain). 34 | 35 | ### 05/28/2018 - 1.0.16 36 | * Fixed the scope resolution operator `::` for php 5.6 which throws the `T_PAAMAYIM_NEKUDOTAYIM` exception error. 37 | 38 | ### 03/13/2018 - 1.0.15 39 | * Quick patch on composer.json file for dev dependency with satooshi/php-coveralls issues on dev-master. (this would have only affected new users from trying to install via composer.) 40 | 41 | ### 02/09/2018 - 1.0.14 42 | * Added #11 a new configuration variable `read_only`. By default `false`, when set to `true` no modifications can be made to the database and if you attempt to make a `save()`, `delete()`, `truncate()` or `flush()` an exception will be thrown as those methods are not allowed to be used in read-only mode. 43 | * The database will not attempt to create a new directory if one does not exist during read-only mode, this can become an issue if you don't have permission to do so, read-only tries to solve that. 44 | * When set to `false` the database functions as normal. 45 | 46 | ### 12/14/2017 - 1.0.13 47 | * Added #10 a new configuration variable `safe_filename`. By default `true`, suppresses any file name errors and converts the file name to a valid name, if set to `false`, an exception will be thrown upon a invalid name. All users who update will notice no errors will appear upon a invalid name. Set `safe_filename` to `false` if you prefer the exception to be thrown. 48 | 49 | ### 12/11/2017 - 1.0.12 50 | * Added #8 - `select()` method on query class. Now allows you to specify which fields you want your documents to return. *Note: using `select` means your documents will not return document objects but only data arrays.* This will allow you to only include the fields you want to use for your current task. (Excluding the rest and reducing memory usage). 51 | * Added `last()` method on query class to return the last item in the result array (opposite of `first()`) 52 | * Added `count()` method on query class to return the number of documents found by the query. 53 | 54 | ### 12/11/2017 - 1.0.11 55 | * Fixed query `sort` which allows for "natural order", issues before would assume "1" and "10" are equal in value, but this has been resolved with this update. Uses php `strnatcasecmp()`, This was fixed for `DESC` order in the previous update. This patch fixes the `ASC` sort order. 56 | 57 | ### 12/10/2017 - 1.0.10 58 | * Fixed query `sort` which allows for "natural order", issues before would assume "1" and "10" are equal in value, but this has been resolved with this update. Uses php `strnatcasecmp()` 59 | * Added argument `results( false )` - `false` on `results()` method that allows it to return the full document object or (by default = `true`) only the document data. 60 | * Added argument `first( false )` - `false` on `first()` method that allows it to return the full document object or (by default = `true`) only the document data. 61 | * Minor additions to the documentation. 62 | 63 | ### 09/09/2017 - 1.0.9 64 | * Fixed `customFilter` on #5 issue with array keys not properly resetting. 65 | * Improved speed of `filter()` since it was running the function closure function twice 66 | * Added alias of `customFilter()` as `filter()` method. 67 | * Added `version()` method on database class. `$db->version()` for the Filebase version number. 68 | 69 | ### 09/08/2017 - 1.0.8 70 | * Updated `customFilter` method to allow passable parameters into closure function. (backwards compatibility allowing param and function arguments to be any order) 71 | 72 | ### 09/05/2017 - 1.0.7 73 | * Added `rollback()` method on the backup class. Now the ability to restore an existing back up (latest one available) 74 | 75 | ### 09/05/2017 - 1.0.6 76 | * Added new `backup` class and functionality to create database backups. 77 | * Added `create()`, `clean()` and `find()` methods on backup class. 78 | 79 | Accessible when invoked on your database `$db->backup->create()`, Rollbacks are on the to do list for the next update. This update includes the ability to create new backups and deletes or shows existing backups for your own records. *Restoring from a previous backup is on the todo list.* 80 | 81 | ### 08/06/2017 - 1.0.5 82 | * Added new method database class `truncate()` as an alias of `flush(true)` 83 | * Added `REGEX` as new query operator. Uses `preg_match($regex, $fieldValue)` 84 | 85 | ### 08/05/2017 - 1.0.4 86 | * Added `first()` (if you want to only return the first array of the query result) 87 | * Ability to use Queries without needing `where()`, can now use queries to find all and order results 88 | * Fixed Predicate Exceptions for bad query arguments (now correctly parsing them) 89 | 90 | ### 08/05/2017 - 1.0.3 91 | * Added `orderBy()` (sorting field and direction `ASC` and `DESC`) 92 | * Added `limit()` Limit results returned, includes Limit and Offset options. 93 | 94 | ### 08/05/2017 - 1.0.2 95 | * Added the `NOT LIKE` operator 96 | 97 | ### 08/04/2017 - 1.0.1 98 | * Added the `LIKE` operator and few small tweaks 99 | 100 | ### 08/04/2017 - 1.0.0 101 | * Initial Release 102 | -------------------------------------------------------------------------------- /src/QueryLogic.php: -------------------------------------------------------------------------------- 1 | database = $database; 37 | $this->predicate = new Predicate(); 38 | 39 | if ($this->database->getConfig()->cache===true) 40 | { 41 | $this->cache = new Cache($this->database); 42 | } 43 | } 44 | 45 | /** 46 | * loadDocuments 47 | * 48 | */ 49 | private function loadDocuments() 50 | { 51 | $predicates = $this->predicate->get(); 52 | 53 | if ($this->cache===false) 54 | { 55 | $this->documents = $this->database->findAll(true,false); 56 | return $this; 57 | } 58 | 59 | $this->cache->setKey(json_encode($predicates)); 60 | 61 | if ($cached_documents = $this->cache->get()) 62 | { 63 | $this->documents = $cached_documents; 64 | 65 | $this->sort(); 66 | $this->offsetLimit(); 67 | return $this; 68 | } 69 | $this->documents = $this->database->findAll(true,false); 70 | return $this; 71 | } 72 | 73 | /** 74 | * run 75 | * 76 | */ 77 | public function run() 78 | { 79 | $predicates = $this->predicate->get(); 80 | $this->documents = []; 81 | $cached_documents = false; 82 | 83 | if (empty($predicates)) 84 | { 85 | $predicates = 'findAll'; 86 | } 87 | 88 | $this->loadDocuments(); 89 | 90 | if ($predicates !== 'findAll') 91 | { 92 | $this->documents = $this->filter($this->documents, $predicates); 93 | } 94 | 95 | if ($this->cache !== false) 96 | { 97 | if ($cached_documents === false) 98 | { 99 | $dsave = []; 100 | foreach($this->documents as $document) 101 | { 102 | $dsave[] = $document->getId(); 103 | } 104 | 105 | $this->cache->store($dsave); 106 | } 107 | } 108 | 109 | $this->sort(); 110 | $this->offsetLimit(); 111 | 112 | if (is_array($this->fields) && !empty($this->fields)) 113 | { 114 | foreach($this->documents as $index => $document) 115 | { 116 | $fields = []; 117 | foreach($this->fields as $fieldTarget) 118 | { 119 | $fields[ $fieldTarget ] = $document->field($fieldTarget); 120 | } 121 | 122 | $this->documents[$index] = $fields; 123 | } 124 | } 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * filter 131 | * 132 | */ 133 | protected function filter($documents, $predicates) 134 | { 135 | $results = []; 136 | 137 | $org_docs = $documents; 138 | 139 | if (isset($predicates['and']) && !empty($predicates['and'])) 140 | { 141 | foreach($predicates['and'] as $predicate) 142 | { 143 | list($field, $operator, $value) = $predicate; 144 | 145 | $documents = array_values(array_filter($documents, function ($document) use ($field, $operator, $value) { 146 | return $this->match($document, $field, $operator, $value); 147 | })); 148 | 149 | $results = $documents; 150 | } 151 | } 152 | 153 | if (isset($predicates['or']) && !empty($predicates['or'])) 154 | { 155 | foreach($predicates['or'] as $predicate) 156 | { 157 | list($field, $operator, $value) = $predicate; 158 | 159 | $documents = array_values(array_filter($org_docs, function ($document) use ($field, $operator, $value) { 160 | return $this->match($document, $field, $operator, $value); 161 | })); 162 | 163 | $results = array_unique(array_merge($results, $documents), SORT_REGULAR); 164 | } 165 | } 166 | 167 | return $results; 168 | } 169 | 170 | /** 171 | * offsetLimit 172 | * 173 | */ 174 | protected function offsetLimit() 175 | { 176 | if ($this->limit != 0 || $this->offset != 0) 177 | { 178 | $this->documents = array_slice($this->documents, $this->offset, $this->limit); 179 | } 180 | } 181 | 182 | /** 183 | * sort 184 | * 185 | */ 186 | protected function sort() 187 | { 188 | if ($this->orderBy[0] == '') 189 | { 190 | return false; 191 | } 192 | 193 | $sortlogic = new SortLogic($this->orderBy, $this->sortBy, 0); 194 | usort($this->documents, [$sortlogic, 'sort']); 195 | } 196 | 197 | /** 198 | * match 199 | * 200 | */ 201 | public function match($document, $field, $operator, $value) 202 | { 203 | $d_value = $document->field($field); 204 | 205 | switch (true) 206 | { 207 | case ($operator === '=' && $d_value == $value): 208 | return true; 209 | case ($operator === '==' && $d_value == $value): 210 | return true; 211 | case ($operator === '===' && $d_value === $value): 212 | return true; 213 | case ($operator === '!=' && $d_value != $value): 214 | return true; 215 | case ($operator === '!==' && $d_value !== $value): 216 | return true; 217 | case (strtoupper($operator) === 'NOT' && $d_value != $value): 218 | return true; 219 | case ($operator === '>' && $d_value > $value): 220 | return true; 221 | case ($operator === '>=' && $d_value >= $value): 222 | return true; 223 | case ($operator === '<' && $d_value < $value): 224 | return true; 225 | case ($operator === '<=' && $d_value <= $value): 226 | return true; 227 | case (strtoupper($operator) === 'LIKE' && preg_match('/'.$value.'/is',$d_value)): 228 | return true; 229 | case (strtoupper($operator) === 'NOT LIKE' && !preg_match('/'.$value.'/is',$d_value)): 230 | return true; 231 | case (strtoupper($operator) === 'IN' && in_array($d_value, (array) $value)): 232 | return true; 233 | case (strtoupper($operator) === 'IN' && in_array($value, (array) $d_value)): 234 | return true; 235 | case (strtoupper($operator) === 'REGEX' && preg_match($value, $d_value)): 236 | return true; 237 | default: 238 | return false; 239 | } 240 | 241 | } 242 | 243 | } 244 | -------------------------------------------------------------------------------- /tests/ValidationTest.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/databases', 11 | 'validate' => [ 12 | 'name' => [ 13 | 'valid.type' => 'string', 14 | 'valid.required' => true 15 | ] 16 | ] 17 | ]); 18 | 19 | $db->flush(true); 20 | 21 | $db->get('test')->set(['name'=>'value'])->save(); 22 | 23 | $this->assertEquals(true, true); 24 | 25 | $db->flush(true); 26 | } 27 | 28 | 29 | public function testStringRequiredBad() 30 | { 31 | $this->expectException(\Exception::class); 32 | 33 | $db = new \Filebase\Database([ 34 | 'dir' => __DIR__.'/databases', 35 | 'validate' => [ 36 | 'name' => [ 37 | 'valid.type' => 'string', 38 | 'valid.required' => true 39 | ] 40 | ] 41 | ]); 42 | 43 | $db->flush(true); 44 | 45 | $db->get('test')->set(['name'=>123])->save(); 46 | 47 | $db->flush(true); 48 | } 49 | 50 | 51 | public function testOnlyRequiredGood() 52 | { 53 | $db = new \Filebase\Database([ 54 | 'dir' => __DIR__.'/databases', 55 | 'validate' => [ 56 | 'name' => [ 57 | 'valid.required' => true 58 | ] 59 | ] 60 | ]); 61 | 62 | $db->flush(true); 63 | 64 | $db->get('test')->set(['name'=>'value'])->save(); 65 | 66 | $this->assertEquals(true, true); 67 | 68 | $db->flush(true); 69 | } 70 | 71 | 72 | public function testOnlyRequiredBad() 73 | { 74 | $this->expectException(\Exception::class); 75 | 76 | $db = new \Filebase\Database([ 77 | 'dir' => __DIR__.'/databases', 78 | 'validate' => [ 79 | 'this_is_new' => [ 80 | 'valid.required' => true 81 | ] 82 | ] 83 | ]); 84 | 85 | $db->flush(true); 86 | 87 | $db->get('test')->set(['name'=>'value'])->save(); 88 | 89 | $this->assertEquals(true, true); 90 | 91 | $db->flush(true); 92 | } 93 | 94 | 95 | 96 | public function testNestedString() 97 | { 98 | $db = new \Filebase\Database([ 99 | 'dir' => __DIR__.'/databases', 100 | 'validate' => [ 101 | 'profile' => [ 102 | 'type' => 'array', 103 | 'aboutme' => [ 104 | 'valid.type' => 'string', 105 | 'valid.required' => true 106 | ] 107 | ] 108 | ] 109 | ]); 110 | 111 | $db->flush(true); 112 | 113 | 114 | $array = [ 115 | 'profile' => [ 116 | 'aboutme' => 'I am a happy coder' 117 | ] 118 | ]; 119 | 120 | 121 | $db->get('test')->set($array)->save(); 122 | 123 | $this->assertEquals(true, true); 124 | 125 | $db->flush(true); 126 | } 127 | 128 | 129 | public function testNestedBad() 130 | { 131 | $this->expectException(\Exception::class); 132 | 133 | $db = new \Filebase\Database([ 134 | 'dir' => __DIR__.'/databases', 135 | 'validate' => [ 136 | 'profile' => [ 137 | 'type' => 'array', 138 | 'aboutme' => [ 139 | 'valid.type' => 'string', 140 | 'valid.required' => true 141 | ] 142 | ] 143 | ] 144 | ]); 145 | 146 | $db->flush(true); 147 | 148 | 149 | $array = [ 150 | 'profile' => [ 151 | 'aboutme' => 321456 152 | ] 153 | ]; 154 | 155 | 156 | $db->get('test')->set($array)->save(); 157 | 158 | $db->flush(true); 159 | } 160 | 161 | 162 | public function testArrayGood() 163 | { 164 | $db = new \Filebase\Database([ 165 | 'dir' => __DIR__.'/databases', 166 | 'validate' => [ 167 | 'profile' => [ 168 | 'valid.type' => 'array' 169 | ] 170 | ] 171 | ]); 172 | 173 | $db->flush(true); 174 | 175 | 176 | $array = [ 177 | 'profile' => [ 178 | "1","2","3" 179 | ] 180 | ]; 181 | 182 | $db->get('test')->set($array)->save(); 183 | 184 | $this->assertEquals(true, true); 185 | 186 | $db->flush(true); 187 | } 188 | 189 | 190 | public function testArrayBad() 191 | { 192 | $this->expectException(\Exception::class); 193 | 194 | $db = new \Filebase\Database([ 195 | 'dir' => __DIR__.'/databases', 196 | 'validate' => [ 197 | 'profile' => [ 198 | 'valid.type' => 'array' 199 | ] 200 | ] 201 | ]); 202 | 203 | $db->flush(true); 204 | 205 | 206 | $array = [ 207 | 'profile' => 123 208 | ]; 209 | 210 | $db->get('test')->set($array)->save(); 211 | 212 | $db->flush(true); 213 | } 214 | 215 | public function testIntBad() 216 | { 217 | $this->expectException(\Exception::class); 218 | 219 | $db = new \Filebase\Database([ 220 | 'dir' => __DIR__.'/databases', 221 | 'validate' => [ 222 | 'profile' => [ 223 | 'valid.type' => 'int' 224 | ] 225 | ] 226 | ]); 227 | 228 | $db->flush(true); 229 | 230 | 231 | $array = [ 232 | 'profile' => '123' 233 | ]; 234 | 235 | $db->get('test')->set($array)->save(); 236 | 237 | $db->flush(true); 238 | } 239 | 240 | public function testIntGood() 241 | { 242 | $db = new \Filebase\Database([ 243 | 'dir' => __DIR__.'/databases', 244 | 'validate' => [ 245 | 'profile' => [ 246 | 'valid.type' => 'int' 247 | ] 248 | ] 249 | ]); 250 | 251 | $db->flush(true); 252 | 253 | 254 | $array = [ 255 | 'profile' => 123 256 | ]; 257 | 258 | $db->get('test')->set($array)->save(); 259 | 260 | $this->assertEquals(true, true); 261 | 262 | $db->flush(true); 263 | } 264 | 265 | 266 | public function testArrType() 267 | { 268 | $db = new \Filebase\Database([ 269 | 'dir' => __DIR__.'/databases', 270 | 'validate' => [ 271 | 'profile' => [ 272 | 'valid.type' => 'arr' 273 | ] 274 | ] 275 | ]); 276 | 277 | $db->flush(true); 278 | 279 | 280 | $array = [ 281 | 'profile' => [123] 282 | ]; 283 | 284 | $db->get('test')->set($array)->save(); 285 | 286 | $this->assertEquals(true, true); 287 | 288 | $db->flush(true); 289 | } 290 | 291 | 292 | public function testUnknownType() 293 | { 294 | $this->expectException(\Exception::class); 295 | 296 | $db = new \Filebase\Database([ 297 | 'dir' => __DIR__.'/databases', 298 | 'validate' => [ 299 | 'profile' => [ 300 | 'valid.type' => 'unknown' 301 | ] 302 | ] 303 | ]); 304 | 305 | $db->flush(true); 306 | 307 | 308 | $array = [ 309 | 'profile' => [123] 310 | ]; 311 | 312 | $db->get('test')->set($array)->save(); 313 | 314 | $this->assertEquals(true, true); 315 | 316 | $db->flush(true); 317 | } 318 | 319 | } 320 | -------------------------------------------------------------------------------- /src/Document.php: -------------------------------------------------------------------------------- 1 | __database = $database; 26 | } 27 | 28 | /** 29 | * saveAs 30 | * 31 | */ 32 | public function saveAs() 33 | { 34 | $data = (object) []; 35 | $vars = get_object_vars($this); 36 | 37 | foreach($vars as $k=>$v) 38 | { 39 | if (in_array($k,['__database','__id','__cache'])) continue; 40 | $data->{$k} = $v; 41 | } 42 | 43 | return $data; 44 | } 45 | 46 | /** 47 | * save() 48 | * 49 | * Saving the document to disk (file) 50 | * 51 | * @param mixed $data (optional, only if you want to "replace" entire doc data) 52 | * @return @see \Filebase\Database save() 53 | */ 54 | public function save($data = '') 55 | { 56 | Validate::valid($this); 57 | 58 | return $this->__database->save($this, $data); 59 | } 60 | 61 | /** 62 | * delete 63 | * 64 | * Deletes document from disk (file) 65 | * 66 | * @return @see \Filebase\Database delete() 67 | */ 68 | public function delete() 69 | { 70 | return $this->__database->delete($this); 71 | } 72 | 73 | /** 74 | * set 75 | * 76 | */ 77 | public function set($data) 78 | { 79 | return $this->__database->set($this, $data); 80 | } 81 | 82 | /** 83 | * toArray 84 | * 85 | */ 86 | public function toArray() 87 | { 88 | return $this->__database->toArray($this); 89 | } 90 | 91 | /** 92 | * __set 93 | * 94 | */ 95 | public function __set($name, $value) 96 | { 97 | $this->data[$name] = $value; 98 | } 99 | 100 | /** 101 | * __get 102 | * 103 | */ 104 | public function &__get($name) 105 | { 106 | if (!array_key_exists($name, $this->data)) 107 | { 108 | $this->data[$name] = null; 109 | } 110 | 111 | return $this->data[$name]; 112 | } 113 | 114 | /** 115 | * __isset 116 | * 117 | */ 118 | public function __isset($name) 119 | { 120 | return isset($this->data[$name]); 121 | } 122 | 123 | /** 124 | * __unset 125 | * 126 | */ 127 | public function __unset($name) 128 | { 129 | unset($this->data[$name]); 130 | } 131 | 132 | 133 | //-------------------------------------------------------------------- 134 | 135 | 136 | /** 137 | * filter 138 | * 139 | * Alias of customFilter 140 | * 141 | * @see customFilter 142 | */ 143 | public function filter($field = 'data', $paramOne = '', $paramTwo = '') 144 | { 145 | return $this->customFilter($field, $paramOne, $paramTwo); 146 | } 147 | 148 | /** 149 | * customFilter 150 | * 151 | * Allows you to run a custom function around each item 152 | * 153 | * @param string $field 154 | * @param callable $function 155 | * @return array $r items that the callable function returned 156 | */ 157 | public function customFilter($field = 'data', $paramOne = '', $paramTwo = '') 158 | { 159 | $items = $this->field($field); 160 | 161 | if (is_callable($paramOne)) 162 | { 163 | $function = $paramOne; 164 | $param = $paramTwo; 165 | } 166 | else 167 | { 168 | if (is_callable($paramTwo)) 169 | { 170 | $function = $paramTwo; 171 | $param = $paramOne; 172 | } 173 | } 174 | 175 | 176 | if (!is_array($items) || empty($items)) 177 | { 178 | return []; 179 | } 180 | 181 | $r = []; 182 | foreach($items as $index => $item) 183 | { 184 | $i = $function($item, $param); 185 | 186 | if ($i!==false && !is_null($i)) 187 | { 188 | $r[$index] = $i; 189 | } 190 | } 191 | 192 | $r = array_values($r); 193 | 194 | return $r; 195 | 196 | } 197 | 198 | /** 199 | * getDatabase 200 | * 201 | * @return $database 202 | */ 203 | public function getDatabase() 204 | { 205 | return $this->__database; 206 | } 207 | 208 | /** 209 | * getId 210 | * 211 | * @return mixed $__id 212 | */ 213 | public function getId() 214 | { 215 | return $this->__id; 216 | } 217 | 218 | /** 219 | * getData 220 | * 221 | * @return mixed data 222 | */ 223 | public function getData() 224 | { 225 | return $this->data; 226 | } 227 | 228 | /** 229 | * setId 230 | * 231 | * @param mixed $id 232 | */ 233 | public function setId($id) 234 | { 235 | $this->__id = $id; 236 | 237 | return $this; 238 | } 239 | 240 | /** 241 | * setCache 242 | * 243 | * @param boolean $cache 244 | */ 245 | public function setFromCache($cache = true) 246 | { 247 | $this->__cache = $cache; 248 | 249 | return $this; 250 | } 251 | 252 | /** 253 | * isCache 254 | * 255 | */ 256 | public function isCache() 257 | { 258 | return $this->__cache; 259 | } 260 | 261 | /** 262 | * createdAt 263 | * 264 | * When this document was created (or complete replaced) 265 | * 266 | * @param string $format php date format (default Y-m-d H:i:s) 267 | * @return string date format 268 | */ 269 | public function createdAt($format = 'Y-m-d H:i:s') 270 | { 271 | if (!$this->__created_at) 272 | { 273 | return date($format); 274 | } 275 | 276 | if ($format !== false) 277 | { 278 | return date($format, $this->__created_at); 279 | } 280 | 281 | return $this->__created_at; 282 | } 283 | 284 | /** 285 | * updatedAt 286 | * 287 | * When this document was updated 288 | * 289 | * @param string $format php date format (default Y-m-d H:i:s) 290 | * @return string date format 291 | */ 292 | public function updatedAt($format = 'Y-m-d H:i:s') 293 | { 294 | if (!$this->__updated_at) 295 | { 296 | return date($format); 297 | } 298 | 299 | if ($format !== false) 300 | { 301 | return date($format, $this->__updated_at); 302 | } 303 | 304 | return $this->__updated_at; 305 | } 306 | 307 | /** 308 | * setCreatedAt 309 | * 310 | * @param int $created_at php time() 311 | */ 312 | public function setCreatedAt($created_at) 313 | { 314 | $this->__created_at = $created_at; 315 | 316 | return $this; 317 | } 318 | 319 | /** 320 | * setuUpdatedAt 321 | * 322 | * @param int $updated_at php time() 323 | */ 324 | public function setUpdatedAt($updated_at) 325 | { 326 | $this->__updated_at = $updated_at; 327 | 328 | return $this; 329 | } 330 | 331 | /** 332 | * field 333 | * 334 | * Gets property based on a string 335 | * 336 | * You can also use string separated by dots for nested arrays 337 | * key_1.key_2.key_3 etc 338 | * 339 | * @param string $field 340 | * @return string $context property 341 | */ 342 | public function field($field) 343 | { 344 | $parts = explode('.', $field); 345 | $context = $this->data; 346 | 347 | if ($field=='data') { 348 | return $context; 349 | } 350 | 351 | if ($field == '__created_at') { 352 | return $this->__created_at; 353 | } 354 | 355 | if ($field == '__updated_at') { 356 | return $this->__updated_at; 357 | } 358 | 359 | if ($field == '__id') { 360 | return $this->__id; 361 | } 362 | 363 | foreach($parts as $part) 364 | { 365 | if (trim($part) == '') 366 | { 367 | return false; 368 | } 369 | 370 | if (is_object($context)) 371 | { 372 | if(!property_exists($context, $part)) 373 | { 374 | return false; 375 | } 376 | 377 | $context = $context->{$part}; 378 | } 379 | else if (is_array($context)) 380 | { 381 | if(!array_key_exists($part, $context)) 382 | { 383 | return false; 384 | } 385 | 386 | $context = $context[$part]; 387 | } 388 | } 389 | 390 | return $context; 391 | } 392 | 393 | } 394 | -------------------------------------------------------------------------------- /tests/DatabaseTest.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/databases', 18 | 'read_only' => true 19 | ]); 20 | 21 | $this->assertRegExp('/[0-9]+\.[0-9]+\.[0-9]+/', $db->version()); 22 | } 23 | 24 | 25 | public function testNotWritable() 26 | { 27 | $this->expectException(\Exception::class); 28 | 29 | if (!is_dir(__DIR__.'/databases/cantedit')) 30 | { 31 | mkdir(__DIR__.'/databases/cantedit'); 32 | } 33 | 34 | chmod(__DIR__.'/databases/cantedit', 0444); 35 | 36 | $db = new \Filebase\Database([ 37 | 'dir' => __DIR__.'/databases/cantedit' 38 | ]); 39 | 40 | chmod(__DIR__.'/databases/cantedit', 0777); 41 | rmdir(__DIR__.'/databases/cantedit'); 42 | } 43 | 44 | 45 | public function testNotWritableButReadonly() 46 | { 47 | if (!is_dir(__DIR__.'/databases/cantedit')) 48 | { 49 | mkdir(__DIR__.'/databases/cantedit'); 50 | } 51 | 52 | chmod(__DIR__.'/databases/cantedit', 0444); 53 | 54 | $db = new \Filebase\Database([ 55 | 'dir' => __DIR__.'/databases/cantedit', 56 | 'read_only' => true 57 | ]); 58 | 59 | $this->assertEquals(true, true); 60 | 61 | chmod(__DIR__.'/databases/cantedit', 0777); 62 | rmdir(__DIR__.'/databases/cantedit'); 63 | } 64 | 65 | 66 | 67 | public function testDatabaseReadOnlyDelete() 68 | { 69 | $this->expectException(\Exception::class); 70 | 71 | $db = new \Filebase\Database([ 72 | 'dir' => __DIR__.'/databases' 73 | ]); 74 | 75 | $db->flush(true); 76 | $db->get('test1')->set(['key'=>'value'])->save(); 77 | 78 | $db2 = new \Filebase\Database([ 79 | 'dir' => __DIR__.'/databases', 80 | 'read_only' => true 81 | ]); 82 | 83 | $db2->get('test1')->delete(); 84 | } 85 | 86 | 87 | 88 | 89 | public function testReadonlyBadFlush() 90 | { 91 | $this->expectException(\Exception::class); 92 | 93 | $db = new \Filebase\Database([ 94 | 'dir' => __DIR__.'/databases', 95 | 'read_only' => true 96 | ]); 97 | 98 | $db->flush(true); 99 | } 100 | 101 | 102 | public function testReadonlyBadTurncate() 103 | { 104 | $this->expectException(\Exception::class); 105 | 106 | $db = new \Filebase\Database([ 107 | 'dir' => __DIR__.'/databases', 108 | 'read_only' => true 109 | ]); 110 | 111 | $db->truncate(); 112 | } 113 | 114 | 115 | 116 | public function testDatabaseBadSave() 117 | { 118 | $this->expectException(\Exception::class); 119 | 120 | $db = new \Filebase\Database([ 121 | 'dir' => __DIR__.'/databases', 122 | 'read_only' => true 123 | ]); 124 | 125 | $db->get('test1')->set(['key'=>'value'])->save(); 126 | } 127 | 128 | 129 | 130 | public function testMissingFormatClass() 131 | { 132 | $this->expectException(\Exception::class); 133 | 134 | $db = new \Filebase\Database([ 135 | 'dir' => __DIR__.'/databases', 136 | 'format' => '' 137 | ]); 138 | } 139 | 140 | 141 | public function testBadFormatClass() 142 | { 143 | $this->expectException(\Exception::class); 144 | 145 | $db = new \Filebase\Database([ 146 | 'dir' => __DIR__.'/databases', 147 | 'format' => badformat::class 148 | ]); 149 | } 150 | 151 | 152 | public function testDatabaseFlushTrue() 153 | { 154 | $db = new \Filebase\Database([ 155 | 'dir' => __DIR__.'/databases' 156 | ]); 157 | 158 | $db->flush(true); 159 | 160 | $db->get('test1')->set(['key'=>'value'])->save(); 161 | $db->get('test2')->set(['key'=>'value'])->save(); 162 | 163 | // true for all documents to be deleted. 164 | $this->assertEquals(true, $db->flush(true)); 165 | } 166 | 167 | 168 | public function testDatabaseTruncate() 169 | { 170 | $db = new \Filebase\Database([ 171 | 'dir' => __DIR__.'/databases/test_delete' 172 | ]); 173 | 174 | $db->flush(true); 175 | 176 | $db->get('test1')->set(['key'=>'value'])->save(); 177 | $db->get('test2')->set(['key'=>'value'])->save(); 178 | 179 | $this->assertEquals(true, $db->truncate()); 180 | 181 | $test = $db->get('test2'); 182 | $this->assertEquals(null, $test->key); 183 | } 184 | 185 | 186 | public function testDatabaseFlushFalse() 187 | { 188 | $this->expectException(\Exception::class); 189 | 190 | $db = new \Filebase\Database([ 191 | 'dir' => __DIR__.'/databases' 192 | ]); 193 | 194 | $db->flush(true); 195 | 196 | $db->get('test1')->set(['key'=>'value'])->save(); 197 | $db->get('test2')->set(['key'=>'value'])->save(); 198 | 199 | $db->flush(); 200 | } 201 | 202 | 203 | public function testDatabaseFindAllSimple() 204 | { 205 | $db = new \Filebase\Database([ 206 | 'dir' => __DIR__.'/databases' 207 | ]); 208 | 209 | $db->flush(true); 210 | 211 | $db->get('test1')->set(['key'=>'value'])->save(); 212 | $db->get('test2')->set(['key'=>'value'])->save(); 213 | 214 | $documents = $db->findAll(false); 215 | 216 | // should have 2 docs 217 | $this->assertEquals(2, count($documents)); 218 | 219 | // check if these equal correctly 220 | $this->assertEquals('test1', $documents[0]); 221 | $this->assertEquals('test2', $documents[1]); 222 | 223 | $db->flush(true); 224 | } 225 | 226 | 227 | public function testDatabaseFindAllDataOnly() 228 | { 229 | $db = new \Filebase\Database([ 230 | 'dir' => __DIR__.'/databases' 231 | ]); 232 | 233 | $db->flush(true); 234 | 235 | $db->get('test')->set(['key'=>'value'])->save(); 236 | 237 | $documents = $db->findAll(true,true); 238 | 239 | // should only have 1 doc 240 | $this->assertEquals(1, count($documents)); 241 | $this->assertEquals(['key'=>'value'], $documents[0]); 242 | 243 | $db->flush(true); 244 | } 245 | 246 | public function testDatabaseSavingNotEncodableDocument() 247 | { 248 | $this->expectException(SavingException::class); 249 | 250 | $db = new \Filebase\Database([ 251 | 'dir' => __DIR__.'/databases' 252 | ]); 253 | 254 | $doc = $db->get("testDatabaseSavingNotEncodableDocument"); 255 | 256 | // insert invalid utf-8 characters 257 | $doc->testProp = "\xB1\x31"; 258 | 259 | $doc->save(); 260 | } 261 | public function test_Call_Queryclass_methods_on_database_without_query_method() 262 | { 263 | $db = new \Filebase\Database([ 264 | 'dir' => __DIR__.'/databases/saved', 265 | 'cache' => true 266 | ]); 267 | 268 | $db->flush(true); 269 | 270 | for ($x = 1; $x <= 10; $x++) 271 | { 272 | $user = $db->get(uniqid()); 273 | $user->name = 'John'; 274 | $user->email = 'john@example.com'; 275 | $user->save(); 276 | } 277 | 278 | $results = $db->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); 279 | $result_from_cache = $db->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); 280 | 281 | $this->assertEquals(10, count($results)); 282 | $this->assertEquals(true, ($result_from_cache[0]->isCache())); 283 | 284 | $id = $result_from_cache[0]->getId(); 285 | $id2 = $result_from_cache[1]->getId(); 286 | 287 | // Change the name 288 | $result_from_cache[0]->name = 'Tim'; 289 | $result_from_cache[0]->save(); 290 | 291 | $results = $db 292 | ->where('name','=','John') 293 | ->andWhere('email','==','john@example.com') 294 | ->resultDocuments(); 295 | 296 | $this->assertEquals($id2, $results[0]->getId()); 297 | $this->assertEquals('John', $results[0]->name); 298 | 299 | $db->flush(true); 300 | } 301 | public function test_must_return_exception_on_non_exist_method() 302 | { 303 | $db = new \Filebase\Database([ 304 | 'dir' => __DIR__.'/databases/saved', 305 | 'cache' => true 306 | ]); 307 | 308 | $this->expectException(\BadMethodCallException::class); 309 | $results = $db->none('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); 310 | } 311 | /** 312 | * based on issue #41 313 | * results() returns document instead of array #41 314 | */ 315 | public function test_must_return_array_on_select_an_culomn_from_cache() 316 | { 317 | $db = new \Filebase\Database([ 318 | 'dir' => __DIR__.'/databases/saved', 319 | 'cache' => true 320 | ]); 321 | 322 | $db->flush(true); 323 | 324 | for ($x = 1; $x <= 10; $x++) 325 | { 326 | $user = $db->get(uniqid()); 327 | $user->name = 'John'; 328 | $user->email = 'john@example.com'; 329 | $user->save(); 330 | } 331 | 332 | $db->where('name','=','John')->andWhere('email','==','john@example.com')->select('email')->results(); 333 | $result_from_cache = $db->where('name','=','John')->andWhere('email','==','john@example.com')->select('email')->results(); 334 | 335 | $this->assertCount(10,$result_from_cache); 336 | $this->assertEquals(['email'=>'john@example.com'],$result_from_cache[0]); 337 | $this->assertInternalType('array', $result_from_cache[0]); 338 | $this->assertInternalType('string', $result_from_cache[0]['email']); 339 | $db->flush(true); 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/Database.php: -------------------------------------------------------------------------------- 1 | getVersion() 17 | */ 18 | const VERSION = '1.0.24'; 19 | 20 | /** 21 | * $config 22 | * 23 | * Stores all the configuration object settings 24 | * \Filebase\Config 25 | */ 26 | protected $config; 27 | 28 | /** 29 | * Database constructor. 30 | * 31 | * @param array $config 32 | * 33 | * @throws FilesystemException 34 | */ 35 | public function __construct(array $config = []) 36 | { 37 | $this->config = new Config($config); 38 | 39 | // if we are set to read only, don't care to look at the directory. 40 | if ($this->config->read_only === true) return false; 41 | 42 | // Check directory and create it if it doesn't exist 43 | if (!is_dir($this->config->dir)) 44 | { 45 | if (!@mkdir($this->config->dir, 0777, true)) 46 | { 47 | throw new FilesystemException(sprintf('`%s` doesn\'t exist and can\'t be created.', $this->config->dir)); 48 | } 49 | } 50 | else if (!is_writable($this->config->dir)) 51 | { 52 | throw new FilesystemException(sprintf('`%s` is not writable.', $this->config->dir)); 53 | } 54 | } 55 | 56 | /** 57 | * version 58 | * 59 | * gets the Filebase version 60 | * 61 | * @return VERSION 62 | */ 63 | public function version() 64 | { 65 | return self::VERSION; 66 | } 67 | 68 | /** 69 | * findAll() 70 | * 71 | * Finds all documents in database directory. 72 | * Then returns you a list of those documents. 73 | * 74 | * @param bool $include_documents (include all document objects in array) 75 | * @param bool $data_only (if true only return the documents data not the full object) 76 | * 77 | * @return array $items 78 | */ 79 | public function findAll($include_documents = true, $data_only = false) 80 | { 81 | $format = $this->config->format; 82 | 83 | $file_extension = $format::getFileExtension(); 84 | $file_location = $this->config->dir.'/'; 85 | 86 | $all_items = Filesystem::getAllFiles($file_location, $file_extension); 87 | if (!$include_documents) 88 | { 89 | return $all_items; 90 | } 91 | $items = []; 92 | 93 | foreach($all_items as $a) 94 | { 95 | if ($data_only === true) 96 | { 97 | $items[] = $this->get($a)->getData(); 98 | } 99 | else 100 | { 101 | $items[] = $this->get($a); 102 | } 103 | } 104 | 105 | return $items; 106 | } 107 | 108 | /** 109 | * get 110 | * 111 | * retrieves a single result (file) 112 | * 113 | * @param mixed $id 114 | * 115 | * @return $document \Filebase\Document object 116 | */ 117 | public function get($id) 118 | { 119 | $content = $this->read($id); 120 | 121 | $document = new Document($this); 122 | $document->setId($id); 123 | 124 | if ($content) 125 | { 126 | if (isset($content['__created_at'])) $document->setCreatedAt($content['__created_at']); 127 | if (isset($content['__updated_at'])) $document->setUpdatedAt($content['__updated_at']); 128 | 129 | $this->set($document,(isset($content['data']) ? $content['data'] : [])); 130 | } 131 | 132 | return $document; 133 | } 134 | 135 | /** 136 | * has 137 | * 138 | * Check if a record already exists 139 | * 140 | * @param mixed $id 141 | * 142 | * @return bool true/false 143 | */ 144 | public function has($id) 145 | { 146 | $format = $this->config->format; 147 | $record = Filesystem::read( $this->config->dir.'/'.Filesystem::validateName($id, $this->config->safe_filename).'.'.$format::getFileExtension() ); 148 | 149 | return $record ? true : false; 150 | } 151 | 152 | /** 153 | * backup 154 | * 155 | * @param string $location (optional) 156 | * 157 | * @return $document \Filebase\Backup object 158 | */ 159 | public function backup($location = '') 160 | { 161 | if ($location) 162 | { 163 | return new Backup($location, $this); 164 | } 165 | 166 | return new Backup($this->config->backupLocation, $this); 167 | } 168 | 169 | /** 170 | * set 171 | * 172 | * @param $document \Filebase\Document object 173 | * @param mixed $data should be an array 174 | * 175 | * @return $document \Filebase\Document object 176 | */ 177 | public function set(Document $document, $data) 178 | { 179 | if ($data) 180 | { 181 | foreach($data as $key => $value) 182 | { 183 | if (is_array($value)) $value = (array) $value; 184 | $document->{$key} = $value; 185 | } 186 | } 187 | 188 | return $document; 189 | } 190 | 191 | /** 192 | * count 193 | * 194 | * 195 | * @return int $total 196 | */ 197 | public function count() 198 | { 199 | return count($this->findAll(false)); 200 | } 201 | 202 | /** 203 | * @param Document $document 204 | * @param string $wdata 205 | * @return bool|Document 206 | * @throws SavingException 207 | */ 208 | public function save(Document $document, $wdata = '') 209 | { 210 | if ($this->config->read_only === true) 211 | { 212 | throw new SavingException("This database is set to be read-only. No modifications can be made."); 213 | } 214 | 215 | $format = $this->config->format; 216 | $id = $document->getId(); 217 | $file_extension = $format::getFileExtension(); 218 | $file_location = $this->config->dir.'/'.Filesystem::validateName($id, $this->config->safe_filename).'.'.$file_extension; 219 | $created = $document->createdAt(false); 220 | 221 | if (isset($wdata) && $wdata !== '') 222 | { 223 | $document = new Document( $this ); 224 | $document->setId($id); 225 | $document->set($wdata); 226 | $document->setCreatedAt($created); 227 | } 228 | 229 | if (!Filesystem::read($file_location) || $created==false) 230 | { 231 | $document->setCreatedAt(time()); 232 | } 233 | 234 | $document->setUpdatedAt(time()); 235 | 236 | try { 237 | $data = $format::encode( $document->saveAs(), $this->config->pretty ); 238 | } catch (EncodingException $e) { 239 | // TODO: add logging 240 | throw new SavingException("Can not encode document.", 0, $e); 241 | } 242 | 243 | if (Filesystem::write($file_location, $data)) 244 | { 245 | $this->flushCache(); 246 | 247 | return $document; 248 | } 249 | 250 | return false; 251 | } 252 | 253 | /** 254 | * query 255 | * 256 | * 257 | */ 258 | public function query() 259 | { 260 | return new Query($this); 261 | } 262 | 263 | /** 264 | * Read and return Document from filesystem by name. 265 | * If doesn't exists return new empty Document. 266 | * 267 | * @param $name 268 | * 269 | * @throws Exception|ReadingException 270 | * @return array|null 271 | */ 272 | protected function read($name) 273 | { 274 | $format = $this->config->format; 275 | 276 | $file = Filesystem::read( 277 | $this->config->dir . '/' 278 | . Filesystem::validateName($name, $this->config->safe_filename) 279 | . '.' . $format::getFileExtension() 280 | ); 281 | 282 | if ($file !== false) { 283 | return $format::decode($file); 284 | } 285 | 286 | return null; 287 | } 288 | 289 | /** 290 | * delete 291 | * 292 | * @param $document \Filebase\Document object 293 | * @return (bool) true/false if file was deleted 294 | */ 295 | public function delete(Document $document) 296 | { 297 | if ($this->config->read_only === true) 298 | { 299 | throw new Exception("This database is set to be read-only. No modifications can be made."); 300 | } 301 | 302 | $format = $this->config->format; 303 | 304 | $d = Filesystem::delete($this->config->dir.'/'.Filesystem::validateName($document->getId(), $this->config->safe_filename).'.'.$format::getFileExtension()); 305 | 306 | $this->flushCache(); 307 | 308 | return $d; 309 | } 310 | 311 | /** 312 | * truncate 313 | * 314 | * Alias for flush(true) 315 | * 316 | * @return @see flush 317 | */ 318 | public function truncate() 319 | { 320 | return $this->flush(true); 321 | } 322 | 323 | /** 324 | * flush 325 | * 326 | * This will DELETE all the documents within the database 327 | * 328 | * @param bool $confirm (confirmation before proceeding) 329 | * @return void 330 | */ 331 | public function flush($confirm = false) 332 | { 333 | if ($this->config->read_only === true) 334 | { 335 | throw new Exception("This database is set to be read-only. No modifications can be made."); 336 | } 337 | 338 | if ($confirm!==true) 339 | { 340 | throw new Exception("Database Flush failed. You must send in TRUE to confirm action."); 341 | } 342 | 343 | $format = $this->config->format; 344 | $documents = $this->findAll(false); 345 | foreach($documents as $document) 346 | { 347 | Filesystem::delete($this->config->dir.'/'.$document.'.'.$format::getFileExtension()); 348 | } 349 | 350 | if ($this->count() === 0) 351 | { 352 | return true; 353 | } 354 | 355 | throw new Exception("Could not delete all database files in ".$this->config->dir); 356 | } 357 | 358 | /** 359 | * flushCache 360 | * 361 | * 362 | */ 363 | public function flushCache() 364 | { 365 | if ($this->getConfig()->cache===true) 366 | { 367 | $cache = new Cache($this); 368 | $cache->flush(); 369 | } 370 | } 371 | 372 | /** 373 | * toArray 374 | * 375 | * @param \Filebase\Document 376 | * @return array 377 | */ 378 | public function toArray(Document $document) 379 | { 380 | return $this->objectToArray( $document->getData() ); 381 | } 382 | 383 | /** 384 | * objectToArray 385 | * 386 | */ 387 | public function objectToArray($obj) 388 | { 389 | if (!is_object($obj) && !is_array($obj)) 390 | { 391 | return $obj; 392 | } 393 | 394 | $arr = []; 395 | foreach ($obj as $key => $value) 396 | { 397 | $arr[$key] = $this->objectToArray($value); 398 | } 399 | 400 | return $arr; 401 | } 402 | 403 | /** 404 | * getConfig 405 | * 406 | * @return $config 407 | */ 408 | public function getConfig() 409 | { 410 | return $this->config; 411 | } 412 | 413 | /** 414 | * __call 415 | * 416 | * Magic method to give us access to query methods on db class 417 | * 418 | */ 419 | public function __call($method,$args) 420 | { 421 | if(method_exists($this,$method)) { 422 | return $this->$method(...$args); 423 | } 424 | 425 | if(method_exists(Query::class,$method)) { 426 | return (new Query($this))->$method(...$args); 427 | } 428 | 429 | throw new \BadMethodCallException("method {$method} not found on 'Database::class' and 'Query::class'"); 430 | } 431 | 432 | } 433 | -------------------------------------------------------------------------------- /tests/DocumentTest.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/databases', 20 | 'cache' => false 21 | ]); 22 | 23 | $db->flush(true); 24 | 25 | // save data 26 | $doc = $db->get('test_save')->save(['key'=>'value']); 27 | 28 | // get saved data (put into array) 29 | $val = $db->get('test_save'); 30 | 31 | // should equal... 32 | $this->assertEquals('value', $val->key); 33 | 34 | $db->flush(true); 35 | } 36 | 37 | 38 | //-------------------------------------------------------------------- 39 | 40 | 41 | 42 | 43 | /** 44 | * testDoesNotExist() 45 | * 46 | * TEST CASE: 47 | * - Save document with data 48 | * - Get the document 49 | * - Check that the data is there and the document exist 50 | * 51 | */ 52 | public function testDoesNotExist() 53 | { 54 | $db = new \Filebase\Database([ 55 | 'dir' => __DIR__.'/databases', 56 | 'cache' => false 57 | ]); 58 | 59 | $db->flush(true); 60 | 61 | // get saved data (put into array) 62 | $doc = $db->get('doesexist')->save(['key'=>'value']); 63 | 64 | $this->assertEquals(true, $db->has('doesexist')); 65 | 66 | $this->assertEquals(false, $db->has('doesnotexist')); 67 | 68 | $db->flush(true); 69 | } 70 | 71 | 72 | //-------------------------------------------------------------------- 73 | 74 | 75 | 76 | 77 | /** 78 | * testSetIdGetId() 79 | * 80 | * TEST CASE: 81 | * - Set and Get Id 82 | * 83 | */ 84 | public function testSetIdGetId() 85 | { 86 | $db = new \Filebase\Database([ 87 | 'dir' => __DIR__.'/databases/data_rename', 88 | 'cache' => false 89 | ]); 90 | 91 | // save data 92 | $doc = $db->get('name_1')->save(['key'=>'value']); 93 | $this->assertEquals('name_1', $doc->getId()); 94 | 95 | // delete existing doc so its not duplicated 96 | // object still exist, but file has been removed 97 | $doc->delete(); 98 | $this->assertEquals('name_1', $doc->getId()); 99 | 100 | // change id and save (new file is created) 101 | $doc->setId('name_2')->save(); 102 | $this->assertEquals('name_2', $doc->getId()); 103 | } 104 | 105 | 106 | //-------------------------------------------------------------------- 107 | 108 | 109 | /** 110 | * testSetValue() 111 | * 112 | * TEST CASE: 113 | * - Using the set method, set the value in object ( DO NOT SAVE ) 114 | * - Check that the properties are in the object (matching) 115 | * 116 | */ 117 | public function testSetValue() 118 | { 119 | $db = new \Filebase\Database([ 120 | 'dir' => __DIR__.'/databases', 121 | 'cache' => false 122 | ]); 123 | 124 | $db->flush(true); 125 | 126 | // FIRST TEST 127 | // use the set() method 128 | $test1 = $db->get('test1')->set(['key'=>'value']); 129 | 130 | $this->assertEquals('value', $test1->key); 131 | 132 | 133 | // SECOND TEST: 134 | // use the property setter 135 | $test2 = $db->get('test2'); 136 | $test2->key = 'value'; 137 | 138 | $this->assertEquals('value', $test2->key); 139 | 140 | 141 | // THIRD TEST (null test) 142 | $test3 = $db->get('test3'); 143 | 144 | $this->assertEquals(null, $test3->key); 145 | 146 | } 147 | 148 | 149 | //-------------------------------------------------------------------- 150 | 151 | 152 | /** 153 | * testIssetUnsetUnknown() 154 | * 155 | * TEST CASE: 156 | * - Check if property isset 157 | * - Unset property and see if it now returns null 158 | * 159 | */ 160 | public function testIssetUnset() 161 | { 162 | $db = new \Filebase\Database([ 163 | 'dir' => __DIR__.'/databases', 164 | 'cache' => false 165 | ]); 166 | 167 | $db->flush(true); 168 | 169 | $test = $db->get('test2'); 170 | $test->key = 'value'; 171 | 172 | $this->assertEquals('value', $test->key); 173 | 174 | $this->assertEquals(1, isset($test->key)); 175 | 176 | unset($test->key); 177 | 178 | $this->assertEquals(null, ($test->key)); 179 | 180 | } 181 | 182 | 183 | //-------------------------------------------------------------------- 184 | 185 | 186 | public function testArraySetValueSave() 187 | { 188 | $db = new \Filebase\Database([ 189 | 'dir' => __DIR__.'/databases' 190 | ]); 191 | 192 | $db->flush(true); 193 | 194 | $db->get('test')->set(['key'=>'value'])->save(); 195 | 196 | $test = $db->get('test'); 197 | 198 | $this->assertEquals('value', $test->key); 199 | 200 | $db->flush(true); 201 | } 202 | 203 | 204 | public function testPropertySetValueSave() 205 | { 206 | $db = new \Filebase\Database([ 207 | 'dir' => __DIR__.'/databases' 208 | ]); 209 | 210 | $db->flush(true); 211 | 212 | $test = $db->get('test'); 213 | $test->key = 'value'; 214 | $test->save(); 215 | 216 | $test = $db->get('test'); 217 | 218 | $this->assertEquals('value', $test->key); 219 | 220 | $db->flush(true); 221 | } 222 | 223 | 224 | public function testToArray() 225 | { 226 | $db = new \Filebase\Database([ 227 | 'dir' => __DIR__.'/databases' 228 | ]); 229 | 230 | $db->flush(true); 231 | 232 | $db->get('test')->set(['key'=>'value'])->save(); 233 | 234 | $test = $db->get('test')->toArray(); 235 | 236 | $this->assertEquals('value', $test['key']); 237 | 238 | $db->flush(true); 239 | } 240 | 241 | 242 | public function testDelete() 243 | { 244 | $db = new \Filebase\Database([ 245 | 'dir' => __DIR__.'/databases' 246 | ]); 247 | 248 | $db->flush(true); 249 | 250 | $db->get('test')->set(['key'=>'value'])->save(); 251 | 252 | $test = $db->get('test')->delete(); 253 | 254 | $this->assertEquals(true, $test); 255 | 256 | $db->flush(true); 257 | } 258 | 259 | 260 | public function testGetId() 261 | { 262 | $db = new \Filebase\Database([ 263 | 'dir' => __DIR__.'/databases' 264 | ]); 265 | 266 | $db->flush(true); 267 | 268 | $db->get('test')->set(['key'=>'value'])->save(); 269 | 270 | $test = $db->get('test'); 271 | 272 | $this->assertEquals('test', $test->getId()); 273 | 274 | $db->flush(true); 275 | } 276 | 277 | 278 | public function testSetId() 279 | { 280 | $db = new \Filebase\Database([ 281 | 'dir' => __DIR__.'/databases' 282 | ]); 283 | 284 | $db->flush(true); 285 | 286 | $db->get('test')->set(['key'=>'value'])->save(); 287 | 288 | $test = $db->get('test')->setId('newid'); 289 | 290 | $this->assertEquals('newid', $test->getId()); 291 | 292 | $db->flush(true); 293 | } 294 | 295 | 296 | 297 | // DATE TESTS 298 | //-------------------------------------------------------------------- 299 | 300 | public function testDates() 301 | { 302 | $db = new \Filebase\Database([ 303 | 'dir' => __DIR__.'/databases' 304 | ]); 305 | 306 | $db->flush(true); 307 | 308 | $db->get('test')->set(['key'=>'value'])->save(); 309 | 310 | $createdAt = strtotime($db->get('test')->createdAt()); 311 | $updatedAt = strtotime($db->get('test')->updatedAt()); 312 | 313 | $this->assertEquals(date('Y-m-d'), date('Y-m-d',$createdAt)); 314 | $this->assertEquals(date('Y-m-d'), date('Y-m-d',$updatedAt)); 315 | 316 | $db->flush(true); 317 | } 318 | 319 | 320 | public function testFormatDates() 321 | { 322 | $db = new \Filebase\Database([ 323 | 'dir' => __DIR__.'/databases' 324 | ]); 325 | 326 | $db->flush(true); 327 | 328 | $db->get('test')->set(['key'=>'value'])->save(); 329 | 330 | $createdAt = $db->get('test')->createdAt('Y-m-d'); 331 | $updatedAt = $db->get('test')->updatedAt('Y-m-d'); 332 | 333 | $this->assertEquals(date('Y-m-d'), $createdAt); 334 | $this->assertEquals(date('Y-m-d'), $updatedAt); 335 | 336 | $db->flush(true); 337 | } 338 | 339 | 340 | public function testNoFormatDates() 341 | { 342 | $db = new \Filebase\Database([ 343 | 'dir' => __DIR__.'/databases' 344 | ]); 345 | 346 | $db->flush(true); 347 | 348 | $db->get('test')->set(['key'=>'value'])->save(); 349 | 350 | $createdAt = $db->get('test')->createdAt(false); 351 | $updatedAt = $db->get('test')->updatedAt(false); 352 | 353 | $this->assertEquals(date('Y-m-d'), date('Y-m-d',$createdAt)); 354 | $this->assertEquals(date('Y-m-d'), date('Y-m-d',$updatedAt)); 355 | 356 | $db->flush(true); 357 | } 358 | 359 | 360 | public function testMissingUpdatedDate() 361 | { 362 | $db = new \Filebase\Database([ 363 | 'dir' => __DIR__.'/databases' 364 | ]); 365 | 366 | $db->flush(true); 367 | 368 | $db->get('test')->set(['key'=>'value'])->save(); 369 | 370 | $setUpdatedAt = $db->get('test')->setUpdatedAt(null); 371 | $setCreatedAt = $db->get('test')->setCreatedAt(null); 372 | 373 | $this->assertEquals(date('Y-m-d'), $setCreatedAt->updatedAt('Y-m-d')); 374 | $this->assertEquals(date('Y-m-d'), $setUpdatedAt->updatedAt('Y-m-d')); 375 | 376 | $db->flush(true); 377 | } 378 | 379 | 380 | //-------------------------------------------------------------------- 381 | 382 | 383 | public function testCustomFilter() 384 | { 385 | $db = new \Filebase\Database([ 386 | 'dir' => __DIR__.'/databases' 387 | ]); 388 | 389 | $db->flush(true); 390 | 391 | $u = []; 392 | $u[] = [ 393 | 'email' => 'email@email.com', 394 | 'status' => 'blocked' 395 | ]; 396 | 397 | $u[] = [ 398 | 'email' => 'notblocked@email.com', 399 | 'status' => 'enabled' 400 | ]; 401 | 402 | $db->get('users')->set($u)->save(); 403 | 404 | $users = $db->get('users')->customFilter('data',function($item) { 405 | return (($item['status']=='blocked') ? $item['email'] : false); 406 | }); 407 | 408 | $this->assertEquals(1, count($users)); 409 | $this->assertEquals('email@email.com', $users[0]); 410 | 411 | $db->flush(true); 412 | } 413 | 414 | 415 | public function testCustomFilterParam() 416 | { 417 | $db = new \Filebase\Database([ 418 | 'dir' => __DIR__.'/databases' 419 | ]); 420 | 421 | $db->flush(true); 422 | 423 | $u = []; 424 | $u[] = [ 425 | 'email' => 'email@email.com', 426 | 'status' => 'blocked' 427 | ]; 428 | 429 | $u[] = [ 430 | 'email' => 'notblocked@email.com', 431 | 'status' => 'enabled' 432 | ]; 433 | 434 | $db->get('users')->set($u)->save(); 435 | 436 | $users = $db->get('users')->customFilter('data','enabled',function($item, $status) { 437 | return (($item['status']==$status) ? $item['email'] : false); 438 | }); 439 | 440 | $this->assertEquals(1, count($users)); 441 | $this->assertEquals('notblocked@email.com', $users[0]); 442 | 443 | $db->flush(true); 444 | } 445 | 446 | 447 | 448 | public function testCustomFilterParamIndex() 449 | { 450 | $db = new \Filebase\Database([ 451 | 'dir' => __DIR__.'/databases' 452 | ]); 453 | 454 | $db->flush(true); 455 | 456 | $u = []; 457 | $u[] = [ 458 | 'email' => 'enabled-email@email.com', 459 | 'id' => '123', 460 | 'status' => 'deactive' 461 | ]; 462 | 463 | $u[] = [ 464 | 'email' => 'enabled-email@email.com', 465 | 'id' => '321', 466 | 'status' => 'enabled' 467 | ]; 468 | 469 | $db->get('users_test_custom')->save($u); 470 | 471 | $users = $db->get('users_test_custom')->filter('data','enabled',function($item, $status) { 472 | return (($item['status']==$status) ? $item : false); 473 | }); 474 | 475 | 476 | $this->assertEquals(1, count($users)); 477 | $this->assertEquals('enabled-email@email.com', $users[0]['email']); 478 | 479 | $db->flush(true); 480 | } 481 | 482 | 483 | public function testCustomFilterEmpty() 484 | { 485 | $db = new \Filebase\Database([ 486 | 'dir' => __DIR__.'/databases' 487 | ]); 488 | 489 | $db->flush(true); 490 | 491 | $db->get('customfilter_test')->set(['email'=>'time'])->save(); 492 | 493 | $users = $db->get('customfilter_test')->customFilter('email',function($item) { 494 | return (($item['status']=='blocked') ? $item['email'] : false); 495 | }); 496 | 497 | // should be empty array 498 | $this->assertEquals([],$users); 499 | 500 | $db->flush(true); 501 | } 502 | 503 | 504 | public function testFieldMethod() 505 | { 506 | $db = new \Filebase\Database([ 507 | 'dir' => __DIR__.'/databases' 508 | ]); 509 | 510 | $db->flush(true); 511 | 512 | $db->get('user_test_email_1')->set(['email'=>'example@example.com'])->save(); 513 | 514 | $f = $db->get('user_test_email_1')->field('email'); 515 | 516 | $this->assertEquals('example@example.com', $f); 517 | 518 | $db->flush(true); 519 | } 520 | 521 | public function testFieldId() 522 | { 523 | $db = new \Filebase\Database([ 524 | 'dir' => __DIR__.'/databases' 525 | ]); 526 | 527 | $db->flush(true); 528 | 529 | $db->get('vegetables')->set(['broccoli'=>'27'])->save(); 530 | 531 | $expected = time(); 532 | $actual = $db->get('vegetables')->field('__created_at'); 533 | $this->assertEquals($expected, $actual); 534 | 535 | $actual = $db->get('vegetables')->field('__updated_at'); 536 | $this->assertEquals($expected, $actual); 537 | 538 | $db->get('weather')->set(['cityname'=>'condition1'])->save(); 539 | 540 | $actual = $db->get('weather')->field('__id'); 541 | $this->assertEquals('weather', $actual); 542 | 543 | $db->flush(true); 544 | } 545 | 546 | 547 | public function testNestedFieldMethod() 548 | { 549 | $db = new \Filebase\Database([ 550 | 'dir' => __DIR__.'/databases' 551 | ]); 552 | 553 | $db->flush(true); 554 | 555 | $db->get('user_test_email_2')->set([ 556 | 'profile' => [ 557 | 'email' => 'example@example.com' 558 | ] 559 | ])->save(); 560 | 561 | $f = $db->get('user_test_email_2')->field('profile.email'); 562 | 563 | $this->assertEquals('example@example.com', $f); 564 | 565 | $db->flush(true); 566 | } 567 | 568 | 569 | public function testBadNameException() 570 | { 571 | $this->expectException(\Exception::class); 572 | 573 | $db = new \Filebase\Database([ 574 | 'dir' => __DIR__.'/databases', 575 | 'safe_filename' => false 576 | ]); 577 | 578 | $db->flush(true); 579 | 580 | $file = $db->get('^*bad_@name%$1#'); 581 | 582 | $db->flush(true); 583 | } 584 | 585 | 586 | public function testBadNameReplacement() 587 | { 588 | $badName = 'ti^@%mo!!~th*y-m_?a(ro%)is.&'; 589 | $newName = Filesystem::validateName($badName, true); 590 | 591 | $this->assertEquals('timothy-m_arois', $newName); 592 | } 593 | 594 | 595 | public function testBadNameReplacementLong() 596 | { 597 | $badName = '1234567890123456789012345678901234567890123456789012345678901234'; 598 | $newName = Filesystem::validateName($badName, true); 599 | 600 | $this->assertEquals(63, (strlen($newName)) ); 601 | $this->assertEquals('123456789012345678901234567890123456789012345678901234567890123', $newName); 602 | } 603 | 604 | } 605 | -------------------------------------------------------------------------------- /tests/DocumentYamlTest.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/databases', 20 | 'cache' => false, 21 | 'format' => \Filebase\Format\Yaml::class 22 | ]); 23 | 24 | $db->flush(true); 25 | 26 | // save data 27 | $doc = $db->get('test_save')->save(['key'=>'value']); 28 | 29 | // get saved data (put into array) 30 | $val = $db->get('test_save'); 31 | 32 | // should equal... 33 | $this->assertEquals('value', $val->key); 34 | 35 | #$db->flush(true); 36 | } 37 | 38 | 39 | //-------------------------------------------------------------------- 40 | 41 | 42 | 43 | 44 | /** 45 | * testDoesNotExist() 46 | * 47 | * TEST CASE: 48 | * - Save document with data 49 | * - Get the document 50 | * - Check that the data is there and the document exist 51 | * 52 | */ 53 | public function testDoesNotExist() 54 | { 55 | $db = new \Filebase\Database([ 56 | 'dir' => __DIR__.'/databases', 57 | 'cache' => false, 58 | 'format' => \Filebase\Format\Yaml::class 59 | ]); 60 | 61 | $db->flush(true); 62 | 63 | // get saved data (put into array) 64 | $doc = $db->get('doesexist')->save(['key'=>'value']); 65 | 66 | $this->assertEquals(true, $db->has('doesexist')); 67 | 68 | $this->assertEquals(false, $db->has('doesnotexist')); 69 | 70 | $db->flush(true); 71 | } 72 | 73 | 74 | //-------------------------------------------------------------------- 75 | 76 | 77 | 78 | 79 | /** 80 | * testSetIdGetId() 81 | * 82 | * TEST CASE: 83 | * - Set and Get Id 84 | * 85 | */ 86 | public function testSetIdGetId() 87 | { 88 | $db = new \Filebase\Database([ 89 | 'dir' => __DIR__.'/databases/data_rename', 90 | 'cache' => false, 91 | 'format' => \Filebase\Format\Yaml::class 92 | ]); 93 | 94 | // save data 95 | $doc = $db->get('name_1')->save(['key'=>'value']); 96 | $this->assertEquals('name_1', $doc->getId()); 97 | 98 | // delete existing doc so its not duplicated 99 | // object still exist, but file has been removed 100 | $doc->delete(); 101 | $this->assertEquals('name_1', $doc->getId()); 102 | 103 | // change id and save (new file is created) 104 | $doc->setId('name_2')->save(); 105 | $this->assertEquals('name_2', $doc->getId()); 106 | } 107 | 108 | 109 | //-------------------------------------------------------------------- 110 | 111 | 112 | /** 113 | * testSetValue() 114 | * 115 | * TEST CASE: 116 | * - Using the set method, set the value in object ( DO NOT SAVE ) 117 | * - Check that the properties are in the object (matching) 118 | * 119 | */ 120 | public function testSetValue() 121 | { 122 | $db = new \Filebase\Database([ 123 | 'dir' => __DIR__.'/databases', 124 | 'cache' => false, 125 | 'format' => \Filebase\Format\Yaml::class 126 | ]); 127 | 128 | $db->flush(true); 129 | 130 | // FIRST TEST 131 | // use the set() method 132 | $test1 = $db->get('test1')->set(['key'=>'value']); 133 | 134 | $this->assertEquals('value', $test1->key); 135 | 136 | 137 | // SECOND TEST: 138 | // use the property setter 139 | $test2 = $db->get('test2'); 140 | $test2->key = 'value'; 141 | 142 | $this->assertEquals('value', $test2->key); 143 | 144 | 145 | // THIRD TEST (null test) 146 | $test3 = $db->get('test3'); 147 | 148 | $this->assertEquals(null, $test3->key); 149 | 150 | } 151 | 152 | 153 | //-------------------------------------------------------------------- 154 | 155 | 156 | /** 157 | * testIssetUnsetUnknown() 158 | * 159 | * TEST CASE: 160 | * - Check if property isset 161 | * - Unset property and see if it now returns null 162 | * 163 | */ 164 | public function testIssetUnset() 165 | { 166 | $db = new \Filebase\Database([ 167 | 'dir' => __DIR__.'/databases', 168 | 'cache' => false, 169 | 'format' => \Filebase\Format\Yaml::class 170 | ]); 171 | 172 | $db->flush(true); 173 | 174 | $test = $db->get('test2'); 175 | $test->key = 'value'; 176 | 177 | $this->assertEquals('value', $test->key); 178 | 179 | $this->assertEquals(1, isset($test->key)); 180 | 181 | unset($test->key); 182 | 183 | $this->assertEquals(null, ($test->key)); 184 | 185 | } 186 | 187 | 188 | //-------------------------------------------------------------------- 189 | 190 | 191 | public function testArraySetValueSave() 192 | { 193 | $db = new \Filebase\Database([ 194 | 'dir' => __DIR__.'/databases', 195 | 'format' => \Filebase\Format\Yaml::class 196 | ]); 197 | 198 | $db->flush(true); 199 | 200 | $db->get('test')->set(['key'=>'value'])->save(); 201 | 202 | $test = $db->get('test'); 203 | 204 | $this->assertEquals('value', $test->key); 205 | 206 | $db->flush(true); 207 | } 208 | 209 | 210 | public function testPropertySetValueSave() 211 | { 212 | $db = new \Filebase\Database([ 213 | 'dir' => __DIR__.'/databases', 214 | 'format' => \Filebase\Format\Yaml::class 215 | ]); 216 | 217 | $db->flush(true); 218 | 219 | $test = $db->get('test'); 220 | $test->key = 'value'; 221 | $test->save(); 222 | 223 | $test = $db->get('test'); 224 | 225 | $this->assertEquals('value', $test->key); 226 | 227 | $db->flush(true); 228 | } 229 | 230 | 231 | public function testToArray() 232 | { 233 | $db = new \Filebase\Database([ 234 | 'dir' => __DIR__.'/databases', 235 | 'format' => \Filebase\Format\Yaml::class 236 | ]); 237 | 238 | $db->flush(true); 239 | 240 | $db->get('test')->set(['key'=>'value'])->save(); 241 | 242 | $test = $db->get('test')->toArray(); 243 | 244 | $this->assertEquals('value', $test['key']); 245 | 246 | $db->flush(true); 247 | } 248 | 249 | 250 | public function testDelete() 251 | { 252 | $db = new \Filebase\Database([ 253 | 'dir' => __DIR__.'/databases', 254 | 'format' => \Filebase\Format\Yaml::class 255 | ]); 256 | 257 | $db->flush(true); 258 | 259 | $db->get('test')->set(['key'=>'value'])->save(); 260 | 261 | $test = $db->get('test')->delete(); 262 | 263 | $this->assertEquals(true, $test); 264 | 265 | $db->flush(true); 266 | } 267 | 268 | 269 | public function testGetId() 270 | { 271 | $db = new \Filebase\Database([ 272 | 'dir' => __DIR__.'/databases', 273 | 'format' => \Filebase\Format\Yaml::class 274 | ]); 275 | 276 | $db->flush(true); 277 | 278 | $db->get('test')->set(['key'=>'value'])->save(); 279 | 280 | $test = $db->get('test'); 281 | 282 | $this->assertEquals('test', $test->getId()); 283 | 284 | $db->flush(true); 285 | } 286 | 287 | 288 | public function testSetId() 289 | { 290 | $db = new \Filebase\Database([ 291 | 'dir' => __DIR__.'/databases', 292 | 'format' => \Filebase\Format\Yaml::class 293 | ]); 294 | 295 | $db->flush(true); 296 | 297 | $db->get('test')->set(['key'=>'value'])->save(); 298 | 299 | $test = $db->get('test')->setId('newid'); 300 | 301 | $this->assertEquals('newid', $test->getId()); 302 | 303 | $db->flush(true); 304 | } 305 | 306 | 307 | 308 | // DATE TESTS 309 | //-------------------------------------------------------------------- 310 | 311 | public function testDates() 312 | { 313 | $db = new \Filebase\Database([ 314 | 'dir' => __DIR__.'/databases', 315 | 'format' => \Filebase\Format\Yaml::class 316 | ]); 317 | 318 | $db->flush(true); 319 | 320 | $db->get('test')->set(['key'=>'value'])->save(); 321 | 322 | $createdAt = strtotime($db->get('test')->createdAt()); 323 | $updatedAt = strtotime($db->get('test')->updatedAt()); 324 | 325 | $this->assertEquals(date('Y-m-d'), date('Y-m-d',$createdAt)); 326 | $this->assertEquals(date('Y-m-d'), date('Y-m-d',$updatedAt)); 327 | 328 | $db->flush(true); 329 | } 330 | 331 | 332 | public function testFormatDates() 333 | { 334 | $db = new \Filebase\Database([ 335 | 'dir' => __DIR__.'/databases', 336 | 'format' => \Filebase\Format\Yaml::class 337 | ]); 338 | 339 | $db->flush(true); 340 | 341 | $db->get('test')->set(['key'=>'value'])->save(); 342 | 343 | $createdAt = $db->get('test')->createdAt('Y-m-d'); 344 | $updatedAt = $db->get('test')->updatedAt('Y-m-d'); 345 | 346 | $this->assertEquals(date('Y-m-d'), $createdAt); 347 | $this->assertEquals(date('Y-m-d'), $updatedAt); 348 | 349 | $db->flush(true); 350 | } 351 | 352 | 353 | public function testNoFormatDates() 354 | { 355 | $db = new \Filebase\Database([ 356 | 'dir' => __DIR__.'/databases', 357 | 'format' => \Filebase\Format\Yaml::class 358 | ]); 359 | 360 | $db->flush(true); 361 | 362 | $db->get('test')->set(['key'=>'value'])->save(); 363 | 364 | $createdAt = $db->get('test')->createdAt(false); 365 | $updatedAt = $db->get('test')->updatedAt(false); 366 | 367 | $this->assertEquals(date('Y-m-d'), date('Y-m-d',$createdAt)); 368 | $this->assertEquals(date('Y-m-d'), date('Y-m-d',$updatedAt)); 369 | 370 | $db->flush(true); 371 | } 372 | 373 | 374 | public function testMissingUpdatedDate() 375 | { 376 | $db = new \Filebase\Database([ 377 | 'dir' => __DIR__.'/databases', 378 | 'format' => \Filebase\Format\Yaml::class 379 | ]); 380 | 381 | $db->flush(true); 382 | 383 | $db->get('test')->set(['key'=>'value'])->save(); 384 | 385 | $setUpdatedAt = $db->get('test')->setUpdatedAt(null); 386 | $setCreatedAt = $db->get('test')->setCreatedAt(null); 387 | 388 | $this->assertEquals(date('Y-m-d'), $setCreatedAt->updatedAt('Y-m-d')); 389 | $this->assertEquals(date('Y-m-d'), $setUpdatedAt->updatedAt('Y-m-d')); 390 | 391 | $db->flush(true); 392 | } 393 | 394 | 395 | //-------------------------------------------------------------------- 396 | 397 | 398 | public function testCustomFilter() 399 | { 400 | $db = new \Filebase\Database([ 401 | 'dir' => __DIR__.'/databases', 402 | 'format' => \Filebase\Format\Yaml::class 403 | ]); 404 | 405 | $db->flush(true); 406 | 407 | $u = []; 408 | $u[] = [ 409 | 'email' => 'email@email.com', 410 | 'status' => 'blocked' 411 | ]; 412 | 413 | $u[] = [ 414 | 'email' => 'notblocked@email.com', 415 | 'status' => 'enabled' 416 | ]; 417 | 418 | $db->get('users')->set($u)->save(); 419 | 420 | $users = $db->get('users')->customFilter('data',function($item) { 421 | return (($item['status']=='blocked') ? $item['email'] : false); 422 | }); 423 | 424 | $this->assertEquals(1, count($users)); 425 | $this->assertEquals('email@email.com', $users[0]); 426 | 427 | $db->flush(true); 428 | } 429 | 430 | 431 | public function testCustomFilterParam() 432 | { 433 | $db = new \Filebase\Database([ 434 | 'dir' => __DIR__.'/databases', 435 | 'format' => \Filebase\Format\Yaml::class 436 | ]); 437 | 438 | $db->flush(true); 439 | 440 | $u = []; 441 | $u[] = [ 442 | 'email' => 'email@email.com', 443 | 'status' => 'blocked' 444 | ]; 445 | 446 | $u[] = [ 447 | 'email' => 'notblocked@email.com', 448 | 'status' => 'enabled' 449 | ]; 450 | 451 | $db->get('users')->set($u)->save(); 452 | 453 | $users = $db->get('users')->customFilter('data','enabled',function($item, $status) { 454 | return (($item['status']==$status) ? $item['email'] : false); 455 | }); 456 | 457 | $this->assertEquals(1, count($users)); 458 | $this->assertEquals('notblocked@email.com', $users[0]); 459 | 460 | $db->flush(true); 461 | } 462 | 463 | 464 | 465 | public function testCustomFilterParamIndex() 466 | { 467 | $db = new \Filebase\Database([ 468 | 'dir' => __DIR__.'/databases', 469 | 'format' => \Filebase\Format\Yaml::class 470 | ]); 471 | 472 | $db->flush(true); 473 | 474 | $u = []; 475 | $u[] = [ 476 | 'email' => 'enabled-email@email.com', 477 | 'id' => '123', 478 | 'status' => 'deactive' 479 | ]; 480 | 481 | $u[] = [ 482 | 'email' => 'enabled-email@email.com', 483 | 'id' => '321', 484 | 'status' => 'enabled' 485 | ]; 486 | 487 | $db->get('users_test_custom')->save($u); 488 | 489 | $users = $db->get('users_test_custom')->filter('data','enabled',function($item, $status) { 490 | return (($item['status']==$status) ? $item : false); 491 | }); 492 | 493 | 494 | $this->assertEquals(1, count($users)); 495 | $this->assertEquals('enabled-email@email.com', $users[0]['email']); 496 | 497 | $db->flush(true); 498 | } 499 | 500 | 501 | public function testCustomFilterEmpty() 502 | { 503 | $db = new \Filebase\Database([ 504 | 'dir' => __DIR__.'/databases', 505 | 'format' => \Filebase\Format\Yaml::class 506 | ]); 507 | 508 | $db->flush(true); 509 | 510 | $db->get('customfilter_test')->set(['email'=>'time'])->save(); 511 | 512 | $users = $db->get('customfilter_test')->customFilter('email',function($item) { 513 | return (($item['status']=='blocked') ? $item['email'] : false); 514 | }); 515 | 516 | // should be empty array 517 | $this->assertEquals([],$users); 518 | 519 | $db->flush(true); 520 | } 521 | 522 | 523 | public function testFieldMethod() 524 | { 525 | $db = new \Filebase\Database([ 526 | 'dir' => __DIR__.'/databases', 527 | 'format' => \Filebase\Format\Yaml::class 528 | ]); 529 | 530 | $db->flush(true); 531 | 532 | $db->get('user_test_email_1')->set(['email'=>'example@example.com'])->save(); 533 | 534 | $f = $db->get('user_test_email_1')->field('email'); 535 | 536 | $this->assertEquals('example@example.com', $f); 537 | 538 | $db->flush(true); 539 | } 540 | 541 | 542 | public function testNestedFieldMethod() 543 | { 544 | $db = new \Filebase\Database([ 545 | 'dir' => __DIR__.'/databases', 546 | 'format' => \Filebase\Format\Yaml::class 547 | ]); 548 | 549 | $db->flush(true); 550 | 551 | $db->get('user_test_email_2')->set([ 552 | 'profile' => [ 553 | 'email' => 'example@example.com' 554 | ] 555 | ])->save(); 556 | 557 | $f = $db->get('user_test_email_2')->field('profile.email'); 558 | 559 | $this->assertEquals('example@example.com', $f); 560 | 561 | $db->flush(true); 562 | } 563 | 564 | 565 | public function testBadNameException() 566 | { 567 | $this->expectException(\Exception::class); 568 | 569 | $db = new \Filebase\Database([ 570 | 'dir' => __DIR__.'/databases', 571 | 'safe_filename' => false, 572 | 'format' => \Filebase\Format\Yaml::class 573 | ]); 574 | 575 | $db->flush(true); 576 | 577 | $file = $db->get('^*bad_@name%$1#'); 578 | 579 | $db->flush(true); 580 | } 581 | 582 | 583 | public function testBadNameReplacement() 584 | { 585 | $badName = 'ti^@%mo!!~th*y-m_?a(ro%)is.&'; 586 | $newName = Filesystem::validateName($badName, true); 587 | 588 | $this->assertEquals('timothy-m_arois', $newName); 589 | } 590 | 591 | 592 | public function testBadNameReplacementLong() 593 | { 594 | $badName = '1234567890123456789012345678901234567890123456789012345678901234'; 595 | $newName = Filesystem::validateName($badName, true); 596 | 597 | $this->assertEquals(63, (strlen($newName)) ); 598 | $this->assertEquals('123456789012345678901234567890123456789012345678901234567890123', $newName); 599 | } 600 | 601 | } 602 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filebase 2 | 3 | [![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) 4 | 5 | [Join Discord](https://discord.gg/kywDsDnJ6C) – For support, updates and collaboration. 6 | 7 | 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](README.md#2-formatting) the way you like). 8 | 9 | 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 | 11 | Doesn't that sound awesome? 12 | 13 | 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. 14 | 15 | Works with **PHP 5.6** and **PHP 7+** 16 | 17 | ### Features 18 | 19 | Filebase is simple by design, but has enough features for the more advanced. 20 | 21 | * Key/Value and Array-based Data Storing 22 | * [Querying data](README.md#8-queries) 23 | * [Custom filters](README.md#7-custom-filters) 24 | * [Caching](README.md#9-caching) (queries) 25 | * [Database Backups](README.md#10-database-backups) 26 | * [Formatting](README.md#2-formatting) (encode/decode) 27 | * [Validation](README.md#6-validation-optional) (on save) 28 | * CRUD (method APIs) 29 | * File locking (on save) 30 | * Intuitive Method Naming 31 | 32 | ## Installation 33 | 34 | Use [Composer](http://getcomposer.org/) to install package. 35 | 36 | Run `composer require tmarois/filebase:^1.0` 37 | 38 | 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. 39 | 40 | ## Usage 41 | 42 | ```php 43 | // setting the access and configration to your database 44 | $database = new \Filebase\Database([ 45 | 'dir' => 'path/to/database/dir' 46 | ]); 47 | 48 | // in this example, you would search an exact user name 49 | // it would technically be stored as user_name.json in the directories 50 | // if user_name.json doesn't exists get will return new empty Document 51 | $item = $database->get('kingslayer'); 52 | 53 | // display property values 54 | echo $item->first_name; 55 | echo $item->last_name; 56 | echo $item->email; 57 | 58 | // change existing or add new properties 59 | $item->email = 'example@example.com'; 60 | $item->tags = ['php','developer','html5']; 61 | 62 | // need to save? thats easy! 63 | $item->save(); 64 | 65 | 66 | // check if a record exists and do something if it does or does not 67 | if ($database->has('kingslayer')) 68 | { 69 | // do some action 70 | } 71 | 72 | // Need to find all the users that have a tag for "php" ? 73 | $users = $db->where('tags','IN','php')->results(); 74 | 75 | // Need to search for all the users who use @yahoo.com email addresses? 76 | $users = $db->where('email','LIKE','@yahoo.com')->results(); 77 | 78 | ``` 79 | 80 | 81 | ## (1) Config Options 82 | 83 | The config is *required* when defining your database. The options are *optional* since they have defaults. 84 | 85 | Usage Example (all options) 86 | 87 | ```php 88 | $db = new \Filebase\Database([ 89 | 'dir' => 'path/to/database/dir', 90 | 'backupLocation' => 'path/to/database/backup/dir', 91 | 'format' => \Filebase\Format\Json::class, 92 | 'cache' => true, 93 | 'cache_expires' => 1800, 94 | 'pretty' => true, 95 | 'safe_filename' => true, 96 | 'read_only' => false, 97 | 'validate' => [ 98 | 'name' => [ 99 | 'valid.type' => 'string', 100 | 'valid.required' => true 101 | ] 102 | ] 103 | ]); 104 | ``` 105 | 106 | |Name |Type |Default Value |Description | 107 | |--- |--- |--- |--- | 108 | |`dir` |string |current directory |The directory where the database files are stored. | 109 | |`backupLocation` |string |current directory (`/backups`) |The directory where the backup zip files will be stored. | 110 | |`format` |object |`\Filebase\Format\Json` |The format class used to encode/decode data | 111 | |`validate` |array | |Check [Validation Rules](README.md#6-validation-optional) for more details | 112 | |`cache` |bool |true |Stores [query](README.md#8-queries) results into cache for faster loading. | 113 | |`cache_expire` |int |1800 |How long caching will last (in seconds) | 114 | |`pretty` |bool |true |Store the data for human readability? Pretty Print | 115 | |`safe_filename` |bool |true |Automatically converts the file name to a valid name (added: 1.0.13) | 116 | |`read_only` |bool |false |Prevents the database from creating/modifying files or directories (added: 1.0.14) | 117 | 118 | 119 | ## (2) Formatting 120 | 121 | Format Class is what defines the encoding and decoding of data within your database files. 122 | 123 | You can write your own or change the existing format class in the config. The methods in the class must be `static` and the class must implement `\Filebase\Format\FormatInterface` 124 | 125 | The Default Format Class: `JSON` 126 | ```php 127 | \Filebase\Format\Json::class 128 | ``` 129 | 130 | Additional Format Classes: `Yaml` 131 | ```php 132 | \Filebase\Format\Yaml::class 133 | ``` 134 | 135 | ## (3) GET (and methods) 136 | 137 | After you've loaded up your database config, then you can use the `get()` method to retrieve a single document of data. 138 | 139 | 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). 140 | 141 | ```php 142 | // my user id 143 | $userId = '92832711'; 144 | 145 | // get the user information by id 146 | $item = $db->get($userId); 147 | 148 | ``` 149 | 150 | `get()` returns `\Filebase\Document` object and has its own methods which you can call. 151 | 152 | |Method|Details| 153 | |---|---| 154 | |`save()` | Saves document in current state | 155 | |`delete()` | Deletes current document (can not be undone) | 156 | |`toArray()` | Array of items in document | 157 | |`getId()` | Document Id | 158 | |`createdAt()` | Document was created (default Y-m-d H:i:s) | 159 | |`updatedAt()` | Document was updated (default Y-m-d H:i:s) | 160 | |`field()` | You can also use `.` dot delimiter to find values from nested arrays | 161 | |`isCache()` | (true/false) if the current document is loaded from cache | 162 | |`filter()` | Refer to the [Custom Filters](README.md#7-custom-filters) | 163 | 164 | Example: 165 | 166 | ```php 167 | // get the timestamp when the user was created 168 | echo $db->get($userId)->createdAt(); 169 | 170 | // grabbing a specific field "tags" within the user 171 | // in this case, tags might come back as an array ["php","html","javascript"] 172 | $user_tags = $db->get($userId)->field('tags'); 173 | 174 | // or if "tags" is nested in the user data, such as about[tags] 175 | $user_tags = $db->get($userId)->field('about.tags'); 176 | 177 | // and of course you can do this as well for getting "tags" 178 | $user = $db->get($userId); 179 | $user_tags = $user->tags; 180 | $user_tags = $user->about['tags']; 181 | ``` 182 | 183 | 184 | ## (4) Create | Update | Delete 185 | 186 | As listed in the above example, its **very simple**. Use `$item->save()`, the `save()` method will either **Create** or **Update** an existing document by default. It will log all changes with `createdAt` and `updatedAt`. If you want to replace *all* data within a single document pass the new data in the `save($data)` method, otherwise don't pass any data to allow it to save the current instance. 187 | 188 | ```php 189 | 190 | // SAVE or CREATE 191 | // this will save the current data and any changed variables 192 | // but it will leave existing variables that you did not modify unchanged. 193 | // This will also create a document if none exist. 194 | $item->title = 'My Document'; 195 | $item->save() 196 | 197 | // This will replace all data within the document 198 | // Allows you to reset the document and put in fresh data 199 | // Ignoring any above changes or changes to variables, since 200 | // This sets its own within the save method. 201 | $item->save([ 202 | 'title' => 'My Document' 203 | ]); 204 | 205 | // DELETE 206 | // This will delete the current document 207 | // This action can not be undone. 208 | $item->delete(); 209 | 210 | ``` 211 | 212 | 213 | ## (5) Database Methods 214 | 215 | ```php 216 | $db = new \Filebase\Database($config); 217 | ``` 218 | 219 | Here is a list of methods you can use on the database class. 220 | 221 | |Method|Details| 222 | |---|---| 223 | |`version()` | Current version of your Filebase library | 224 | |`get($id)` | Refer to [get()](README.md#3-get-and-methods) | 225 | |`has($id)` | Check if a record exist returning true/false | 226 | |`findAll()` | Returns all documents in database | 227 | |`count()` | Number of documents in database | 228 | |`flush(true)` | Deletes all documents. | 229 | |`flushCache()` | Clears all the cache | 230 | |`truncate()` | Deletes all documents. Alias of `flush(true)` | 231 | |`query()` | Refer to the [Queries](README.md#8-queries) | 232 | |`backup()` | Refer to the [Backups](README.md#10-database-backups) | 233 | 234 | Examples 235 | 236 | ```php 237 | $users = new \Filebase\Database([ 238 | 'dir' => '/storage/users', 239 | ]); 240 | 241 | // displays number of users in the database 242 | echo $users->count(); 243 | 244 | 245 | // Find All Users and display their email addresses 246 | 247 | $results = $users->findAll(); 248 | foreach($results as $user) 249 | { 250 | echo $user->email; 251 | 252 | // you can also run GET methods on each user (document found) 253 | // Displays when the user was created. 254 | echo $user->createdAt(); 255 | } 256 | 257 | 258 | // deletes all users in the database 259 | // this action CAN NOT be undone (be warned) 260 | $users->flush(true); 261 | 262 | ``` 263 | 264 | 265 | ## (6) Validation *(optional)* 266 | 267 | When invoking `save()` method, the document will be checked for validation rules (if set). 268 | These rules MUST pass in order for the document to save. 269 | 270 | ```php 271 | $db = new \Filebase\Database([ 272 | 'dir' => '/path/to/database/dir', 273 | 'validate' => [ 274 | 'name' => [ 275 | 'valid.type' => 'string', 276 | 'valid.required' => true 277 | ], 278 | 'description' => [ 279 | 'valid.type' => 'string', 280 | 'valid.required' => false 281 | ], 282 | 'emails' => [ 283 | 'valid.type' => 'array', 284 | 'valid.required' => true 285 | ], 286 | 'config' => [ 287 | 'settings' => [ 288 | 'valid.type' => 'array', 289 | 'valid.required' => true 290 | ] 291 | ] 292 | ] 293 | ]); 294 | ``` 295 | 296 | In the above example `name`, `description`, `emails` and `config` array keys would be replaced with your own that match your data. Notice that `config` has a nested array `settings`, yes you can nest validations. 297 | 298 | **Validation rules:** 299 | 300 | |Name |Allowed Values |Description | 301 | |--- |--- |--- | 302 | |`valid.type` |`string`, `str`, `integer`, `int`, `array` |Checks if the property is the current type | 303 | |`valid.required` |`true`, `false` |Checks if the property is on the document | 304 | 305 | 306 | ## (7) Custom Filters 307 | 308 | *NOTE Custom filters only run on a single document* 309 | 310 | Item filters allow you to customize the results, and do simple querying within the same document. These filters are great if you have an array of items within one document. Let's say you store "users" as an array in `users.json`, then you could create a filter to show you all the users that have a specific tag, or field matching a specific value. 311 | 312 | This example will output all the emails of users who are blocked. 313 | 314 | ```php 315 | // Use [data] for all items within the document 316 | // But be sure that each array item uses the same format (otherwise except isset errors) 317 | 318 | $users = $db->get('users')->filter('data','blocked',function($item, $status) { 319 | return (($item['status']==$status) ? $item['email'] : false); 320 | }); 321 | 322 | // Nested Arrays? 323 | // This uses NESTED properties. If the users array was stored as an array inside [list] 324 | // You can also use `.` dot delimiter to get arrays from nested arrays 325 | 326 | $users = $db->get('users')->filter('list.users','blocked',function($item, $status) { 327 | return (($item['status']==$status) ? $item['email'] : false); 328 | }); 329 | ``` 330 | 331 | ## (8) Queries 332 | 333 | Queries allow you to search **multiple documents** and return only the ones that match your criteria. 334 | 335 | If caching is enabled, queries will use `findAll()` and then cache results for the next run. 336 | 337 | > Note: You no longer need to call `query()`, you can now call query methods directly on the database class. 338 | 339 | ```php 340 | // Simple (equal to) Query 341 | // return all the users that are blocked. 342 | $users = $db->where(['status' => 'blocked'])->results(); 343 | 344 | // Stackable WHERE clauses 345 | // return all the users who are blocked, 346 | // AND have "php" within the tag array 347 | $users = $db->where('status','=','blocked') 348 | ->andWhere('tag','IN','php') 349 | ->results(); 350 | 351 | // You can also use `.` dot delimiter to use on nested keys 352 | $users = $db->where('status.language.english','=','blocked')->results(); 353 | 354 | // Limit Example: Same query as above, except we only want to limit the results to 10 355 | $users = $db->where('status.language.english','=','blocked')->limit(10)->results(); 356 | 357 | 358 | 359 | // Query LIKE Example: how about find all users that have a gmail account? 360 | $usersWithGmail = $db->where('email','LIKE','@gmail.com')->results(); 361 | 362 | // OrderBy Example: From the above query, what if you want to order the results by nested array 363 | $usersWithGmail = $db->where('email','LIKE','@gmail.com') 364 | ->orderBy('profile.name', 'ASC') 365 | ->results(); 366 | 367 | // or just order the results by email address 368 | $usersWithGmail = $db->where('email','LIKE','@gmail.com') 369 | ->orderBy('email', 'ASC') 370 | ->results(); 371 | 372 | // OrderBy can be applied multiple times to perform a multi-sort 373 | $usersWithGmail = $db->query() 374 | ->where('email','LIKE','@gmail.com') 375 | ->orderBy('last_name', 'ASC') 376 | ->orderBy('email', 'ASC') 377 | ->results(); 378 | 379 | // this will return the first user in the list based on ascending order of user name. 380 | $user = $db->orderBy('name','ASC')->first(); 381 | // print out the user name 382 | echo $user['name']; 383 | 384 | // You can also order multiple columns as such (stacking) 385 | $orderMultiples = $db->orderBy('field1','ASC') 386 | ->orderBy('field2','DESC') 387 | ->results(); 388 | 389 | // What about regex search? Finds emails within a field 390 | $users = $db->where('email','REGEX','/[a-z\d._%+-]+@[a-z\d.-]+\.[a-z]{2,4}\b/i')->results(); 391 | 392 | // Find all users that have gmail addresses and only returning their name and age fields (excluding the rest) 393 | $users = $db->select('name,age')->where('email','LIKE','@gmail.com')->results(); 394 | 395 | // Instead of returning users, how about just count how many users are found. 396 | $totalUsers = $db->where('email','LIKE','@gmail.com')->count(); 397 | 398 | 399 | // You can delete all documents that match the query (BULK DELETE) 400 | $db->where('name','LIKE','john')->delete(); 401 | 402 | // Delete all items that match query and match custom filter 403 | $db->where('name','LIKE','john')->delete(function($item){ 404 | return ($item->name == 'John' && $item->email == 'some@mail.com'); 405 | }); 406 | 407 | 408 | // GLOBAL VARIABLES 409 | 410 | // ability to sort the results by created at or updated at times 411 | $documents = $db->orderBy('__created_at', 'DESC')->results(); 412 | $documents = $db->orderBy('__updated_at', 'DESC')->results(); 413 | 414 | // search for items that match the (internal) id 415 | $documents = $db->where('__id', 'IN', ['id1', 'id2'])->results(); 416 | 417 | ``` 418 | 419 | To run the query use `results()` or if you only want to return the first item use `first()` 420 | 421 | ### Query Methods: 422 | 423 | *These methods are optional and they are stackable* 424 | 425 | |Method |Arguments |Details 426 | |--- |--- |---| 427 | |`select()` | `array` or `string` (comma separated) | Select only the fields you wish to return (for each document), usage: `field1,field2` | 428 | |`where()` | `mixed` | `array` for simple "equal to" OR `where($field, $operator, $value)` | 429 | |`andWhere()` | `mixed` | see `where()`, uses the logical `AND` | 430 | |`orWhere()` | `mixed` | see `where()`, this uses the logical `OR` | 431 | |`limit()` | `int` limit, `int` offset | How many documents to return, and offset | 432 | |`orderBy()` | `field` , `sort order` | Order documents by a specific field and order by `ASC` or `DESC` | 433 | |`delete()` | `Closure` | Ability to Bulk-delete all items that match | 434 | 435 | 436 | The below **methods execute the query** and return results *(do not try to use them together)* 437 | 438 | |Method |Details| 439 | |--- |---| 440 | |`count()` | Counts and returns the number of documents in results. | 441 | |`first()` | Returns only the first document in results. | 442 | |`last()` | Returns only the last document in results. | 443 | |`results()` | This will return all the documents found and their data as an array. Passing the argument of `false` will be the same as `resultDocuments()` (returning the full document objects) | 444 | |`resultDocuments()` | This will return all the documents found and their data as document objects, or you can do `results(false)` which is the alias. | 445 | 446 | ### Comparison Operators: 447 | 448 | |Name |Details| 449 | |--- |---| 450 | |`=` or `==` |Equality| 451 | |`===` |Strict Equality| 452 | |`!=` |Not Equals| 453 | |`NOT` |Not Equals (same as `!=`)| 454 | |`!==` |Strict Not Equals| 455 | |`>` |Greater than| 456 | |`>=` |Greater than or equal| 457 | |`<` |Less than| 458 | |`<=` |Less than or equal| 459 | |`IN` |Checks if the value is within a array| 460 | |`LIKE` |case-insensitive regex expression search| 461 | |`NOT LIKE` |case-insensitive regex expression search (opposite)| 462 | |`REGEX` |Regex search| 463 | 464 | 465 | ## (9) Caching 466 | If caching is enabled, it will automatically store your results from queries into sub-directories within your database directory. 467 | 468 | Cached queries will only be used if a specific saved cache is less than the expire time, otherwise it will use live data and automatically replace the existing cache for next time use. 469 | 470 | 471 | ## (10) Database Backups 472 | By default you can backup your database using `$db->backup()->create()`, this will create a `.zip` file of your entire database based on your `dir` path. 473 | 474 | ### Methods: 475 | These methods can be used when invoking `backup()` on your `Database`. 476 | 477 | - `create()` Creates a backup of your database (in your backup location `.zip`) 478 | - `clean()` Purges all existing backups (`.zip` files in your backup location) 479 | - `find()` Returns an `array` of all existing backups (array key by `time()` when backup was created) 480 | - `rollback()` Restore an existing backup (latest available), replaces existing database `dir` 481 | 482 | **Example:** 483 | 484 | ```php 485 | // invoke your database 486 | $database = new \Filebase\Database([ 487 | 'dir' => '/storage/users', 488 | 'backupLocation' => '/storage/backup', 489 | ]); 490 | 491 | // create a new backup of your database 492 | // will look something like /storage/backup/1504631092.zip 493 | $database->backup()->create(); 494 | 495 | // delete all existing backups 496 | $database->backup()->clean(); 497 | 498 | // get a list of all existing backups (organized from new to old) 499 | $backups = $database->backup()->find(); 500 | 501 | // restore an existing backup (latest backup available) 502 | $database->backup()->rollback(); 503 | 504 | ``` 505 | 506 | 507 | ## Why Filebase? 508 | 509 | 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). 510 | 511 | Inspired by [Flywheel](https://github.com/jamesmoss/flywheel) and [Flinetone](https://github.com/fire015/flintstone). 512 | 513 | 514 | ## How Versions Work 515 | 516 | Versions are as follows: Major.Minor.Patch 517 | 518 | * Major: Rewrites with completely new code-base. 519 | * Minor: New Features/Changes that breaks compatibility. 520 | * Patch: New Features/Fixes that does not break compatibility. 521 | 522 | Filebase will work-hard to be **backwards-compatible** when possible. 523 | 524 | 525 | ## Sites and Users of Filebase 526 | 527 | * [Grayscale Inc](https://grayscale.com) 528 | * [VIP Auto](http://vipautoli.com) 529 | * [Ideal Internet](http://idealinternet.com) 530 | * [OnlineFun](http://onlinefun.com) 531 | * [PuzzlePlay](http://puzzleplay.com) 532 | * [Square Media LLC](http://squaremedia.com) 533 | * [My Map Directions](https://mymapdirections.com) 534 | * [Discount Savings](https://discount-savings.com) 535 | * [Vivint - Smart Homes](http://smarthomesecurityplans.com/) 536 | 537 | *If you are using Filebase – send in a pull request and we will add your project here.* 538 | 539 | 540 | ## Contributions 541 | 542 | 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. 543 | 544 | 545 | ## License 546 | 547 | Filebase is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). 548 | -------------------------------------------------------------------------------- /tests/QueryYamlTest.php: -------------------------------------------------------------------------------- 1 | ["name" => "Roy"] ]) 19 | * 20 | * Comparisons used "=", "==", "===" 21 | * 22 | */ 23 | public function testWhereCountAllEqualCompare() 24 | { 25 | $db = new \Filebase\Database([ 26 | 'dir' => __DIR__.'/databases/users_1', 27 | 'cache' => false, 28 | 'format' => \Filebase\Format\Yaml::class 29 | ]); 30 | 31 | // FIRST TEST 32 | $db->flush(true); 33 | 34 | for ($x = 1; $x <= 10; $x++) 35 | { 36 | $user = $db->get(uniqid()); 37 | $user->name = 'John'; 38 | $user->contact['email'] = 'john@john.com'; 39 | $user->save(); 40 | } 41 | 42 | $count = $db->count(); 43 | 44 | // standard matches 45 | $query1 = $db->query()->where('name','=','John')->results(); 46 | $query2 = $db->query()->where('name','==','John')->results(); 47 | $query3 = $db->query()->where('name','===','John')->results(); 48 | $query4 = $db->query()->where(['name' => 'John'])->results(); 49 | 50 | // testing nested level 51 | $query5 = $db->query()->where('contact.email','=','john@john.com')->results(); 52 | 53 | $this->assertEquals($count, count($query1)); 54 | $this->assertEquals($count, count($query2)); 55 | $this->assertEquals($count, count($query3)); 56 | $this->assertEquals($count, count($query4)); 57 | $this->assertEquals($count, count($query5)); 58 | 59 | $db->flush(true); 60 | } 61 | 62 | 63 | //-------------------------------------------------------------------- 64 | 65 | 66 | /** 67 | * testWhereCountAllNotEqualCompare() 68 | * 69 | * TEST CASE: 70 | * - Creates 10 items in database with ["name" = "John"] 71 | * - Counts the total items in the database 72 | * 73 | * FIRST TEST: 74 | * - Compares the number of items in db to the number items the query found 75 | * - Should match "10" 76 | * 77 | * SECOND TEST: 78 | * - Secondary Tests to find items that DO NOT match "John" 79 | * - Should match "0" 80 | * 81 | * Comparisons used "!=", "!==", "NOT" 82 | * 83 | */ 84 | public function testWhereCountAllNotEqualCompare() 85 | { 86 | $db = new \Filebase\Database([ 87 | 'dir' => __DIR__.'/databases/users_1', 88 | 'cache' => false, 89 | 'format' => \Filebase\Format\Yaml::class 90 | ]); 91 | 92 | $db->flush(true); 93 | 94 | for ($x = 1; $x <= 10; $x++) 95 | { 96 | $user = $db->get(uniqid()); 97 | $user->name = 'John'; 98 | $user->save(); 99 | } 100 | 101 | $count = $db->count(); 102 | 103 | $query1 = $db->query()->where('name','!=','Max')->results(); 104 | $query2 = $db->query()->where('name','!==','Smith')->results(); 105 | $query3 = $db->query()->where('name','NOT','Jason')->results(); 106 | 107 | $query4 = $db->query()->where('name','!=','John')->results(); 108 | $query5 = $db->query()->where('name','!==','John')->results(); 109 | $query6 = $db->query()->where('name','NOT','John')->results(); 110 | 111 | $this->assertEquals($count, count($query1)); 112 | $this->assertEquals($count, count($query2)); 113 | $this->assertEquals($count, count($query3)); 114 | 115 | $this->assertEquals(0, count($query4)); 116 | $this->assertEquals(0, count($query5)); 117 | $this->assertEquals(0, count($query6)); 118 | 119 | $db->flush(true); 120 | } 121 | 122 | 123 | //-------------------------------------------------------------------- 124 | 125 | 126 | 127 | /** 128 | * testWhereCountAllGreaterLessCompare() 129 | * 130 | * TEST CASE: 131 | * - Creates 10 items in database with ["pages" = 5] 132 | * - Counts the total items in the database 133 | * 134 | * FIRST TEST: Greater Than 135 | * - Should match "10" 136 | * 137 | * SECOND TEST: Less Than 138 | * - Should match "10" 139 | * 140 | * THIRD TEST: Less/Greater than "no match" 141 | * - Should match "0" 142 | * 143 | * Comparisons used ">=", ">", "<=", "<" 144 | * 145 | */ 146 | public function testWhereCountAllGreaterLessCompare() 147 | { 148 | $db = new \Filebase\Database([ 149 | 'dir' => __DIR__.'/databases/users_1', 150 | 'cache' => false, 151 | 'format' => \Filebase\Format\Yaml::class 152 | ]); 153 | 154 | $db->flush(true); 155 | 156 | for ($x = 1; $x <= 10; $x++) 157 | { 158 | $user = $db->get(uniqid()); 159 | $user->index = $x; 160 | $user->index2 = mt_rand(1,2); 161 | $user->pages = 5; 162 | $user->save(); 163 | } 164 | 165 | $count = $db->count(); 166 | 167 | $queryIndex = $db->query() 168 | ->where('pages','>','4') 169 | ->where('index','=','1') 170 | ->where('index2','=','2') 171 | ->results(); 172 | 173 | // print_r($queryIndex); 174 | 175 | // FIRST TEST 176 | $query1 = $db->query()->where('pages','>','4')->results(); 177 | $query2 = $db->query()->where('pages','>=','5')->results(); 178 | 179 | // SECOND TEST 180 | $query3 = $db->query()->where('pages','<','6')->results(); 181 | $query4 = $db->query()->where('pages','<=','5')->results(); 182 | 183 | // THIRD TEST 184 | $query5 = $db->query()->where('pages','>','5')->results(); 185 | $query6 = $db->query()->where('pages','<','5')->results(); 186 | 187 | $this->assertEquals($count, count($query1)); 188 | $this->assertEquals($count, count($query2)); 189 | $this->assertEquals($count, count($query3)); 190 | $this->assertEquals($count, count($query4)); 191 | $this->assertEquals(0, count($query5)); 192 | $this->assertEquals(0, count($query6)); 193 | 194 | $db->flush(true); 195 | } 196 | 197 | 198 | //-------------------------------------------------------------------- 199 | 200 | 201 | /** 202 | * testWhereLike() 203 | * 204 | * TEST CASE: 205 | * - Creates a bunch of items with the same information 206 | * - Creates one item with different info (finding the needle) 207 | * 208 | * Comparisons used "LIKE", "NOT LIKE", "==" 209 | * 210 | */ 211 | public function testWhereLike() 212 | { 213 | $db = new \Filebase\Database([ 214 | 'dir' => __DIR__.'/databases/users_like', 215 | 'cache' => false, 216 | 'format' => \Filebase\Format\Yaml::class 217 | ]); 218 | 219 | $db->flush(true); 220 | 221 | for ($x = 1; $x <= 10; $x++) 222 | { 223 | $user = $db->get(uniqid()); 224 | $user->name = 'John Ellot'; 225 | $user->email = 'johnellot@example.com'; 226 | $user->save(); 227 | } 228 | 229 | // the needle 230 | $user = $db->get(uniqid()); 231 | $user->name = 'Timothy Marois'; 232 | $user->email = 'timothymarois@email.com'; 233 | $user->save(); 234 | 235 | $count = $db->count(); 236 | 237 | // should return exact match 238 | $query1 = $db->query()->where('name','==','Timothy Marois')->results(); 239 | // this should fail to find anything 240 | $query2 = $db->query()->where('name','==','timothy marois')->results(); 241 | 242 | // this should find match with regex loose expression 243 | $query3 = $db->query()->where('name','LIKE','timothy marois')->results(); 244 | // this should find match by looking for loose expression on "timothy" 245 | $query4 = $db->query()->where('name','LIKE','timothy')->results(); 246 | // this should find all teh users that have an email address using "@email.com" 247 | $query5 = $db->query()->where('email','LIKE','@email.com')->results(); 248 | // this should return 1 as its looking at only the emails not like "@example.com" 249 | $query6 = $db->query()->where('email','NOT LIKE','@example.com')->results(); 250 | 251 | $this->assertEquals(1, count($query1)); 252 | $this->assertEquals(0, count($query2)); 253 | $this->assertEquals(1, count($query3)); 254 | $this->assertEquals(1, count($query4)); 255 | $this->assertEquals(1, count($query5)); 256 | $this->assertEquals(1, count($query6)); 257 | 258 | $db->flush(true); 259 | } 260 | 261 | 262 | //-------------------------------------------------------------------- 263 | 264 | 265 | /** 266 | * testWhereRegex() 267 | * 268 | * TEST CASE: 269 | * - Testing the use of regex 270 | * 271 | * Comparisons used "REGEX" 272 | * 273 | */ 274 | public function testWhereRegex() 275 | { 276 | $db = new \Filebase\Database([ 277 | 'dir' => __DIR__.'/databases/users_regex', 278 | 'cache' => false, 279 | 'format' => \Filebase\Format\Yaml::class 280 | ]); 281 | 282 | $db->flush(true); 283 | 284 | for ($x = 1; $x <= 10; $x++) 285 | { 286 | $user = $db->get(uniqid()); 287 | $user->name = 'John Ellot'; 288 | $user->email = 'johnellot@example.com'; 289 | $user->save(); 290 | } 291 | 292 | // the needle (with bad email) 293 | $user = $db->get(uniqid()); 294 | $user->name = 'Leo Ash'; 295 | $user->email = 'example@emailcom'; 296 | $user->save(); 297 | 298 | $count = $db->count(); 299 | 300 | // this should find match with regex loose expression 301 | $query1 = $db->query()->where('name','REGEX','/leo/i')->results(); 302 | $query2 = $db->query()->where('name','REGEX','/leo\sash/i')->results(); 303 | 304 | // finds all the emails in a field 305 | $query3 = $db->query()->where('email','REGEX','/[a-z\d._%+-]+@[a-z\d.-]+\.[a-z]{2,4}\b/i')->results(); 306 | 307 | $this->assertEquals(1, count($query1)); 308 | $this->assertEquals(1, count($query2)); 309 | $this->assertEquals(10, count($query3)); 310 | 311 | $db->flush(true); 312 | } 313 | 314 | 315 | //-------------------------------------------------------------------- 316 | 317 | 318 | /** 319 | * testLimitOffset() 320 | * 321 | * TEST CASE: 322 | * - Creates 6 company profiles 323 | * - Queries them and limits the results 324 | * 325 | * 326 | */ 327 | public function testLimitOffset() 328 | { 329 | $db = new \Filebase\Database([ 330 | 'dir' => __DIR__.'/databases/users_orderby', 331 | 'cache' => false, 332 | 'format' => \Filebase\Format\Yaml::class 333 | ]); 334 | 335 | $db->flush(true); 336 | 337 | $companies = ['Google'=>150, 'Apple'=>150, 'Microsoft'=>150, 'Amex'=>150, 'Hooli'=>20, 'Amazon'=>10]; 338 | 339 | foreach($companies as $company=>$rank) 340 | { 341 | $user = $db->get(uniqid()); 342 | $user->name = $company; 343 | $user->rank = $rank; 344 | $user->save(); 345 | } 346 | 347 | // test that it limits the results to "2" (total query pulls "5") 348 | $test1 = $db->query()->where('rank','=',150)->limit(2)->results(); 349 | 350 | // test the offset, no limit, should be 3 (total query pulls "5") 351 | $test2 = $db->query()->where('rank','=',150)->limit(0,1)->results(); 352 | 353 | // test that the offset takes off the first array (should return "apple", not "google") 354 | $test3 = $db->query()->where('rank','=',150)->limit(1,1)->results(); 355 | 356 | $this->assertEquals(2, (count($test1))); 357 | $this->assertEquals(3, (count($test2))); 358 | $this->assertEquals('Apple', $test3[0]['name']); 359 | 360 | $db->flush(true); 361 | } 362 | 363 | 364 | //-------------------------------------------------------------------- 365 | 366 | /** 367 | * testSorting() 368 | * 369 | * TEST CASE: 370 | * - Creates 6 company profiles 371 | * - Sorts them by DESC/ASC 372 | * 373 | * 374 | */ 375 | /*public function testSelectQuery() 376 | { 377 | $db = new \Filebase\Database([ 378 | 'dir' => __DIR__.'/databases/users_select', 379 | 'cache' => false, 'format' => \Filebase\Format\Yaml::class 380 | ]); 381 | 382 | $db->flush(true); 383 | 384 | $users = [ 385 | 'JR MM'=> 386 | [ 387 | 'about'=>'this is a long about me section', 388 | 'email'=>'jrmm@email.com', 389 | 'profile' => [ 390 | 'website' => 'jr.com' 391 | ] 392 | ], 393 | 'Tim'=> 394 | [ 395 | 'about'=>'this is a section about tim', 396 | 'email'=>'timothymarois@email.com', 397 | 'profile' => [ 398 | 'website' => 'timothymarois.com' 399 | ] 400 | ] 401 | ]; 402 | foreach($users as $name=>$info) 403 | { 404 | $user = $db->get(uniqid()); 405 | $user->name = $name; 406 | $user->about = $info['about']; 407 | $user->email = $info['email']; 408 | $user->profile = $info['profile']; 409 | $user->save(); 410 | } 411 | 412 | // return the "name" (selecting only 1 item) 413 | $test1 = $db->query()->select('name')->results(); 414 | $this->assertEquals(['JR MM','Tim'], [$test1[0]['name'],$test1[1]['name']]); 415 | // count how many items are in array (should be only 1 each) 416 | $this->assertEquals([1,1], [count($test1[0]),count($test1[1])]); 417 | 418 | // return the "name" (selecting only 1 item) 419 | $test2 = $db->query()->select('name,email')->first(); 420 | $this->assertEquals(['JR MM','jrmm@email.com'], [$test2['name'],$test2['email']]); 421 | 422 | // select using arrays instead of strings 423 | $test3 = $db->query()->select(['name','email'])->first(); 424 | $this->assertEquals(['JR MM','jrmm@email.com'], [$test3['name'],$test3['email']]); 425 | 426 | // return the "name" (selecting only 1 item) 427 | // currently DOES not work with nested.. 428 | // $test3 = $db->query()->select('name,profile.website')->first(); 429 | 430 | // print_r($test3); 431 | // $this->assertEquals(['JR MM','jrmm@email.com'], [$test2['name'],$test2['email']]); 432 | }*/ 433 | 434 | 435 | /** 436 | * testSorting() 437 | * 438 | * TEST CASE: 439 | * - Creates 6 company profiles 440 | * - Sorts them by DESC/ASC 441 | * 442 | * 443 | */ 444 | public function testSorting() 445 | { 446 | $db = new \Filebase\Database([ 447 | 'dir' => __DIR__.'/databases/users_orderby', 448 | 'cache' => false, 449 | 'format' => \Filebase\Format\Yaml::class 450 | ]); 451 | 452 | $db->flush(true); 453 | 454 | $companies = ['Google'=>150, 'Apple'=>180, 'Microsoft'=>120, 'Amex'=>20, 'Hooli'=>50, 'Amazon'=>140]; 455 | 456 | foreach($companies as $company=>$rank) 457 | { 458 | $user = $db->get(uniqid()); 459 | $user->name = $company; 460 | $user->rank['reviews'] = $rank; 461 | $user->status = 'enabled'; 462 | $user->save(); 463 | } 464 | 465 | // test that they are ordered by name ASC (check first, second, and last) 466 | $test1 = $db->query()->where('status','=','enabled')->orderBy('name', 'ASC')->results(); 467 | $this->assertEquals(['first'=>'Amazon','second'=>'Amex','last'=>'Microsoft'], ['first'=>$test1[0]['name'],'second'=>$test1[1]['name'],'last'=>$test1[5]['name']]); 468 | 469 | // test that they are ordered by name ASC (check first, second, and last) 470 | $test2 = $db->query()->where('status','=','enabled')->limit(3)->orderBy('name', 'ASC')->results(); 471 | $this->assertEquals(['Amazon','Amex','Apple'], [$test2[0]['name'],$test2[1]['name'],$test2[2]['name']]); 472 | 473 | // test that they are ordered by name DESC (check first, second, and last) 474 | $test3 = $db->query()->where('status','=','enabled')->limit(3)->orderBy('name', 'DESC')->results(); 475 | $this->assertEquals(['Microsoft','Hooli','Google'], [$test3[0]['name'],$test3[1]['name'],$test3[2]['name']]); 476 | 477 | // test that they are ordered by rank nested [reviews] DESC 478 | $test4 = $db->query()->where('status','=','enabled')->limit(3)->orderBy('rank.reviews', 'DESC')->results(); 479 | $this->assertEquals(['Apple','Google','Amazon'], [$test4[0]['name'],$test4[1]['name'],$test4[2]['name']]); 480 | 481 | // test that they are ordered by rank nested [reviews] ASC 482 | $test5 = $db->query()->where('status','=','enabled')->limit(3)->orderBy('rank.reviews', 'ASC')->results(); 483 | $this->assertEquals(['Amex','Hooli','Microsoft'], [$test5[0]['name'],$test5[1]['name'],$test5[2]['name']]); 484 | 485 | $db->flush(true); 486 | 487 | $companies = ['Google 9', 'Google 3', 'Google 10', 'Google 1', 'Google 2', 'Google 7']; 488 | 489 | foreach($companies as $company) 490 | { 491 | $user = $db->get(uniqid()); 492 | $user->name = $company; 493 | $user->save(); 494 | } 495 | 496 | // order the results ASC (but inject numbers into strings) 497 | $test6 = $db->query()->limit(3)->orderBy('name', 'ASC')->results(); 498 | $this->assertEquals(['Google 1','Google 2','Google 3'], [$test6[0]['name'],$test6[1]['name'],$test6[2]['name']]); 499 | 500 | // order the results DESC (but inject numbers into strings) 501 | $test6 = $db->query()->limit(3)->orderBy('name', 'DESC')->results(); 502 | $this->assertEquals(['Google 10','Google 9','Google 7'], [$test6[0]['name'],$test6[1]['name'],$test6[2]['name']]); 503 | 504 | $db->flush(true); 505 | 506 | } 507 | 508 | 509 | //-------------------------------------------------------------------- 510 | 511 | 512 | /** 513 | * testWhereIn() 514 | * 515 | * TEST CASE: 516 | * - Testing the Where "IN" operator 517 | * 518 | * 519 | */ 520 | public function testWhereIn() 521 | { 522 | $db = new \Filebase\Database([ 523 | 'dir' => __DIR__.'/databases/users_1', 524 | 'cache' => false, 525 | 'format' => \Filebase\Format\Yaml::class 526 | ]); 527 | 528 | $db->flush(true); 529 | 530 | $companies = [ 531 | 'Google'=>[ 532 | 'tags' => [ 533 | 'search','display' 534 | ] 535 | ], 536 | 'Facebook'=>[ 537 | 'tags' => [ 538 | 'social','network' 539 | ] 540 | ], 541 | 'Microsoft'=>[ 542 | 'tags' => [ 543 | 'windows','xbox','search' 544 | ] 545 | ] 546 | ]; 547 | 548 | foreach($companies as $company=>$tags) 549 | { 550 | $user = $db->get(uniqid()); 551 | $user->name = $company; 552 | $user->tags = $tags['tags']; 553 | $user->save(); 554 | } 555 | 556 | 557 | // test that they are ordered by name ASC (check first, second, and last) 558 | $test1 = $db->query()->where('tags','IN','display')->first(); 559 | $test2 = $db->query()->where('tags','IN','network')->first(); 560 | $test3 = $db->query()->where('tags','IN','windows')->first(); 561 | $test4 = $db->query()->where('tags','IN','search')->results(); 562 | 563 | // testing the object return boolean argument 564 | $test5 = $db->query()->where('tags','IN','search')->results(false); 565 | $test6 = $db->query()->where('tags','IN','search')->results(true); 566 | 567 | // make sure the results equal the right names 568 | $this->assertEquals('Google', $test1['name']); 569 | $this->assertEquals('Facebook', $test2['name']); 570 | $this->assertEquals('Microsoft', $test3['name']); 571 | 572 | // check if the method createdAt() exists or not based on the argument boolean 573 | $this->assertEquals(true, method_exists($test5[0], 'createdAt')); 574 | $this->assertEquals(false, method_exists($test6[0], 'createdAt')); 575 | 576 | // check if the results = 2 577 | $this->assertEquals(2, count($test4)); 578 | 579 | // this will test the IN clause if name matches one of these 580 | $test3 = $db->query()->where('name','IN',['Google','Facebook'])->results(); 581 | 582 | $this->assertEquals(2, count($test3)); 583 | 584 | $db->flush(true); 585 | } 586 | 587 | 588 | //-------------------------------------------------------------------- 589 | 590 | 591 | /** 592 | * testWithoutWhere() 593 | * 594 | * TEST CASE: 595 | * - Run queries without using Where() 596 | * - This will run a findAll() 597 | * 598 | * 599 | */ 600 | public function testWithoutWhere() 601 | { 602 | $db = new \Filebase\Database([ 603 | 'dir' => __DIR__.'/databases/users_1', 604 | 'cache' => false, 605 | 'format' => \Filebase\Format\Yaml::class 606 | ]); 607 | 608 | $db->flush(true); 609 | 610 | $companies = [ 611 | 'Google'=>[ 612 | 'site' => 'google.com', 613 | 'type' => 'search' 614 | ], 615 | 'Yahoo'=>[ 616 | 'site' => 'yahoo.com', 617 | 'type' => 'search' 618 | ], 619 | 'Facebook'=>[ 620 | 'site' => 'facebook.com', 621 | 'type' => 'social' 622 | ] 623 | ]; 624 | 625 | foreach($companies as $company=>$options) 626 | { 627 | $user = $db->get(uniqid()); 628 | $user->name = $company; 629 | $user->type = $options['type']; 630 | $user->site = $options['site']; 631 | $user->save(); 632 | } 633 | 634 | 635 | // test that they are ordered by name ASC (check first, second, and last) 636 | $test1 = $db->query()->orderBy('name', 'DESC')->first(); 637 | $test2 = $db->query()->orderBy('name', 'DESC')->first( false ); 638 | $test3 = $db->query()->orderBy('name', 'DESC')->first( true ); 639 | 640 | $this->assertEquals(true, method_exists($test2, 'createdAt')); 641 | $this->assertEquals(false, method_exists($test3, 'createdAt')); 642 | 643 | $this->assertEquals('Yahoo', $test1['name']); 644 | 645 | $db->flush(true); 646 | } 647 | 648 | 649 | //-------------------------------------------------------------------- 650 | 651 | 652 | /** 653 | * testingBadOperator() 654 | * 655 | * BAD TEST CASE: 656 | * - Tries to run a query with an operator that does not exist. 657 | * 658 | */ 659 | public function testingBadOperator() 660 | { 661 | $this->expectException(\InvalidArgumentException::class); 662 | 663 | $db = new \Filebase\Database([ 664 | 'dir' => __DIR__.'/databases/users_1', 665 | 'cache' => false, 666 | 'format' => \Filebase\Format\Yaml::class 667 | ]); 668 | 669 | // FIRST TEST 670 | $db->flush(true); 671 | 672 | $user = $db->get(uniqid()); 673 | $user->name = 'John'; 674 | $user->save(); 675 | 676 | // standard matches 677 | $query = $db->query()->where('name','&','John')->results(); 678 | 679 | $db->flush(true); 680 | } 681 | 682 | 683 | //-------------------------------------------------------------------- 684 | 685 | 686 | /** 687 | * testingBadField() 688 | * 689 | * BAD TEST CASE: 690 | * - Tries to run a query with a blank field 691 | * 692 | */ 693 | public function testingBadField() 694 | { 695 | $this->expectException(\InvalidArgumentException::class); 696 | 697 | $db = new \Filebase\Database([ 698 | 'dir' => __DIR__.'/databases/users_1', 699 | 'cache' => false, 700 | 'format' => \Filebase\Format\Yaml::class 701 | ]); 702 | 703 | // FIRST TEST 704 | $db->flush(true); 705 | 706 | $user = $db->get(uniqid()); 707 | $user->name = 'John'; 708 | $user->save(); 709 | 710 | // standard matches 711 | $query = $db->query()->where('','=','John')->results(); 712 | 713 | $db->flush(true); 714 | } 715 | 716 | 717 | //-------------------------------------------------------------------- 718 | 719 | 720 | /** 721 | * testingMissingQueryArguments() 722 | * 723 | * BAD TEST CASE: 724 | * - Tries to run a query with just a string arg 725 | * 726 | */ 727 | public function testingMissingQueryArguments() 728 | { 729 | $this->expectException(\InvalidArgumentException::class); 730 | 731 | $db = new \Filebase\Database([ 732 | 'dir' => __DIR__.'/databases/users_1', 733 | 'cache' => false, 734 | 'format' => \Filebase\Format\Yaml::class 735 | ]); 736 | 737 | $db->flush(true); 738 | 739 | $user = $db->get(uniqid()); 740 | $user->name = 'John'; 741 | $user->save(); 742 | 743 | // standard matches 744 | $query = $db->query()->where('John')->results(); 745 | 746 | $db->flush(true); 747 | } 748 | 749 | 750 | 751 | //-------------------------------------------------------------------- 752 | 753 | 754 | /** 755 | * testUserNameQuery() 756 | * 757 | * 758 | */ 759 | public function testUserNameQuery() 760 | { 761 | $db = new \Filebase\Database([ 762 | 'dir' => __DIR__.'/databases/users_names', 763 | 'cache' => false, 764 | 'format' => \Filebase\Format\Yaml::class 765 | ]); 766 | 767 | $db->flush(true); 768 | 769 | for ($x = 1; $x <= 10; $x++) 770 | { 771 | $user = $db->get(uniqid()); 772 | $user->name = 'John '.$x; 773 | $user->contact['email'] = 'john@john.com'; 774 | $user->save(); 775 | } 776 | 777 | $userAccountFirst = $db->query()->orderBy('name', 'DESC')->first(); 778 | $userAccountLast = $db->query()->orderBy('name', 'DESC')->last(); 779 | 780 | // testing "first" method and sorting 781 | $this->assertEquals('John 10', $userAccountFirst['name']); 782 | 783 | // testing "last" method and sorting 784 | $this->assertEquals('John 1', $userAccountLast['name']); 785 | 786 | $db->flush(true); 787 | } 788 | 789 | 790 | //-------------------------------------------------------------------- 791 | 792 | 793 | /** 794 | * testQueryCount() 795 | * 796 | * 797 | */ 798 | public function testQueryCount() 799 | { 800 | $db = new \Filebase\Database([ 801 | 'dir' => __DIR__.'/databases/users_qcounter', 802 | 'cache' => false, 803 | 'format' => \Filebase\Format\Yaml::class 804 | ]); 805 | 806 | $db->flush(true); 807 | 808 | for ($x = 1; $x <= 10; $x++) 809 | { 810 | $user = $db->get(uniqid()); 811 | $user->name = 'John '.$x; 812 | $user->contact['email'] = 'john@john.com'; 813 | $user->save(); 814 | } 815 | 816 | $userCount1 = $db->query()->count(); 817 | $userCount2 = $db->query()->where('name','==','Nothing')->count(); 818 | $userCount3 = $db->query()->where('name','==','John 2')->count(); 819 | 820 | // find users that have the number 1 in their name (should be 2) 821 | // John 1 and John 10 822 | $userCount4 = $db->query()->where('name','REGEX','/1/')->count(); 823 | 824 | // should = 10 documents 825 | $this->assertEquals(10, $userCount1); 826 | $this->assertEquals(0, $userCount2); 827 | $this->assertEquals(1, $userCount3); 828 | $this->assertEquals(2, $userCount4); 829 | 830 | $db->flush(true); 831 | } 832 | 833 | 834 | 835 | //-------------------------------------------------------------------- 836 | 837 | 838 | 839 | 840 | 841 | public function testWhereQueryWhereCount() 842 | { 843 | $db = new \Filebase\Database([ 844 | 'dir' => __DIR__.'/databases/users_1', 845 | 'cache' => false, 846 | 'format' => \Filebase\Format\Yaml::class 847 | ]); 848 | 849 | $db->flush(true); 850 | 851 | for ($x = 1; $x <= 10; $x++) 852 | { 853 | $user = $db->get(uniqid()); 854 | $user->name = 'John'; 855 | $user->email = 'john@example.com'; 856 | $user->criteria = [ 857 | 'label' => 'lead' 858 | ]; 859 | 860 | $user->save(); 861 | } 862 | 863 | $results = $db->query() 864 | ->where('name','=','John') 865 | ->andWhere('email','==','john@example.com') 866 | ->results(); 867 | 868 | $this->assertEquals($db->count(), count($results)); 869 | 870 | $db->flush(true); 871 | $db->flushCache(); 872 | } 873 | 874 | 875 | public function testWhereQueryFindNameCount() 876 | { 877 | $db = new \Filebase\Database([ 878 | 'dir' => __DIR__.'/databases/users_2', 879 | 'format' => \Filebase\Format\Yaml::class 880 | ]); 881 | 882 | $db->flush(true); 883 | 884 | for ($x = 1; $x <= 10; $x++) 885 | { 886 | $user = $db->get(uniqid()); 887 | 888 | if ($x < 6) 889 | { 890 | $user->name = 'John'; 891 | } 892 | else 893 | { 894 | $user->name = 'Max'; 895 | } 896 | 897 | $user->save(); 898 | } 899 | 900 | $results = $db->query() 901 | ->where('name','=','John') 902 | ->results(); 903 | 904 | $this->assertEquals(5, count($results)); 905 | 906 | #$db->flush(true); 907 | $db->flushCache(); 908 | } 909 | 910 | 911 | 912 | public function testOrWhereQueryCount() 913 | { 914 | $db = new \Filebase\Database([ 915 | 'dir' => __DIR__.'/databases/users_1', 916 | 'cache' => false, 917 | 'format' => \Filebase\Format\Yaml::class 918 | ]); 919 | 920 | $db->flush(true); 921 | 922 | for ($x = 1; $x <= 10; $x++) 923 | { 924 | $user = $db->get(uniqid()); 925 | 926 | if ($x < 6) 927 | { 928 | $user->name = 'John'; 929 | } 930 | else 931 | { 932 | $user->name = 'Max'; 933 | } 934 | 935 | $user->save(); 936 | } 937 | 938 | $results = $db->query() 939 | ->where('name','=','John') 940 | ->orWhere('name','=','Max') 941 | ->results(); 942 | 943 | $this->assertEquals($db->count(), count($results)); 944 | 945 | $db->flush(true); 946 | $db->flushCache(); 947 | } 948 | 949 | 950 | public function testWhereQueryFromCache() 951 | { 952 | $db = new \Filebase\Database([ 953 | 'dir' => __DIR__.'/databases/users_1', 954 | 'format' => \Filebase\Format\Yaml::class 955 | ]); 956 | 957 | $db->flush(true); 958 | 959 | for ($x = 1; $x <= 10; $x++) 960 | { 961 | $user = $db->get(uniqid()); 962 | $user->name = 'John'; 963 | $user->email = 'john@example.com'; 964 | $user->save(); 965 | } 966 | 967 | $results = $db->query() 968 | ->where('name','=','John') 969 | ->andWhere('email','==','john@example.com') 970 | ->resultDocuments(); 971 | 972 | $result_from_cache = $db->query() 973 | ->where('name','=','John') 974 | ->andWhere('email','==','john@example.com') 975 | ->resultDocuments(); 976 | 977 | $this->assertEquals(10, count($results)); 978 | $this->assertEquals(true, ($result_from_cache[0]->isCache())); 979 | 980 | $db->flush(true); 981 | } 982 | 983 | 984 | public function testQueryFromCacheAfterDelete() 985 | { 986 | $db = new \Filebase\Database([ 987 | 'dir' => __DIR__.'/databases/deleted', 988 | 'cache' => true, 989 | 'format' => \Filebase\Format\Yaml::class 990 | ]); 991 | 992 | $db->flush(true); 993 | 994 | for ($x = 1; $x <= 10; $x++) 995 | { 996 | $user = $db->get(uniqid()); 997 | $user->name = 'John'; 998 | $user->email = 'john@example.com'; 999 | $user->save(); 1000 | } 1001 | 1002 | $results = $db->query()->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); 1003 | $result_from_cache = $db->query()->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); 1004 | 1005 | $this->assertEquals(10, count($results)); 1006 | $this->assertEquals(true, ($result_from_cache[0]->isCache())); 1007 | 1008 | $id = $result_from_cache[0]->getId(); 1009 | $id2 = $result_from_cache[1]->getId(); 1010 | 1011 | // delete the file 1012 | $result_from_cache[0]->delete(); 1013 | 1014 | $results = $db->query() 1015 | ->where('name','=','John') 1016 | ->andWhere('email','==','john@example.com') 1017 | ->resultDocuments(); 1018 | 1019 | $this->assertEquals($id2, $results[0]->getId()); 1020 | 1021 | $db->flush(true); 1022 | } 1023 | 1024 | 1025 | 1026 | public function testQueryFromCacheAfterSave() 1027 | { 1028 | $db = new \Filebase\Database([ 1029 | 'dir' => __DIR__.'/databases/saved', 1030 | 'cache' => true, 1031 | 'format' => \Filebase\Format\Yaml::class 1032 | ]); 1033 | 1034 | $db->flush(true); 1035 | 1036 | for ($x = 1; $x <= 10; $x++) 1037 | { 1038 | $user = $db->get(uniqid()); 1039 | $user->name = 'John'; 1040 | $user->email = 'john@example.com'; 1041 | $user->save(); 1042 | } 1043 | 1044 | $results = $db->query()->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); 1045 | $result_from_cache = $db->query()->where('name','=','John')->andWhere('email','==','john@example.com')->resultDocuments(); 1046 | 1047 | $this->assertEquals(10, count($results)); 1048 | $this->assertEquals(true, ($result_from_cache[0]->isCache())); 1049 | 1050 | $id = $result_from_cache[0]->getId(); 1051 | $id2 = $result_from_cache[1]->getId(); 1052 | 1053 | // Change the name 1054 | $result_from_cache[0]->name = 'Tim'; 1055 | $result_from_cache[0]->save(); 1056 | 1057 | $results = $db->query() 1058 | ->where('name','=','John') 1059 | ->andWhere('email','==','john@example.com') 1060 | ->resultDocuments(); 1061 | 1062 | $this->assertEquals($id2, $results[0]->getId()); 1063 | $this->assertEquals('John', $results[0]->name); 1064 | 1065 | $db->flush(true); 1066 | } 1067 | 1068 | } 1069 | --------------------------------------------------------------------------------