├── .gitignore ├── CHANGELOG.md ├── src ├── Contracts │ ├── Factory.php │ ├── Store.php │ └── Repository.php ├── helpers.php ├── OptionServiceProvider.php ├── Converter.php ├── FileStore.php ├── DatabaseStore.php ├── CastValues.php ├── OptionManager.php └── Repository.php ├── database └── migrations │ └── 2019_02_14_000000_create_options_table.php ├── composer.json ├── LICENSE ├── LICENSE.md ├── config └── option.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /node_modules 4 | package-lock.json 5 | composer.phar 6 | composer.lock 7 | phpunit.xml 8 | .phpunit.result.cache 9 | .DS_Store 10 | Thumbs.db -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | version 0.1.1 (2019-12-07) 2 | ----------------------------- 3 | ### FIXED 4 | - Fixed some bugs and documentations. 5 | 6 | version 0.1.0 (2019-12-07) 7 | ----------------------------- 8 | ### INITED -------------------------------------------------------------------------------- /src/Contracts/Factory.php: -------------------------------------------------------------------------------- 1 | get($key, $default); 14 | } 15 | 16 | return app('armincms.option')->store(); 17 | } 18 | } 19 | 20 | if (! function_exists('option_exists')) { 21 | /** 22 | * Check existance of option. 23 | * 24 | * @param string $key 25 | * @return boolean 26 | */ 27 | function option_exists(string $key) 28 | { 29 | return option()->has($key); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2019_02_14_000000_create_options_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('key')->unique(); 18 | $table->longText('value')->nullable(); 19 | $table->string('tag')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('options'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "armincms/option", 3 | "description": "The armincms option package.", 4 | "license": "MIT", 5 | "homepage": "https://armincms.com", 6 | "support": { 7 | }, 8 | "authors": [ 9 | { 10 | "name": "Ismail Zare", 11 | "email": "zarehesmail@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Armincms\\Option\\": "src" 19 | }, 20 | "files" : [ 21 | "src/helpers.php" 22 | ] 23 | }, 24 | "extra": { 25 | "laravel" : { 26 | "providers" : [ 27 | "Armincms\\Option\\OptionServiceProvider" 28 | ], 29 | "aliases" : { 30 | } 31 | } 32 | }, 33 | "suggest": { 34 | }, 35 | "config": { 36 | "sort-packages": true 37 | }, 38 | "minimum-stability": "dev" 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 armincms 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 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 armincms 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 | -------------------------------------------------------------------------------- /src/Contracts/Store.php: -------------------------------------------------------------------------------- 1 | env('OPTION_DRIVER', 'file'), 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Option Stores 25 | |-------------------------------------------------------------------------- 26 | | 27 | | Here you may define all of the option "stores" for your application as 28 | | well as their drivers. You may even define multiple stores for the 29 | | same option driver to group types of items stored in your options. 30 | | 31 | */ 32 | 33 | 'stores' => [ 34 | 'database' => [ 35 | 'driver' => 'database', 36 | 'table' => 'options', 37 | 'connection' => null, 38 | ], 39 | 40 | 'file' => [ 41 | 'driver' => 'file', 42 | 'path' => storage_path('framework/option'), 43 | 'single_file' => true, 44 | ], 45 | ], 46 | ]; 47 | -------------------------------------------------------------------------------- /src/OptionServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('armincms.option', function ($app) { 18 | return new OptionManager($app); 19 | }); 20 | 21 | $this->app->singleton('armincms.option.store', function ($app) { 22 | return $app['armincms.option']->driver(); 23 | }); 24 | 25 | $this->registerPublishing(); 26 | } 27 | 28 | /** 29 | * Register the package's publishable resources. 30 | * 31 | * @return void 32 | */ 33 | protected function registerPublishing() 34 | { 35 | $this->publishes([ 36 | __DIR__.'/../database/migrations' => database_path('migrations') 37 | ], ['armincms.option', 'armincms.option.migrations']); 38 | 39 | $this->publishes([ 40 | __DIR__.'/../config/option.php' => config_path('option.php') 41 | ], ['armincms.option', 'armincms.option.config']); 42 | } 43 | 44 | /** 45 | * Get the services provided by the provider. 46 | * 47 | * @return array 48 | */ 49 | public function provides() 50 | { 51 | return [ 52 | 'armincms.option', 'armincms.option.store' 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Contracts/Repository.php: -------------------------------------------------------------------------------- 1 | toArray()); 36 | } elseif ($value instanceof Jsonable) { 37 | return 'object'; 38 | $value = $value->toJson(); 39 | } elseif ($value instanceof JsonSerializable) { 40 | return 'object'; 41 | $value = $value->jsonSerialize(); 42 | } elseif (empty($value)) { 43 | return 'null'; 44 | $value = null; 45 | } 46 | 47 | return 'original'; 48 | } 49 | 50 | /** 51 | * Retrive stored option. 52 | * 53 | * @param string $key 54 | * @return \Armincms\OptionItem 55 | */ 56 | public function serialize($value) : array 57 | { 58 | switch ($type) { 59 | case 'array': 60 | case 'object': 61 | return Collection::make(json_decode($data['value'], true)); 62 | break; 63 | case 'boolean': 64 | return (boolean) $value; 65 | break; 66 | case 'integer': 67 | return (int) $value; 68 | break; 69 | case 'float': 70 | return floatval($value); 71 | break; 72 | case 'double': 73 | return doubleval($value); 74 | break; 75 | case 'null': 76 | return null; 77 | break; 78 | case 'datetime': 79 | return Carbon::parse($value); 80 | break; 81 | 82 | default: 83 | return (string) $value; 84 | break; 85 | } 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Option 2 | A key-value storage for laravel 3 | 4 | ##### Table of Contents 5 | 6 | * [Introduction](#introduction) 7 | * [Installation](#installation) 8 | * [Configuration](#configuration) 9 | * [Single Storing](#single-storing) 10 | * [Mass Storing](#mass-storing) 11 | * [Grouped Data](#grouped-data) 12 | * [Retrieving](#retrieving) 13 | * [Retrieving From Other Storage](#retrieving-from-other-storage) 14 | * [Check For Existence](#check-for-existence) 15 | 16 | 17 | 18 | ## Introduction 19 | Armincms Option is a package for store `key-value`. with this package, you can store values by `key` and `tag` in the simplest way into multiple storages. 20 | 21 | ## Installation 22 | 23 | To get started with Armincms Option, first run: 24 | 25 | ``` 26 | composer require armincms/option 27 | ``` 28 | 29 | Then publish configuration: 30 | 31 | ``` 32 | php artisan vendor:publish --tag=armincms.option 33 | ``` 34 | 35 | This command publishes `config` and `migration` file into the appropriate path. 36 | 37 | ## Configuration 38 | 39 | This package supports `file` and `database` storage for storing data. 40 | The default storage is `file`. for change the storage type you have two way: 41 | 42 | * With `.env` file: `OPTION_DRIVER=database` 43 | * With `Config` respository: `Config::set('option.default', 'file')` 44 | 45 | **Attention 1:** 46 | *if you want use database storage you should run `php artisan migrate` in console.* 47 | 48 | **Attention 2:** 49 | *For access to `option-manager` by laravel container you can use `app('armincms.option')`* 50 | 51 | **Attention 3:** 52 | *For simple coding: you can use helper `option()` insteadof `app('armincms.option')`* 53 | 54 | 55 | ### Single Storing 56 | 57 | There exists two way for storing single data: 58 | 59 | * first: 60 | `option()->put(key, value)` 61 | 62 | * second: 63 | `option()->key = value` 64 | 65 | 66 | 67 | ### Mass Storing 68 | 69 | For mass storing data use the followings method: 70 | 71 | option()->putMany([ 72 | key1 => value1, 73 | key2 => value2, 74 | ]) 75 | 76 | 77 | 78 | ### Grouped Data 79 | 80 | For grouping many option, can pass `tag` parameter when storing a data: 81 | 82 | app('armincms.option')->put(key, value, tag) 83 | 84 | Also; it's possible to attach a tag into data when mass storing: 85 | 86 | app('armincms.option')->putMany([ 87 | key1 => value1, 88 | key2 => value2, 89 | ], tag) 90 | 91 | 92 | 93 | ### Retrieving 94 | 95 | There exist many ways to retrieve your data.you can retrieve your data, `single` or `multiple`. 96 | 97 | *single retrieving:* 98 | 99 | To retrieve an option, you can use `option()->key`. but if you need `default` value for missed values; you can use `option(key, default)` or `option()->get(key, default)` . 100 | 101 | *multiple retrieving:* 102 | 103 | Also, retrieving multiple options is not difficult. you can retrieve many values by its keys with the `many` method; like `option()->many(keys)`. 104 | If you need default value for missed values; you can pass an associative array of `keys` and `default values`; like following: 105 | 106 | ``` 107 | option()->many([ 108 | key1 => key1-default, 109 | key2 => key2-default, 110 | key3, 111 | key4, 112 | key5 => key5-default 113 | ]) 114 | ``` 115 | 116 | And you can retrieve `tagged` values with `option()->tag(tag)`. 117 | 118 | 119 | 120 | ### Retrieving From Other Storage 121 | 122 | For store an option into `none default` driver with assumption that default 123 | driver is `database`; follow this: 124 | 125 | app('armincms.option')->store('file')->put(key, value, tag) 126 | app('armincms.option')->store('file')->many([key1, key2], tag) 127 | 128 | For retrieve you can use this: 129 | 130 | app('armincms.option')->store('file')->get(key, default) 131 | app('armincms.option')->store('file')->tag(tag) 132 | app('armincms.option')->store('file')->many(keys) 133 | 134 | 135 | ### Check For Existence 136 | If you want to check for existance of an option ; you can use helper `option_exists(key)` or `option()->has(key)`. 137 | 138 | -------------------------------------------------------------------------------- /src/FileStore.php: -------------------------------------------------------------------------------- 1 | files = $files; 36 | $this->directory = $directory; 37 | } 38 | 39 | /** 40 | * Retrieve all stored items. 41 | * 42 | * @return array 43 | */ 44 | public function all() : array 45 | { 46 | return $this->getPayloads()->map->value->toArray(); 47 | } 48 | 49 | /** 50 | * Retrieve multiple items from the storage by key. 51 | * 52 | * Items not found in the storage will have a null value. 53 | * 54 | * @param array $keys 55 | * @return array 56 | */ 57 | public function many(array $keys) : array 58 | { 59 | return $this->getPayloads()->only($keys)->map->value->toArray(); 60 | } 61 | 62 | /** 63 | * Retrieve multiple items from the storage by tag. 64 | * 65 | * Items not found in the storage will have a null value. 66 | * 67 | * @param string $tag 68 | * @return array 69 | */ 70 | public function tags(string $tag) : array 71 | { 72 | return $this->getPayloads()->where('tag', $tag)->map->value->toArray(); 73 | } 74 | 75 | /** 76 | * Retrieve an item from the storage by key. 77 | * 78 | * @param string $key 79 | * @return mixed 80 | */ 81 | public function get(string $key) 82 | { 83 | return $this->getPayload($key)['value'] ?? null; 84 | } 85 | 86 | /** 87 | * Store a data in the storage. 88 | * 89 | * @param string $key 90 | * @param mixed $value 91 | * @param string $tag 92 | * @return bool 93 | */ 94 | public function put(string $key, $value, string $tag = null) : bool 95 | { 96 | $this->ensureOptionDirectoryExists($path = $this->path($key)); 97 | 98 | $merged = $this->getPayloads()->put($key, compact('tag', 'value', 'key'))->toArray(); 99 | 100 | return false !== $this->files->put($path, serialize($merged), true); 101 | } 102 | 103 | /** 104 | * Remove an item from the option. 105 | * 106 | * @param string $key 107 | * @return bool 108 | */ 109 | public function delete(string $key) 110 | { 111 | if($data = $this->getPayloads()) { 112 | unset($data[$key]); 113 | 114 | return false !== $this->files->put($this->path(), serialize($data), true); 115 | } 116 | 117 | return true; 118 | } 119 | 120 | /** 121 | * Create the file option directory if necessary. 122 | * 123 | * @param string $path 124 | * @return void 125 | */ 126 | protected function ensureOptionDirectoryExists($path) 127 | { 128 | if (! $this->files->exists(dirname($path))) { 129 | $this->files->makeDirectory(dirname($path), 0777, true, true); 130 | } 131 | } 132 | 133 | /** 134 | * Retrieve an item from the option by key. 135 | * 136 | * @param string $key 137 | * @return array 138 | */ 139 | protected function getPayload($key) 140 | { 141 | return $this->getPayloads()->get($key); 142 | } 143 | 144 | /** 145 | * Retrieve stored items. 146 | * 147 | * @param string $key 148 | * @return \Illuminate\Support\Collection 149 | */ 150 | protected function getPayloads() : Collection 151 | { 152 | // If the file doesn't exist, we obviously cannot return the option so we will 153 | // just return null 154 | try { 155 | $contents = $this->files->get($this->path(), true); 156 | } catch (Exception $e) { 157 | return $this->emptyPayload(); 158 | } 159 | 160 | 161 | try { 162 | return Collection::make(unserialize($contents)); 163 | } catch (Exception $e) { 164 | return $this->emptyPayload(); 165 | } 166 | } 167 | 168 | /** 169 | * Get a default empty payload for the option. 170 | * 171 | * @return \Illuminate\Support\Collection 172 | */ 173 | protected function emptyPayload() : Collection 174 | { 175 | return Collection::make([]); 176 | } 177 | 178 | /** 179 | * Get the full path for the given option key. 180 | * 181 | * @param string $key 182 | * @return string 183 | */ 184 | protected function path(string $key = null) 185 | { 186 | return "{$this->directory}/options";//. ($key ? "{$key}" : 'option'); 187 | } 188 | 189 | /** 190 | * Get the Filesystem instance. 191 | * 192 | * @return \Illuminate\Filesystem\Filesystem 193 | */ 194 | public function getFilesystem() 195 | { 196 | return $this->files; 197 | } 198 | 199 | /** 200 | * Get the working directory of the option. 201 | * 202 | * @return string 203 | */ 204 | public function getDirectory() 205 | { 206 | return $this->directory; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/DatabaseStore.php: -------------------------------------------------------------------------------- 1 | table = $table; 39 | $this->connection = $connection; 40 | } 41 | 42 | /** 43 | * Retrieve all stored items. 44 | * 45 | * @return array 46 | */ 47 | public function all() : array 48 | { 49 | return $this->handleManyResult( $this->table()->get() ); 50 | } 51 | 52 | /** 53 | * Retrieve multiple items from the storage by key. 54 | * 55 | * Items not found in the storage will have a null value. 56 | * 57 | * @param array $keys 58 | * @return array 59 | */ 60 | public function many(array $keys) : array 61 | { 62 | return $this->handleManyResult( $this->table()->whereIn('key', $keys)->get() ); 63 | } 64 | 65 | /** 66 | * Retrieve multiple items from the storage by tag. 67 | * 68 | * Items not found in the storage will have a null value. 69 | * 70 | * @param string $tag 71 | * @return array 72 | */ 73 | public function tags(string $tag) : array 74 | { 75 | return $this->handleManyResult( $this->table()->where('tag', '=', $tag)->get() ); 76 | } 77 | 78 | /** 79 | * Retrieve an item from the storage by key. 80 | * 81 | * @param string $key 82 | * @return mixed 83 | */ 84 | public function get(string $key) 85 | { 86 | $option = $this->table()->where('key', '=', $key)->first(); 87 | $option = is_array($option)? (object) $option : $option; 88 | 89 | return is_null($option) ? null : $this->unserialize($option->value); 90 | } 91 | 92 | /** 93 | * Store a data in the storage. 94 | * 95 | * @param string $key 96 | * @param mixed $value 97 | * @param string $tag 98 | * @return bool 99 | */ 100 | public function put(string $key, $value, string $tag = null) : bool 101 | { 102 | if($this->has($key)) { 103 | return $this->update($key, $value, $tag); 104 | } 105 | 106 | try { 107 | return $this->table()->insert([ 108 | 'key' => $key, 109 | 'value' => $this->serialize($value), 110 | 'tag' => $tag 111 | ]); 112 | } catch (Exception $e) { 113 | return false; 114 | } 115 | } 116 | 117 | /** 118 | * Update the existence option value. 119 | * 120 | * @param string $key 121 | * @param mixed $value 122 | * @param string $tag 123 | * @return bool 124 | */ 125 | public function update(string $key, $value, string $tag = null) : bool 126 | { 127 | try { 128 | return $this->table()->where('key', '=', $key)->update([ 129 | 'value' => $this->serialize($value), 130 | 'tag' => $tag 131 | ]) >= 0; 132 | } catch (Exception $e) { 133 | return false; 134 | } 135 | } 136 | 137 | /** 138 | * Indicate that the option exists. 139 | * 140 | * @param string $key 141 | * @return bool 142 | */ 143 | public function has(string $key) : bool 144 | { 145 | return $this->table()->where('key', '=', $key)->count() > 0; 146 | } 147 | 148 | /** 149 | * Remove an item from the option. 150 | * 151 | * @param string $key 152 | * @return bool 153 | */ 154 | public function delete(string $key) 155 | { 156 | return $this->table()->where('key', '=', $key)->delete(); 157 | } 158 | 159 | /** 160 | * Get a query builder for the option table. 161 | * 162 | * @return \Illuminate\Database\Query\Builder 163 | */ 164 | protected function table() 165 | { 166 | return $this->connection->table($this->table); 167 | } 168 | 169 | /** 170 | * Get the underlying database connection. 171 | * 172 | * @return \Illuminate\Database\ConnectionInterface 173 | */ 174 | public function getConnection() 175 | { 176 | return $this->connection; 177 | } 178 | 179 | /** 180 | * Serialize the given value. 181 | * 182 | * @param mixed $value 183 | * @return string 184 | */ 185 | protected function serialize($value) 186 | { 187 | $result = serialize($value); 188 | 189 | if ($this->connection instanceof PostgresConnection && Str::contains($result, "\0")) { 190 | $result = base64_encode($result); 191 | } 192 | 193 | return $result; 194 | } 195 | 196 | /** 197 | * Unserialize the given value. 198 | * 199 | * @param string $value 200 | * @return mixed 201 | */ 202 | protected function unserialize($value) 203 | { 204 | if ($this->connection instanceof PostgresConnection && ! Str::contains($value, [':', ';'])) { 205 | $value = base64_decode($value); 206 | } 207 | 208 | return unserialize($value); 209 | } 210 | 211 | /** 212 | * Handle a result for the "all" and "tag" method. 213 | * 214 | * @param \Illuminate\Support\Collection $results 215 | * @return array 216 | */ 217 | protected function handleManyResult($results) : array 218 | { 219 | return $results->pluck('value', 'key')->map(function($value) { 220 | return $this->unserialize($value); 221 | })->toArray(); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/CastValues.php: -------------------------------------------------------------------------------- 1 | castValue($data['type'], $data['value']); 21 | } 22 | 23 | /** 24 | * Cast an value to a native PHP type. 25 | * 26 | * @param string $type 27 | * @param mixed $value 28 | * @return mixed 29 | */ 30 | public function castValue(string $type, $value) 31 | { 32 | if (is_null($value)) { 33 | return $value; 34 | } 35 | 36 | switch ($type) { 37 | case 'int': 38 | case 'integer': 39 | return (int) $value; 40 | case 'real': 41 | case 'float': 42 | case 'double': 43 | return $this->fromFloat($value); 44 | case 'string': 45 | return (string) $value; 46 | case 'bool': 47 | case 'boolean': 48 | return (bool) $value; 49 | case 'object': 50 | return $this->fromJson($value, true); 51 | case 'array': 52 | case 'json': 53 | case 'collection': 54 | return new Collection($this->fromJson($value)); 55 | case 'date': 56 | return $this->asDate($value); 57 | case 'datetime': 58 | return $this->asDateTime($value); 59 | case 'timestamp': 60 | return $this->asTimestamp($value); 61 | default: 62 | return $value; 63 | } 64 | } 65 | 66 | 67 | /** 68 | * Decode the given float. 69 | * 70 | * @param mixed $value 71 | * @return mixed 72 | */ 73 | public function fromFloat($value) 74 | { 75 | switch ((string) $value) { 76 | case 'Infinity': 77 | return INF; 78 | case '-Infinity': 79 | return -INF; 80 | case 'NaN': 81 | return NAN; 82 | default: 83 | return (float) $value; 84 | } 85 | } 86 | 87 | /** 88 | * Return a timestamp as DateTime object with time set to 00:00:00. 89 | * 90 | * @param mixed $value 91 | * @return \Illuminate\Support\Carbon 92 | */ 93 | protected function asDate($value) 94 | { 95 | return $this->asDateTime($value)->startOfDay(); 96 | } 97 | 98 | /** 99 | * Return a timestamp as DateTime object. 100 | * 101 | * @param mixed $value 102 | * @return \Illuminate\Support\Carbon 103 | */ 104 | protected function asDateTime($value) 105 | { 106 | // If this value is already a Carbon instance, we shall just return it as is. 107 | // This prevents us having to re-instantiate a Carbon instance when we know 108 | // it already is one, which wouldn't be fulfilled by the DateTime check. 109 | if ($value instanceof Carbon || $value instanceof CarbonInterface) { 110 | return Date::instance($value); 111 | } 112 | 113 | // If the value is already a DateTime instance, we will just skip the rest of 114 | // these checks since they will be a waste of time, and hinder performance 115 | // when checking the field. We will just return the DateTime right away. 116 | if ($value instanceof DateTimeInterface) { 117 | return Date::parse( 118 | $value->format('Y-m-d H:i:s.u'), $value->getTimezone() 119 | ); 120 | } 121 | 122 | // If this value is an integer, we will assume it is a UNIX timestamp's value 123 | // and format a Carbon object from this timestamp. This allows flexibility 124 | // when defining your date fields as they might be UNIX timestamps here. 125 | if (is_numeric($value)) { 126 | return Date::createFromTimestamp($value); 127 | } 128 | 129 | // If the value is in simply year, month, day format, we will instantiate the 130 | // Carbon instances from that format. Again, this provides for simple date 131 | // fields on the database, while still supporting Carbonized conversion. 132 | if ($this->isStandardDateFormat($value)) { 133 | return Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay()); 134 | } 135 | 136 | $format = $this->getDateFormat(); 137 | 138 | // https://bugs.php.net/bug.php?id=75577 139 | if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { 140 | $format = str_replace('.v', '.u', $format); 141 | } 142 | 143 | // Finally, we will just assume this date is in the format used by default on 144 | // the database connection and use that format to create the Carbon object 145 | // that is returned back out to the developers after we convert it here. 146 | return Date::createFromFormat($format, $value); 147 | } 148 | 149 | /** 150 | * Determine if the given value is a standard date format. 151 | * 152 | * @param string $value 153 | * @return bool 154 | */ 155 | protected function isStandardDateFormat($value) 156 | { 157 | return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value); 158 | } 159 | 160 | /** 161 | * Convert a DateTime to a storable string. 162 | * 163 | * @param mixed $value 164 | * @return string|null 165 | */ 166 | public function fromDateTime($value) 167 | { 168 | return empty($value) ? $value : $this->asDateTime($value)->format( 169 | $this->getDateFormat() 170 | ); 171 | } 172 | 173 | /** 174 | * Return a timestamp as unix timestamp. 175 | * 176 | * @param mixed $value 177 | * @return int 178 | */ 179 | protected function asTimestamp($value) 180 | { 181 | return $this->asDateTime($value)->getTimestamp(); 182 | } 183 | 184 | 185 | /** 186 | * Decode the given JSON back into an array or object. 187 | * 188 | * @param string $value 189 | * @param bool $asObject 190 | * @return mixed 191 | */ 192 | public function fromJson($value, $asObject = false) 193 | { 194 | return json_decode($value, ! $asObject); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/OptionManager.php: -------------------------------------------------------------------------------- 1 | app = $app; 45 | } 46 | 47 | /** 48 | * Get an option store instance by name, wrapped in a repository. 49 | * 50 | * @param string|null $name 51 | * @return \Armincms\Option\Contracts\Repository 52 | */ 53 | public function store($name = null) 54 | { 55 | $name = $name ?: $this->getDefaultDriver(); 56 | 57 | return $this->stores[$name] = $this->get($name); 58 | } 59 | 60 | /** 61 | * Get an option driver instance. 62 | * 63 | * @param string|null $driver 64 | * @return \Armincms\Option\Contracts\Repository 65 | */ 66 | public function driver($driver = null) 67 | { 68 | return $this->store($driver); 69 | } 70 | 71 | /** 72 | * Attempt to get the store from the local option. 73 | * 74 | * @param string $name 75 | * @return \Armincms\Option\Contracts\Repository 76 | */ 77 | protected function get($name) 78 | { 79 | return $this->stores[$name] ?? $this->resolve($name); 80 | } 81 | 82 | /** 83 | * Resolve the given store. 84 | * 85 | * @param string $name 86 | * @return \Armincms\Option\Contracts\Repository 87 | * 88 | * @throws \InvalidArgumentException 89 | */ 90 | protected function resolve($name) 91 | { 92 | $config = $this->getConfig($name); 93 | 94 | if (is_null($config)) { 95 | throw new InvalidArgumentException("Option store [{$name}] is not defined."); 96 | } 97 | 98 | if (isset($this->customCreators[$config['driver']])) { 99 | return $this->callCustomCreator($config); 100 | } else { 101 | $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; 102 | 103 | if (method_exists($this, $driverMethod)) { 104 | return $this->{$driverMethod}($config); 105 | } else { 106 | throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported."); 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * Call a custom driver creator. 113 | * 114 | * @param array $config 115 | * @return mixed 116 | */ 117 | protected function callCustomCreator(array $config) 118 | { 119 | return $this->customCreators[$config['driver']]($this->app, $config); 120 | } 121 | 122 | /** 123 | * Create an instance of the file option driver. 124 | * 125 | * @param array $config 126 | * @return \Armincms\Option\Repository 127 | */ 128 | protected function createFileDriver(array $config) 129 | { 130 | return $this->repository(new FileStore($this->app['files'], $config['path'])); 131 | } 132 | 133 | /** 134 | * Create an instance of the Null option driver. 135 | * 136 | * @return \Armincms\Option\Repository 137 | */ 138 | protected function createNullDriver() 139 | { 140 | return $this->repository(new NullStore); 141 | } 142 | 143 | /** 144 | * Create an instance of the database option driver. 145 | * 146 | * @param array $config 147 | * @return \Armincms\Option\Repository 148 | */ 149 | protected function createDatabaseDriver(array $config) 150 | { 151 | $connection = $this->app['db']->connection($config['connection'] ?? null); 152 | 153 | return $this->repository( 154 | new DatabaseStore($connection, $config['table']) 155 | ); 156 | } 157 | 158 | /** 159 | * Create a new option repository with the given implementation. 160 | * 161 | * @param \Armincms\Option\Contracts\Store $store 162 | * @return \Armincms\Option\Repository 163 | */ 164 | public function repository(Store $store) 165 | { 166 | return new Repository($store); 167 | } 168 | 169 | /** 170 | * Get the option connection configuration. 171 | * 172 | * @param string $name 173 | * @return array 174 | */ 175 | protected function getConfig($name) 176 | { 177 | return $this->app['config']["option.stores.{$name}"]; 178 | } 179 | 180 | /** 181 | * Get the default option driver name. 182 | * 183 | * @return string 184 | */ 185 | public function getDefaultDriver() 186 | { 187 | return $this->app['config']['option.default']; 188 | } 189 | 190 | /** 191 | * Set the default option driver name. 192 | * 193 | * @param string $name 194 | * @return void 195 | */ 196 | public function setDefaultDriver($name) 197 | { 198 | $this->app['config']['option.default'] = $name; 199 | } 200 | 201 | /** 202 | * Unset the given driver instances. 203 | * 204 | * @param array|string|null $name 205 | * @return $this 206 | */ 207 | public function forgetDriver($name = null) 208 | { 209 | $name = $name ?? $this->getDefaultDriver(); 210 | 211 | foreach ((array) $name as $optionName) { 212 | if (isset($this->stores[$optionName])) { 213 | unset($this->stores[$optionName]); 214 | } 215 | } 216 | 217 | return $this; 218 | } 219 | 220 | /** 221 | * Register a custom driver creator Closure. 222 | * 223 | * @param string $driver 224 | * @param \Closure $callback 225 | * @return $this 226 | */ 227 | public function extend($driver, Closure $callback) 228 | { 229 | $this->customCreators[$driver] = $callback->bindTo($this, $this); 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * Dynamically call the default driver instance. 236 | * 237 | * @param string $method 238 | * @param array $parameters 239 | * @return mixed 240 | */ 241 | public function __call($method, $parameters) 242 | { 243 | return $this->store()->$method(...$parameters); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/Repository.php: -------------------------------------------------------------------------------- 1 | store = $store; 37 | } 38 | 39 | /** 40 | * Determine if an option exists in the option. 41 | * 42 | * @param string $key 43 | * @return bool 44 | */ 45 | public function has($key) 46 | { 47 | return ! is_null($this->get($key)); 48 | } 49 | 50 | /** 51 | * Retrieve items from the storage by the tag name. 52 | * 53 | * @param string $tag 54 | * @return array 55 | */ 56 | public function tag(string $tag) : array 57 | { 58 | return $this->store->tags($tag); 59 | } 60 | 61 | /** 62 | * Retrieve an option from the storage. 63 | * 64 | * @param string $key 65 | * @param mixed $default 66 | * @return mixed 67 | */ 68 | public function get(string $key, $default = null) 69 | { 70 | $value = $this->store->get($this->optionKey($key)); 71 | 72 | return is_null($value) ? value($default) : $value; 73 | } 74 | 75 | /** 76 | * Retrieve multiple options from the storage by key. 77 | * 78 | * Items not found in the option will have a null value. 79 | * 80 | * @param array $keys 81 | * @return array 82 | */ 83 | public function many(array $keys) 84 | { 85 | $values = $this->store->many(collect($keys)->map(function ($value, $key) { 86 | return is_string($key) ? $key : $value; 87 | })->values()->all()); 88 | 89 | return collect($values)->map(function ($value, $key) use ($keys) { 90 | return $this->handleManyResult($keys, $key, $value); 91 | })->all(); 92 | } 93 | 94 | /** 95 | * Handle a result for the "many" method. 96 | * 97 | * @param array $keys 98 | * @param string $key 99 | * @param mixed $value 100 | * @return mixed 101 | */ 102 | protected function handleManyResult($keys, $key, $value) 103 | { 104 | // If we could not find the option value, we will get the default value 105 | // for this option value. This default could be a callback 106 | // so we will execute the value function which will resolve it if needed. 107 | if (is_null($value)) { 108 | return isset($keys[$key]) ? value($keys[$key]) : null; 109 | } 110 | 111 | return $value; 112 | } 113 | 114 | /** 115 | * Store an option. 116 | * 117 | * @param string $key 118 | * @param mixed $value 119 | * @param string $tag 120 | * @return bool 121 | */ 122 | public function put(string $key, $value, string $tag = null) : bool 123 | { 124 | return $this->store->put($this->optionKey($key), $value, $tag); 125 | } 126 | 127 | /** 128 | * Store multiple options into storage. 129 | * 130 | * @param array $values 131 | * @param int $seconds 132 | * @return bool 133 | */ 134 | public function putMany(array $values, string $tag = null): bool 135 | { 136 | return Collection::make($values)->filter(function($value, $key) use ($tag) { 137 | return $this->put($key, $value, $tag); 138 | })->count() === count($values); 139 | } 140 | 141 | /** 142 | * Retrieve an option from the storage and delete it. 143 | * 144 | * @param string $key 145 | * @param mixed $default 146 | * @return mixed 147 | */ 148 | public function pull(string $key, $default = null) 149 | { 150 | return tap($this->get($key, $default), function () use ($key) { 151 | $this->delete($key); 152 | }); 153 | } 154 | 155 | /** 156 | * Retrieve options from storage by tag name and delete them. 157 | * 158 | * @param string $tag 159 | * @return array 160 | */ 161 | public function pullTag(string $tag, array $defaults = []) : array 162 | { 163 | $data = $this->store->tags($tag); 164 | 165 | return Collection::make($data)->map(function($value, $key) use ($defaults) { 166 | return $this->pull($key, $defaults[$key] ?? null); 167 | })->toArray(); 168 | } 169 | 170 | /** 171 | * Remove an option from the storage. 172 | * 173 | * @param string $key 174 | * @return bool 175 | */ 176 | public function delete($key) 177 | { 178 | return $this->store->delete($this->optionKey($key)); 179 | } 180 | 181 | /** 182 | * Format the option key for storage. 183 | * 184 | * @param string $key 185 | * @return string 186 | */ 187 | protected function optionKey($key) 188 | { 189 | return $key; 190 | } 191 | 192 | /** 193 | * Get the option store implementation. 194 | * 195 | * @return \Armincms\Option\Contracts\Store 196 | */ 197 | public function getStore() 198 | { 199 | return $this->store; 200 | } 201 | 202 | /** 203 | * Determine if a option exists. 204 | * 205 | * @param string $key 206 | * @return bool 207 | */ 208 | public function offsetExists($key) 209 | { 210 | return $this->has($key); 211 | } 212 | 213 | /** 214 | * Retrieve an option from the storage by key. 215 | * 216 | * @param string $key 217 | * @return mixed 218 | */ 219 | public function offsetGet($key) 220 | { 221 | return $this->get($key); 222 | } 223 | 224 | /** 225 | * Store an option in the storge without tag. 226 | * 227 | * @param string $key 228 | * @param mixed $value 229 | * @return void 230 | */ 231 | public function offsetSet($key, $value) 232 | { 233 | $this->put($key, $value, $this->default); 234 | } 235 | 236 | /** 237 | * Remove an option from the storage. 238 | * 239 | * @param string $key 240 | * @return void 241 | */ 242 | public function offsetUnset($key) 243 | { 244 | $this->delete($key); 245 | } 246 | 247 | /** 248 | * Handle dynamic calls into macros or pass missing methods to the store. 249 | * 250 | * @param string $method 251 | * @param array $parameters 252 | * @return mixed 253 | */ 254 | public function __call($method, $parameters) 255 | { 256 | if (static::hasMacro($method)) { 257 | return $this->macroCall($method, $parameters); 258 | } 259 | 260 | return $this->store->$method(...$parameters); 261 | } 262 | 263 | /** 264 | * Dynamically get option. 265 | * 266 | * @param string $key 267 | */ 268 | public function __get($key) 269 | { 270 | return $this->offsetGet($key); 271 | } 272 | 273 | /** 274 | * Dynamically set the value of an attribute. 275 | * 276 | * @param string $key 277 | * @param mixed $value 278 | * @return void 279 | */ 280 | public function __set($key, $value) 281 | { 282 | $this->offsetSet($key, $value); 283 | } 284 | 285 | /** 286 | * Dynamically check if an attribute is set. 287 | * 288 | * @param string $key 289 | * @return bool 290 | */ 291 | public function __isset($key) 292 | { 293 | return $this->offsetExists($key); 294 | } 295 | 296 | /** 297 | * Dynamically unset an attribute. 298 | * 299 | * @param string $key 300 | * @return void 301 | */ 302 | public function __unset($key) 303 | { 304 | $this->offsetUnset($key); 305 | } 306 | } 307 | --------------------------------------------------------------------------------