PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / 3.1.13.4
JetBackup – Backup, Restore & Migrate v3.1.13.4
3.1.22.4 3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / 3rdparty / SleekDB / Store.php
backup / src / JetBackup / 3rdparty / SleekDB Last commit date
Classes 9 months ago Exceptions 9 months ago .htaccess 9 months ago Cache.php 9 months ago Query.php 9 months ago QueryBuilder.php 9 months ago SleekDB.php 9 months ago Store.php 9 months ago autoload.php 9 months ago index.html 9 months ago web.config 9 months ago
Store.php
899 lines
1 <?php
2
3 namespace SleekDB;
4
5 use Exception;
6 use SleekDB\Classes\IoHelper;
7 use SleekDB\Classes\NestedHelper;
8 use SleekDB\Exceptions\InvalidArgumentException;
9 use SleekDB\Exceptions\IdNotAllowedException;
10 use SleekDB\Exceptions\InvalidConfigurationException;
11 use SleekDB\Exceptions\IOException;
12 use SleekDB\Exceptions\JsonException;
13
14 class Store
15 {
16
17 protected $root = __DIR__;
18
19 protected $storeName = "";
20 protected $storePath = "";
21
22 protected $databasePath = "";
23
24 protected $useCache = true;
25 protected $folderPermissions = 0777;
26 protected $defaultCacheLifetime;
27 protected $primaryKey = "_id";
28 protected $timeout = 120;
29 protected $searchOptions = [
30 "minLength" => 2,
31 "scoreKey" => "searchScore",
32 "mode" => "or",
33 "algorithm" => Query::SEARCH_ALGORITHM["hits"]
34 ];
35
36 const dataDirectory = "data" . DIRECTORY_SEPARATOR;
37
38 /**
39 * Store constructor.
40 *
41 * @param string $storeName
42 * @param string $databasePath
43 * @param array $configuration
44 *
45 * @throws InvalidArgumentException
46 * @throws IOException
47 * @throws InvalidConfigurationException
48 */
49 public function __construct(string $storeName, string $databasePath, array $configuration = [])
50 {
51 $storeName = trim($storeName);
52 if (empty($storeName)) {
53 throw new InvalidArgumentException('store name can not be empty');
54 }
55 $this->storeName = $storeName;
56
57 $databasePath = trim($databasePath);
58 if (empty($databasePath)) {
59 throw new InvalidArgumentException('data directory can not be empty');
60 }
61
62 IoHelper::normalizeDirectory($databasePath);
63 $this->databasePath = $databasePath;
64
65 $this->setConfiguration($configuration);
66
67 // boot store
68 $this->createDatabasePath();
69 $this->createStore();
70 }
71
72 /**
73 * Change the destination of the store object.
74 *
75 * @param string $storeName
76 * @param string|null $databasePath If empty, previous database path will be used.
77 * @param array $configuration
78 *
79 * @return Store
80 * @throws IOException
81 * @throws InvalidArgumentException
82 * @throws InvalidConfigurationException
83 */
84 public function changeStore(string $storeName, ?string $databasePath = null, array $configuration = []): Store
85 {
86 if(empty($databasePath)){
87 $databasePath = $this->getDatabasePath();
88 }
89 $this->__construct($storeName, $databasePath, $configuration);
90 return $this;
91 }
92
93 /**
94 * @return string
95 */
96 public function getStoreName(): string
97 {
98 return $this->storeName;
99 }
100
101 /**
102 * @return string
103 */
104 public function getDatabasePath(): string
105 {
106 return $this->databasePath;
107 }
108
109 /**
110 * @return QueryBuilder
111 */
112 public function createQueryBuilder(): QueryBuilder
113 {
114 return new QueryBuilder($this);
115 }
116
117 /**
118 * Insert a new document to the store.
119 * It is stored as a plaintext JSON document.
120 * @param array $data
121 * @return array inserted document
122 * @throws IOException
123 * @throws IdNotAllowedException
124 * @throws InvalidArgumentException
125 * @throws JsonException
126 */
127 public function insert(array $data): array
128 {
129 // Handle invalid data
130 if (empty($data)) {
131 throw new InvalidArgumentException('No data found to insert in the store');
132 }
133
134 $data = $this->writeNewDocumentToStore($data);
135
136 $this->createQueryBuilder()->getQuery()->getCache()->deleteAllWithNoLifetime();
137
138 return $data;
139 }
140
141 /**
142 * Insert multiple documents to the store.
143 * They are stored as plaintext JSON documents.
144 * @param array $data
145 * @return array inserted documents
146 * @throws IOException
147 * @throws IdNotAllowedException
148 * @throws InvalidArgumentException
149 * @throws JsonException
150 */
151 public function insertMany(array $data): array
152 {
153 // Handle invalid data
154 if (empty($data)) {
155 throw new InvalidArgumentException('No data found to insert in the store');
156 }
157
158 // All results.
159 $results = [];
160 foreach ($data as $document) {
161 $results[] = $this->writeNewDocumentToStore($document);
162 }
163
164 $this->createQueryBuilder()->getQuery()->getCache()->deleteAllWithNoLifetime();
165 return $results;
166 }
167
168
169 /**
170 * Delete store with all its data and cache.
171 * @return bool
172 * @throws IOException
173 */
174 public function deleteStore(): bool
175 {
176 $storePath = $this->getStorePath();
177 return IoHelper::deleteFolder($storePath);
178 }
179
180
181 /**
182 * Return the last created store object ID.
183 * @return int
184 * @throws IOException
185 */
186 public function getLastInsertedId(): int
187 {
188 $counterPath = $this->getStorePath() . '_cnt.sdb';
189
190 return (int) IoHelper::getFileContentRaw($counterPath);
191 }
192
193 /**
194 * @return string
195 */
196 public function getStorePath(): string
197 {
198 return $this->storePath;
199 }
200
201 /**
202 * Retrieve all documents.
203 *
204 * @param array|null $orderBy array($fieldName => $order). $order can be "asc" or "desc"
205 * @param int|null $limit the amount of data record to limit
206 * @param int|null $offset the amount of data record to skip
207 *
208 * @return array
209 * @throws IOException
210 * @throws InvalidArgumentException
211 */
212 public function findAll(?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
213 {
214 $qb = $this->createQueryBuilder();
215 if(!is_null($orderBy)){
216 $qb->orderBy($orderBy);
217 }
218 if(!is_null($limit)){
219 $qb->limit($limit);
220 }
221 if(!is_null($offset)){
222 $qb->skip($offset);
223 }
224 return $qb->getQuery()->fetch();
225 }
226
227 /**
228 * Retrieve one document by its primary key. Very fast because it finds the document by its file path.
229 * @param int|string $id
230 * @return array|null
231 * @throws InvalidArgumentException
232 */
233 public function findById($id){
234
235 $id = $this->checkAndStripId($id);
236
237 $filePath = $this->getDataPath() . "$id.json";
238
239 try{
240 $content = IoHelper::getFileContent($filePath);
241 } catch (Exception $exception){
242 return null;
243 }
244
245 return @json_decode($content, true);
246 }
247
248 /**
249 * Retrieve one or multiple documents.
250 *
251 * @param array $criteria
252 * @param array|null $orderBy
253 * @param int|null $limit
254 * @param int|null $offset
255 *
256 * @return array
257 * @throws IOException
258 * @throws InvalidArgumentException
259 */
260 public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
261 {
262 $qb = $this->createQueryBuilder();
263
264 $qb->where($criteria);
265
266 if($orderBy !== null) {
267 $qb->orderBy($orderBy);
268 }
269 if($limit !== null) {
270 $qb->limit($limit);
271 }
272 if($offset !== null) {
273 $qb->skip($offset);
274 }
275
276 return $qb->getQuery()->fetch();
277 }
278
279 /**
280 * Retrieve one document.
281 * @param array $criteria
282 * @return array|null single document or NULL if no document can be found
283 * @throws IOException
284 * @throws InvalidArgumentException
285 */
286 public function findOneBy(array $criteria)
287 {
288 $qb = $this->createQueryBuilder();
289
290 $qb->where($criteria);
291
292 $result = $qb->getQuery()->first();
293
294 return (!empty($result))? $result : null;
295
296 }
297
298 /**
299 * Update or insert one document.
300 * @param array $data
301 * @param bool $autoGenerateIdOnInsert
302 * @return array updated / inserted document
303 * @throws IOException
304 * @throws InvalidArgumentException
305 * @throws JsonException
306 */
307 public function updateOrInsert(array $data, bool $autoGenerateIdOnInsert = true): array
308 {
309 $primaryKey = $this->getPrimaryKey();
310
311 if(empty($data)) {
312 throw new InvalidArgumentException("No document to update or insert.");
313 }
314
315 // // we can use this check to determine if multiple documents are given
316 // // because documents have to have at least the primary key.
317 // if(array_keys($data) !== range(0, (count($data) - 1))){
318 // $data = [ $data ];
319 // }
320
321 if(!array_key_exists($primaryKey, $data)) {
322 // $documentString = var_export($document, true);
323 // throw new InvalidArgumentException("Documents have to have the primary key \"$primaryKey\". Got data: $documentString");
324 $data[$primaryKey] = $this->increaseCounterAndGetNextId();
325 } else {
326 $data[$primaryKey] = $this->checkAndStripId($data[$primaryKey]);
327 if($autoGenerateIdOnInsert && $this->findById($data[$primaryKey]) === null){
328 $data[$primaryKey] = $this->increaseCounterAndGetNextId();
329 }
330 }
331
332 // One document to update or insert
333
334 // save to access file with primary key value because we secured it above
335 $storePath = $this->getDataPath() . "$data[$primaryKey].json";
336 IoHelper::writeContentToFile($storePath, json_encode($data));
337
338 $this->createQueryBuilder()->getQuery()->getCache()->deleteAllWithNoLifetime();
339
340 return $data;
341 }
342
343 /**
344 * Update or insert multiple documents.
345 * @param array $data
346 * @param bool $autoGenerateIdOnInsert
347 * @return array updated / inserted documents
348 * @throws IOException
349 * @throws InvalidArgumentException
350 * @throws JsonException
351 */
352 public function updateOrInsertMany(array $data, bool $autoGenerateIdOnInsert = true): array
353 {
354 $primaryKey = $this->getPrimaryKey();
355
356 if(empty($data)) {
357 throw new InvalidArgumentException("No documents to update or insert.");
358 }
359
360 // // we can use this check to determine if multiple documents are given
361 // // because documents have to have at least the primary key.
362 // if(array_keys($data) !== range(0, (count($data) - 1))){
363 // $data = [ $data ];
364 // }
365
366 // Check if all documents have the primary key before updating or inserting any
367 foreach ($data as $key => $document){
368 if(!is_array($document)) {
369 throw new InvalidArgumentException('Documents have to be an arrays.');
370 }
371 if(!array_key_exists($primaryKey, $document)) {
372 // $documentString = var_export($document, true);
373 // throw new InvalidArgumentException("Documents have to have the primary key \"$primaryKey\". Got data: $documentString");
374 $document[$primaryKey] = $this->increaseCounterAndGetNextId();
375 } else {
376 $document[$primaryKey] = $this->checkAndStripId($document[$primaryKey]);
377 if($autoGenerateIdOnInsert && $this->findById($document[$primaryKey]) === null){
378 $document[$primaryKey] = $this->increaseCounterAndGetNextId();
379 }
380 }
381 // after the stripping and checking we apply it back
382 $data[$key] = $document;
383 }
384
385 // One or multiple documents to update or insert
386 foreach ($data as $document) {
387 // save to access file with primary key value because we secured it above
388 $storePath = $this->getDataPath() . "$document[$primaryKey].json";
389 IoHelper::writeContentToFile($storePath, json_encode($document));
390 }
391
392 $this->createQueryBuilder()->getQuery()->getCache()->deleteAllWithNoLifetime();
393
394 return $data;
395 }
396
397
398 /**
399 * Update one or multiple documents.
400 * @param array $updatable
401 * @return bool true if all documents could be updated and false if one document did not exist
402 * @throws IOException
403 * @throws InvalidArgumentException
404 */
405 public function update(array $updatable): bool
406 {
407 $primaryKey = $this->getPrimaryKey();
408
409 if(empty($updatable)) {
410 throw new InvalidArgumentException("No documents to update.");
411 }
412
413 // we can use this check to determine if multiple documents are given
414 // because documents have to have at least the primary key.
415 if(array_keys($updatable) !== range(0, (count($updatable) - 1))){
416 $updatable = [ $updatable ];
417 }
418
419 // Check if all documents exist and have the primary key before updating any
420 foreach ($updatable as $key => $document){
421 if(!is_array($document)) {
422 throw new InvalidArgumentException('Documents have to be an arrays.');
423 }
424 if(!array_key_exists($primaryKey, $document)) {
425 throw new InvalidArgumentException("Documents have to have the primary key \"$primaryKey\".");
426 }
427
428 $document[$primaryKey] = $this->checkAndStripId($document[$primaryKey]);
429 // after the stripping and checking we apply it back to the updatable array.
430 $updatable[$key] = $document;
431
432 $storePath = $this->getDataPath() . "$document[$primaryKey].json";
433
434 if (!file_exists($storePath)) {
435 return false;
436 }
437 }
438
439 // One or multiple documents to update
440 foreach ($updatable as $document) {
441 // save to access file with primary key value because we secured it above
442 $storePath = $this->getDataPath() . "$document[$primaryKey].json";
443 IoHelper::writeContentToFile($storePath, json_encode($document));
444 }
445
446 $this->createQueryBuilder()->getQuery()->getCache()->deleteAllWithNoLifetime();
447
448 return true;
449 }
450
451 /**
452 * Update properties of one document.
453 * @param int|string $id
454 * @param array $updatable
455 * @return array|false Updated document or false if document does not exist.
456 * @throws IOException If document could not be read or written.
457 * @throws InvalidArgumentException If one key to update is primary key or $id is not int or string.
458 * @throws JsonException If content of document file could not be decoded.
459 */
460 public function updateById($id, array $updatable)
461 {
462
463
464 $id = $this->checkAndStripId($id);
465
466 $filePath = $this->getDataPath() . "$id.json";
467
468 $primaryKey = $this->getPrimaryKey();
469
470 if(array_key_exists($primaryKey, $updatable)) {
471 throw new InvalidArgumentException("You can not update the primary key \"$primaryKey\" of documents.");
472 }
473
474 if(!file_exists($filePath)){
475 return false;
476 }
477
478 $content = IoHelper::updateFileContent($filePath, function($content) use ($filePath, $updatable){
479
480 $content = @json_decode($content, true);
481
482 if(!is_array($content)){
483 throw new JsonException("[updateFileContent] Could not decode content of \"$filePath\" with json_decode.");
484 }
485
486 foreach ($updatable as $key => $value){
487 NestedHelper::updateNestedValue($key, $content, $value);
488 }
489
490 return json_encode($content);
491
492 });
493
494 $this->createQueryBuilder()->getQuery()->getCache()->deleteAllWithNoLifetime();
495
496 return json_decode($content, true);
497 }
498
499 /**
500 * Delete one or multiple documents.
501 * @param array $criteria
502 * @param int $returnOption
503 * @return array|bool|int
504 * @throws IOException
505 * @throws InvalidArgumentException
506 */
507 public function deleteBy(array $criteria, int $returnOption = Query::DELETE_RETURN_BOOL){
508
509 $query = $this->createQueryBuilder()->where($criteria)->getQuery();
510
511 $query->getCache()->deleteAllWithNoLifetime();
512
513 return $query->delete($returnOption);
514 }
515
516 /**
517 * Delete one document by its primary key. Very fast because it deletes the document by its file path.
518 * @param int|string $id
519 * @return bool true if document does not exist or deletion was successful, false otherwise
520 * @throws InvalidArgumentException
521 */
522 public function deleteById($id): bool
523 {
524
525 $id = $this->checkAndStripId($id);
526
527 $filePath = $this->getDataPath() . "$id.json";
528
529 $this->createQueryBuilder()->getQuery()->getCache()->deleteAllWithNoLifetime();
530
531 return (!file_exists($filePath) || true === @unlink($filePath));
532 }
533
534 /**
535 * Remove fields from one document by its primary key.
536 * @param int|string $id
537 * @param array $fieldsToRemove
538 * @return false|array
539 * @throws IOException
540 * @throws InvalidArgumentException
541 * @throws JsonException
542 */
543 public function removeFieldsById($id, array $fieldsToRemove)
544 {
545 $id = $this->checkAndStripId($id);
546 $filePath = $this->getDataPath() . "$id.json";
547 $primaryKey = $this->getPrimaryKey();
548
549 if(in_array($primaryKey, $fieldsToRemove, false)) {
550 throw new InvalidArgumentException("You can not remove the primary key \"$primaryKey\" of documents.");
551 }
552 if(!file_exists($filePath)){
553 return false;
554 }
555
556 $content = IoHelper::updateFileContent($filePath, function($content) use ($filePath, $fieldsToRemove){
557 $content = @json_decode($content, true);
558 if(!is_array($content)){
559 throw new JsonException("[removeFieldsById] Could not decode content of \"$filePath\" with json_decode.");
560 }
561 foreach ($fieldsToRemove as $fieldToRemove){
562 NestedHelper::removeNestedField($content, $fieldToRemove);
563 }
564 return $content;
565 });
566
567 $this->createQueryBuilder()->getQuery()->getCache()->deleteAllWithNoLifetime();
568
569 return json_decode($content, true);
570 }
571
572 /**
573 * Do a fulltext like search against one or multiple fields.
574 *
575 * @param array $fields
576 * @param string $query
577 * @param array|null $orderBy
578 * @param int|null $limit
579 * @param int|null $offset
580 *
581 * @return array
582 * @throws IOException
583 * @throws InvalidArgumentException
584 */
585 public function search(array $fields, string $query, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
586 {
587
588 $qb = $this->createQueryBuilder();
589
590 $qb->search($fields, $query);
591
592 if($orderBy !== null) {
593 $qb->orderBy($orderBy);
594 }
595
596 if($limit !== null) {
597 $qb->limit($limit);
598 }
599
600 if($offset !== null) {
601 $qb->skip($offset);
602 }
603
604 return $qb->getQuery()->fetch();
605 }
606
607 /**
608 * Get the name of the field used as the primary key.
609 * @return string
610 */
611 public function getPrimaryKey(): string
612 {
613 return $this->primaryKey;
614 }
615
616 /**
617 * Returns the amount of documents in the store.
618 * @return int
619 * @throws IOException
620 */
621 public function count(): int
622 {
623 if($this->_getUseCache() === true){
624 $cacheTokenArray = ["count" => true];
625 $cache = new Cache($this->getStorePath(), $cacheTokenArray, null);
626 $cacheValue = $cache->get();
627 if(is_array($cacheValue) && array_key_exists("count", $cacheValue)){
628 return $cacheValue["count"];
629 }
630 }
631 $value = [
632 "count" => IoHelper::countFolderContent($this->getDataPath())
633 ];
634 if(isset($cache)) {
635 $cache->set($value);
636 }
637 return $value["count"];
638 }
639
640 /**
641 * Returns the search options of the store.
642 * @return array
643 */
644 public function _getSearchOptions(): array
645 {
646 return $this->searchOptions;
647 }
648
649 /**
650 * Returns if caching is enabled store wide.
651 * @return bool
652 */
653 public function _getUseCache(): bool
654 {
655 return $this->useCache;
656 }
657
658 /**
659 * Returns the store wide default cache lifetime.
660 * @return null|int
661 */
662 public function _getDefaultCacheLifetime()
663 {
664 return $this->defaultCacheLifetime;
665 }
666
667 /**
668 * @return string
669 * @deprecated since version 2.7, use getDatabasePath instead.
670 */
671 public function getDataDirectory(): string
672 {
673 // TODO remove with version 3.0
674 return $this->databasePath;
675 }
676
677 /**
678 * @throws IOException
679 */
680 private function createDatabasePath()
681 {
682 $databasePath = $this->getDatabasePath();
683 IoHelper::createFolder($databasePath, $this->folderPermissions);
684 }
685
686 /**
687 * @throws IOException
688 */
689 private function createStore()
690 {
691 $storeName = $this->getStoreName();
692 // Prepare store name.
693 IoHelper::normalizeDirectory($storeName);
694 // Store directory path.
695 $this->storePath = $this->getDatabasePath() . $storeName;
696 $storePath = $this->getStorePath();
697 IoHelper::createFolder($storePath, $this->folderPermissions);
698
699 // Create the cache directory.
700 $cacheDirectory = $storePath . 'cache';
701 IoHelper::createFolder($cacheDirectory, $this->folderPermissions);
702
703 // Create the data directory.
704 IoHelper::createFolder($storePath . self::dataDirectory, $this->folderPermissions);
705
706 // Create the store counter file.
707 $counterFile = $storePath . '_cnt.sdb';
708 if(!file_exists($counterFile)){
709 IoHelper::writeContentToFile($counterFile, '0');
710 }
711 }
712
713 /**
714 * @param array $configuration
715 * @throws InvalidConfigurationException
716 */
717 private function setConfiguration(array $configuration)
718 {
719 if(array_key_exists("auto_cache", $configuration)){
720 $autoCache = $configuration["auto_cache"];
721 if(!is_bool($configuration["auto_cache"])){
722 throw new InvalidConfigurationException("auto_cache has to be boolean");
723 }
724
725 $this->useCache = $autoCache;
726 }
727
728 if(array_key_exists("cache_lifetime", $configuration)){
729 $defaultCacheLifetime = $configuration["cache_lifetime"];
730 if(!is_int($defaultCacheLifetime) && !is_null($defaultCacheLifetime)){
731 throw new InvalidConfigurationException("cache_lifetime has to be null or int");
732 }
733
734 $this->defaultCacheLifetime = $defaultCacheLifetime;
735 }
736
737 // TODO remove timeout on major update
738 // Set timeout.
739 if (array_key_exists("timeout", $configuration)) {
740 if ((!is_int($configuration['timeout']) || $configuration['timeout'] <= 0) && !($configuration['timeout'] === false)){
741 throw new InvalidConfigurationException("timeout has to be an int > 0 or false");
742 }
743 $this->timeout = $configuration["timeout"];
744 }
745 if($this->timeout !== false){
746 $message = 'The "timeout" configuration is deprecated and will be removed with the next major update.' .
747 ' Set the "timeout" configuration to false and if needed use the set_timeout_limit() function in your own code.';
748 trigger_error($message, E_USER_DEPRECATED);
749 set_time_limit($this->timeout);
750 }
751
752 if(array_key_exists("primary_key", $configuration)){
753 $primaryKey = $configuration["primary_key"];
754 if(!is_string($primaryKey)){
755 throw new InvalidConfigurationException("primary key has to be a string");
756 }
757 $this->primaryKey = $primaryKey;
758 }
759
760 if(array_key_exists("search", $configuration)){
761 $searchConfig = $configuration["search"];
762
763 if(array_key_exists("min_length", $searchConfig)){
764 $searchMinLength = $searchConfig["min_length"];
765 if(!is_int($searchMinLength) || $searchMinLength <= 0){
766 throw new InvalidConfigurationException("min length for searching has to be an int >= 0");
767 }
768 $this->searchOptions["minLength"] = $searchMinLength;
769 }
770
771 if(array_key_exists("mode", $searchConfig)){
772 $searchMode = $searchConfig["mode"];
773 if(!is_string($searchMode) || !in_array(strtolower(trim($searchMode)), ["and", "or"])){
774 throw new InvalidConfigurationException("search mode can just be \"and\" or \"or\"");
775 }
776 $this->searchOptions["mode"] = strtolower(trim($searchMode));
777 }
778
779 if(array_key_exists("score_key", $searchConfig)){
780 $searchScoreKey = $searchConfig["score_key"];
781 if((!is_string($searchScoreKey) && !is_null($searchScoreKey))){
782 throw new InvalidConfigurationException("search score key for search has to be a not empty string or null");
783 }
784 $this->searchOptions["scoreKey"] = $searchScoreKey;
785 }
786
787 if(array_key_exists("algorithm", $searchConfig)){
788 $searchAlgorithm = $searchConfig["algorithm"];
789 if(!in_array($searchAlgorithm, Query::SEARCH_ALGORITHM, true)){
790 $searchAlgorithm = implode(', ', $searchAlgorithm);
791 throw new InvalidConfigurationException("The search algorithm has to be one of the following integer values ($searchAlgorithm)");
792 }
793 $this->searchOptions["algorithm"] = $searchAlgorithm;
794 }
795 }
796
797 if ( array_key_exists("folder_permissions", $configuration) ) {
798 $folderPermissions = $configuration["folder_permissions"];
799 if ( !is_int($folderPermissions) ) {
800 throw new InvalidConfigurationException("folder_permissions has to be an integer (e.g. 0777)");
801 }
802 $this->folderPermissions = $folderPermissions;
803 }
804 }
805
806 /**
807 * Writes an object in a store.
808 * @param array $storeData
809 * @return array
810 * @throws IOException
811 * @throws IdNotAllowedException
812 * @throws JsonException
813 */
814 private function writeNewDocumentToStore(array $storeData): array
815 {
816 $primaryKey = $this->getPrimaryKey();
817 // Check if it has the primary key
818 if (isset($storeData[$primaryKey])) {
819 throw new IdNotAllowedException(
820 "The \"$primaryKey\" index is reserved by SleekDB, please delete the $primaryKey key and try again"
821 );
822 }
823 $id = $this->increaseCounterAndGetNextId();
824 // Add the system ID with the store data array.
825 $storeData[$primaryKey] = $id;
826 // Prepare storable data
827 $storableJSON = @json_encode($storeData);
828 if ($storableJSON === false) {
829 throw new JsonException('Unable to encode the data array,
830 please provide a valid PHP associative array');
831 }
832 // Define the store path
833 $filePath = $this->getDataPath()."$id.json";
834
835 IoHelper::writeContentToFile($filePath, $storableJSON);
836
837 return $storeData;
838 }
839
840 /**
841 * Increments the store wide unique store object ID and returns it.
842 * @return int
843 * @throws IOException
844 * @throws JsonException
845 */
846 private function increaseCounterAndGetNextId(): int
847 {
848 $counterPath = $this->getStorePath() . '_cnt.sdb';
849
850 if (!file_exists($counterPath)) {
851 throw new IOException("File $counterPath does not exist.");
852 }
853
854 $dataPath = $this->getDataPath();
855
856 return (int) IoHelper::updateFileContent($counterPath, function ($counter) use ($dataPath){
857 $newCounter = ((int) $counter) + 1;
858
859 while(file_exists($dataPath."$newCounter.json") === true){
860 $newCounter++;
861 }
862 return (string)$newCounter;
863 });
864 }
865
866
867 /**
868 * @param string|int $id
869 * @return int
870 * @throws InvalidArgumentException
871 */
872 private function checkAndStripId($id): int
873 {
874 if(!is_string($id) && !is_int($id)){
875
876 throw new InvalidArgumentException("The id of the document has to be an integer or string");
877 }
878
879 if(is_string($id)){
880 $id = IoHelper::secureStringForFileAccess($id);
881 }
882
883 if(!is_numeric($id)){
884 throw new InvalidArgumentException("The id of the document has to be numeric");
885 }
886
887 return (int) $id;
888 }
889
890 /**
891 * @return string
892 */
893 private function getDataPath(): string
894 {
895 return $this->getStorePath() . self::dataDirectory;
896 }
897
898 }
899