vendor/rakibtg/sleekdb/src/Store.php line 748

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