SleekDB.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <?php
  2. namespace SleekDB;
  3. use SleekDB\Exceptions\ConditionNotAllowedException;
  4. use SleekDB\Exceptions\EmptyFieldNameException;
  5. use SleekDB\Exceptions\EmptyStoreDataException;
  6. use SleekDB\Exceptions\EmptyStoreNameException;
  7. use SleekDB\Exceptions\IdNotAllowedException;
  8. use SleekDB\Exceptions\IndexNotFoundException;
  9. use SleekDB\Exceptions\InvalidConfigurationException;
  10. use SleekDB\Exceptions\InvalidDataException;
  11. use SleekDB\Exceptions\InvalidStoreDataException;
  12. use SleekDB\Exceptions\IOException;
  13. use SleekDB\Exceptions\JsonException;
  14. use SleekDB\Traits\HelperTrait;
  15. use SleekDB\Traits\CacheTrait;
  16. use SleekDB\Traits\ConditionTrait;
  17. // to provide usage without composer, we need to require the files
  18. require_once __DIR__ . "/Exceptions/ConditionNotAllowedException.php";
  19. require_once __DIR__ . "/Exceptions/EmptyFieldNameException.php";
  20. require_once __DIR__ . "/Exceptions/EmptyStoreNameException.php";
  21. require_once __DIR__ . "/Exceptions/IdNotAllowedException.php";
  22. require_once __DIR__ . "/Exceptions/IndexNotFoundException.php";
  23. require_once __DIR__ . "/Exceptions/InvalidConfigurationException.php";
  24. require_once __DIR__ . "/Exceptions/InvalidDataException.php";
  25. require_once __DIR__ . "/Exceptions/InvalidStoreDataException.php";
  26. require_once __DIR__ . "/Exceptions/IOException.php";
  27. require_once __DIR__ . "/Exceptions/JsonException.php";
  28. require_once __DIR__ . "/Traits/HelperTrait.php";
  29. require_once __DIR__ . "/Traits/CacheTrait.php";
  30. require_once __DIR__ . "/Traits/ConditionTrait.php";
  31. class SleekDB{
  32. use HelperTrait;
  33. use ConditionTrait;
  34. use CacheTrait;
  35. private $root = __DIR__;
  36. private $storeName;
  37. private $makeCache;
  38. private $useCache;
  39. private $deleteCacheOnCreate;
  40. private $storePath;
  41. private $dataDirectory;
  42. private $shouldKeepConditions;
  43. private $results;
  44. private $limit;
  45. private $skip;
  46. private $conditions;
  47. private $orConditions;
  48. private $in;
  49. private $notIn;
  50. private $orderBy;
  51. private $searchKeyword;
  52. private $fieldsToSelect = [];
  53. private $fieldsToExclude = [];
  54. private $orConditionsWithAnd = [];
  55. /**
  56. * SleekDB constructor.
  57. * Initialize the database.
  58. * @param string $dataDir
  59. * @param array $configurations
  60. * @throws IOException
  61. * @throws InvalidConfigurationException
  62. */
  63. function __construct( $dataDir = '', $configurations = [] ) {
  64. // Add data dir.
  65. $configurations[ 'data_directory' ] = $dataDir;
  66. // Initialize SleekDB
  67. $this->init( $configurations );
  68. }
  69. /**
  70. * Initialize the store.
  71. * @param string $storeName
  72. * @param string $dataDir
  73. * @param array $options
  74. * @return SleekDB
  75. * @throws EmptyStoreNameException
  76. * @throws IOException
  77. * @throws InvalidConfigurationException
  78. */
  79. public static function store( $storeName, $dataDir, $options = [] ) {
  80. if ( empty( $storeName ) ) throw new EmptyStoreNameException( 'Store name was not valid' );
  81. $_dbInstance = new SleekDB( $dataDir, $options );
  82. $_dbInstance->storeName = $storeName;
  83. // Boot store.
  84. $_dbInstance->bootStore();
  85. // Initialize variables for the store.
  86. $_dbInstance->initVariables();
  87. return $_dbInstance;
  88. }
  89. /**
  90. * Read store objects.
  91. * @return array
  92. * @throws ConditionNotAllowedException
  93. * @throws IndexNotFoundException
  94. * @throws EmptyFieldNameException
  95. * @throws InvalidDataException
  96. */
  97. public function fetch() {
  98. $fetchedData = null;
  99. // Check if data should be provided from the cache.
  100. if ( $this->makeCache === true ) {
  101. $fetchedData = $this->reGenerateCache(); // Re-generate cache.
  102. }
  103. else if ( $this->useCache === true ) {
  104. $fetchedData = $this->useExistingCache(); // Use existing cache else re-generate.
  105. }
  106. else {
  107. $fetchedData = $this->findStoreDocuments(); // Returns data without looking for cached data.
  108. }
  109. $this->initVariables(); // Reset state.
  110. return $fetchedData;
  111. }
  112. /**
  113. * Creates a new object in the store.
  114. * The object is a plaintext JSON document.
  115. * @param array $storeData
  116. * @return array
  117. * @throws EmptyStoreDataException
  118. * @throws IOException
  119. * @throws InvalidStoreDataException
  120. * @throws JsonException
  121. * @throws IdNotAllowedException
  122. */
  123. public function insert( $storeData ) {
  124. // Handle invalid data
  125. if ( empty( $storeData ) ) throw new EmptyStoreDataException( 'No data found to store' );
  126. // Make sure that the data is an array
  127. if ( ! is_array( $storeData ) ) throw new InvalidStoreDataException( 'Storable data must an array' );
  128. $storeData = $this->writeInStore( $storeData );
  129. // Check do we need to wipe the cache for this store.
  130. if ( $this->deleteCacheOnCreate === true ) $this->_emptyAllCache();
  131. return $storeData;
  132. }
  133. /**
  134. * Creates multiple objects in the store.
  135. * @param $storeData
  136. * @return array
  137. * @throws EmptyStoreDataException
  138. * @throws IOException
  139. * @throws InvalidStoreDataException
  140. * @throws JsonException
  141. * @throws IdNotAllowedException
  142. */
  143. public function insertMany( $storeData ) {
  144. // Handle invalid data
  145. if ( empty( $storeData ) ) throw new EmptyStoreDataException( 'No data found to insert in the store' );
  146. // Make sure that the data is an array
  147. if ( ! is_array( $storeData ) ) throw new InvalidStoreDataException( 'Data must be an array in order to insert in the store' );
  148. // All results.
  149. $results = [];
  150. foreach ( $storeData as $key => $node ) {
  151. $results[] = $this->writeInStore( $node );
  152. }
  153. // Check do we need to wipe the cache for this store.
  154. if ( $this->deleteCacheOnCreate === true ) $this->_emptyAllCache();
  155. return $results;
  156. }
  157. /**
  158. * @param $updatable
  159. * @return bool
  160. * @throws IndexNotFoundException
  161. * @throws ConditionNotAllowedException
  162. * @throws EmptyFieldNameException
  163. * @throws InvalidDataException
  164. */
  165. public function update($updatable ) {
  166. // Find all store objects.
  167. $storeObjects = $this->findStoreDocuments();
  168. // If no store object found then return an empty array.
  169. if ( empty( $storeObjects ) ) {
  170. $this->initVariables(); // Reset state.
  171. return false;
  172. }
  173. foreach ( $storeObjects as $data ) {
  174. foreach ($updatable as $key => $value ) {
  175. // Do not update the _id reserved index of a store.
  176. if( $key != '_id' ) {
  177. $data[ $key ] = $value;
  178. }
  179. }
  180. $storePath = $this->storePath . 'data/' . $data[ '_id' ] . '.json';
  181. if ( file_exists( $storePath ) ) {
  182. // Wait until it's unlocked, then update data.
  183. file_put_contents( $storePath, json_encode( $data ), LOCK_EX );
  184. }
  185. }
  186. // Check do we need to wipe the cache for this store.
  187. if ( $this->deleteCacheOnCreate === true ) $this->_emptyAllCache();
  188. $this->initVariables(); // Reset state.
  189. return true;
  190. }
  191. /**
  192. * Deletes matched store objects.
  193. * @return bool
  194. * @throws IOException
  195. * @throws IndexNotFoundException
  196. * @throws ConditionNotAllowedException
  197. * @throws EmptyFieldNameException
  198. * @throws InvalidDataException
  199. */
  200. public function delete() {
  201. // Find all store objects.
  202. $storeObjects = $this->findStoreDocuments();
  203. if ( ! empty( $storeObjects ) ) {
  204. foreach ( $storeObjects as $data ) {
  205. if ( ! unlink( $this->storePath . 'data/' . $data[ '_id' ] . '.json' ) ) {
  206. $this->initVariables(); // Reset state.
  207. throw new IOException(
  208. 'Unable to delete storage file!
  209. Location: "'.$this->storePath . 'data/' . $data[ '_id' ] . '.json'.'"'
  210. );
  211. }
  212. }
  213. // Check do we need to wipe the cache for this store.
  214. if ( $this->deleteCacheOnCreate === true ) $this->_emptyAllCache();
  215. $this->initVariables(); // Reset state.
  216. return true;
  217. } else {
  218. // Nothing found to delete
  219. $this->initVariables(); // Reset state.
  220. return true;
  221. // throw new \Exception( 'Invalid store object found, nothing to delete.' );
  222. }
  223. }
  224. /**
  225. * Deletes a store and wipes all the data and cache it contains.
  226. * @return bool
  227. */
  228. public function deleteStore() {
  229. $it = new \RecursiveDirectoryIterator( $this->storePath, \RecursiveDirectoryIterator::SKIP_DOTS );
  230. $files = new \RecursiveIteratorIterator( $it, \RecursiveIteratorIterator::CHILD_FIRST );
  231. foreach( $files as $file ) {
  232. if ( $file->isDir() ) rmdir( $file->getRealPath() );
  233. else unlink( $file->getRealPath() );
  234. }
  235. return rmdir( $this->storePath );
  236. }
  237. }