├── tests └── .gitkeep ├── .gitignore ├── TODO.md ├── .travis.yml ├── views ├── base │ └── bootstrap.blade.php └── inputs │ ├── string.blade.php │ ├── textarea.blade.php │ ├── radios.blade.php │ └── checkboxes.blade.php ├── src ├── Exceptions │ ├── ResourceKeyNotSpecifiedExceptionException.php │ ├── InvalidCacheDriverException.php │ ├── ResourceDescriptorNotDefinedException.php │ ├── ResourceNotDefinedException.php │ ├── ResourceDescriptorChoicesNotDefinedException.php │ └── ResourceDescriptorNameNotDefinedException.php ├── Facades │ ├── Resource.php │ └── ResourceGroup.php ├── Descriptors │ ├── Text.php │ ├── String.php │ ├── ChooseOne.php │ └── ChooseMany.php ├── Contracts │ ├── StorageInterface.php │ └── DescriptorInterface.php ├── Traits │ ├── PlainStorage.php │ ├── SerializedStorage.php │ └── ChooseableDescriptor.php ├── Commands │ ├── stubs │ │ ├── CreateResourcesTable.php │ │ └── CreateResourceTranslationsTable.php │ ├── TableCommand.php │ └── ImportCommand.php ├── Models │ ├── ResourceTranslation.php │ └── Resource.php ├── ServiceProvider.php ├── Descriptor.php ├── ResourceGroup.php └── Resource.php ├── phpunit.xml ├── composer.json ├── config └── resources.php ├── LICENSE └── README.md /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /vendor/ 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | settings (id, namespace, name, value, description, group_id, is_translateable) 2 | setting_translations( id, setting_id, locale, description, value ) 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - composer self-update 11 | - composer install --prefer-source --no-interaction --dev 12 | 13 | script: phpunit 14 | -------------------------------------------------------------------------------- /views/base/bootstrap.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @yield('input.content') 4 | 5 | @if ($errors->has($id)) 6 | {{$errors->first($id)}} 7 | @endif 8 |
9 | -------------------------------------------------------------------------------- /src/Exceptions/ResourceKeyNotSpecifiedExceptionException.php: -------------------------------------------------------------------------------- 1 | {{ $name }} 5 | 6 | @overwrite 7 | -------------------------------------------------------------------------------- /views/inputs/textarea.blade.php: -------------------------------------------------------------------------------- 1 | @extends('resources::base.bootstrap') 2 | 3 | @section('input.content') 4 | 5 | 6 | @overwrite 7 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidCacheDriverException.php: -------------------------------------------------------------------------------- 1 | {{ $name }} 5 | @foreach( $choices as $_key => $_choice ) 6 |
7 | 15 |
16 | @endforeach 17 | @overwrite 18 | -------------------------------------------------------------------------------- /src/Descriptors/ChooseOne.php: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Traits/PlainStorage.php: -------------------------------------------------------------------------------- 1 | =5.4", 12 | "illuminate/redis": "~5", 13 | "illuminate/console": "~5", 14 | "illuminate/database": "~5", 15 | "illuminate/support": "~5" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "~4.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Cviebrock\\LaravelResources\\": "src" 23 | } 24 | }, 25 | "minimum-stability": "dev" 26 | } 27 | -------------------------------------------------------------------------------- /src/Traits/SerializedStorage.php: -------------------------------------------------------------------------------- 1 | {{ $name }} 5 | {{-- hidden element to force the input to be there, even if all the checkboxes are empty --}} 6 | 7 | @foreach( $choices as $_key => $_choice ) 8 |
9 | 17 |
18 | @endforeach 19 | @overwrite 20 | -------------------------------------------------------------------------------- /src/Exceptions/ResourceDescriptorNotDefinedException.php: -------------------------------------------------------------------------------- 1 | key; 20 | } 21 | 22 | 23 | /** 24 | * Set the key name. 25 | * 26 | * @param $key 27 | * @return $this 28 | */ 29 | 30 | public function setKey($key) { 31 | $this->key = $key; 32 | $this->message = "Resource descriptor not found for [{$key}]."; 33 | 34 | return $this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Commands/stubs/CreateResourcesTable.php: -------------------------------------------------------------------------------- 1 | increments('resource_id'); 17 | $table->string('resource_key'); 18 | $table->string('resource_class'); 19 | $table->timestamps(); 20 | $table->softDeletes(); 21 | 22 | $table->unique('resource_key'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('%PREFIX%resources'); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/Models/ResourceTranslation.php: -------------------------------------------------------------------------------- 1 | setTable($prefix . 'resource_translations'); 24 | 25 | parent::__construct($attributes); 26 | } 27 | 28 | 29 | /** 30 | * Relationship with Resource Model 31 | * 32 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 33 | */ 34 | public function resource() { 35 | return $this->belongsTo('Resource'); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /config/resources.php: -------------------------------------------------------------------------------- 1 | '', 20 | 21 | /** 22 | * The cache prefix used to store the settings. 23 | */ 24 | 'cachePrefix' => 'resources', 25 | 26 | /** 27 | * Here is where you define all the resources your application needs. 28 | * You can make it a nested array, or use dot-notation. 29 | * 30 | * The values of the array represent the resource descriptor classes 31 | * that are used 32 | */ 33 | 'resources' => [ 34 | 35 | // 'home.title' => 'My App' 36 | ] 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /src/Commands/stubs/CreateResourceTranslationsTable.php: -------------------------------------------------------------------------------- 1 | increments('resource_translation_id'); 17 | $table->unsignedInteger('resource_id'); 18 | $table->string('locale', 10); 19 | $table->text('value')->nullable()->default(null); 20 | $table->timestamps(); 21 | $table->softDeletes(); 22 | 23 | $table->index('locale'); 24 | $table->unique(['resource_id','locale']); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::dropIfExists('%PREFIX%resource_translations'); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/Exceptions/ResourceNotDefinedException.php: -------------------------------------------------------------------------------- 1 | key = $key; 29 | $this->locale = $locale; 30 | $this->message = "Resource not found for [{$locale}:{$key}]."; 31 | 32 | return $this; 33 | } 34 | 35 | 36 | /** 37 | * @return mixed 38 | */ 39 | public function getLocale() { 40 | return $this->locale; 41 | } 42 | 43 | 44 | /** 45 | * Get the key name 46 | * 47 | * @return string 48 | */ 49 | public function getKey() { 50 | return $this->key; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Exceptions/ResourceDescriptorChoicesNotDefinedException.php: -------------------------------------------------------------------------------- 1 | locale; 25 | } 26 | 27 | 28 | /** 29 | * Get the key name. 30 | * 31 | * @return string 32 | */ 33 | public function getKey() { 34 | return $this->key; 35 | } 36 | 37 | 38 | /** 39 | * Set the error reference. 40 | * 41 | * @param $key 42 | * @param $locale 43 | * @return $this 44 | */ 45 | public function setReference($key, $locale) { 46 | $this->key = $key; 47 | $this->locale = $locale; 48 | $this->message = "Resource choices not defined for [{$locale}:{$key}]."; 49 | 50 | return $this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Exceptions/ResourceDescriptorNameNotDefinedException.php: -------------------------------------------------------------------------------- 1 | key = $key; 29 | $this->descriptorClass = $descriptorClass; 30 | $this->message = "Resource descriptor [{$descriptorClass}] not found for [{$key}]."; 31 | 32 | return $this; 33 | } 34 | 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getKey() { 40 | return $this->key; 41 | } 42 | 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getDescriptorClass() { 48 | return $this->descriptorClass; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Traits/ChooseableDescriptor.php: -------------------------------------------------------------------------------- 1 | getChoices(); 20 | 21 | return $data; 22 | } 23 | 24 | 25 | /** 26 | * Return the list of choices available for the descriptor. 27 | * 28 | * @return array 29 | * @throws ResourceDescriptorChoicesNotDefinedException 30 | */ 31 | public function getChoices() { 32 | 33 | $locale = $this->getLocale(); 34 | $choices = array_get($this->choiceValues, $locale); 35 | 36 | if (!$choices) { 37 | throw (new ResourceDescriptorChoicesNotDefinedException)->setReference(get_called_class(), $locale); 38 | } 39 | 40 | return $choices; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Colin Viebrock 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-resources 2 | 3 | > Note: The `master` branch and `>=1.0` releases are for Laravel 5. 4 | > Use the `0.x` branch and `~0.9` releases for Laravel 4. 5 | 6 | 7 | 8 | ## Installation 9 | 10 | Include the package: 11 | 12 | ```sh 13 | composer require "cviebrock/laravel-resources:~0.9" 14 | ``` 15 | 16 | Add service provider and facades to `app/config.php`: 17 | 18 | ```php 19 | 'providers' => [ 20 | 'Cviebrock\LaravelResources\ServiceProvider', 21 | ], 22 | 'aliases' => [ 23 | 'Resource' => 'Cviebrock\LaravelResources\Facades\Resource', 24 | 'ResourceGroup' => 'Cviebrock\LaravelResources\Facades\ResourceGroup', 25 | ] 26 | ``` 27 | 28 | Publish the configuration: 29 | 30 | ```sh 31 | php artisan config:publish "cviebrock/laravel-resources" 32 | ``` 33 | 34 | Edit the configuration (if needed), then generate and run the migration: 35 | 36 | ```sh 37 | php artisan resources:table 38 | php artisan migrate 39 | ``` 40 | 41 | ## Configuration 42 | 43 | Update `app/config/packages/cviebrock/laravel-resources/resources.php` with the array of keys/descriptor classes you need. 44 | 45 | Then, run the initial import to load those values in to the database: 46 | 47 | ```sh 48 | php artisan resources:import 49 | ``` 50 | -------------------------------------------------------------------------------- /src/Models/Resource.php: -------------------------------------------------------------------------------- 1 | setTable($prefix . 'resources'); 26 | 27 | parent::__construct($attributes); 28 | } 29 | 30 | 31 | /** 32 | * Find the first model with the given value for "key" 33 | * 34 | * @param $key string 35 | * @return mixed 36 | */ 37 | public static function firstByKey($key) { 38 | return static::where('resource_key', $key)->first(); 39 | } 40 | 41 | 42 | /** 43 | * Relationship with ResourceTranslations models. 44 | * 45 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 46 | */ 47 | public function translations() { 48 | return $this->hasMany('Cviebrock\LaravelResources\Models\ResourceTranslation'); 49 | } 50 | 51 | 52 | public function findTranslation($locale) { 53 | return $this->translations->first(function ($idx, $item) use ($locale) { 54 | return $item->locale === $locale; 55 | }); 56 | } 57 | } 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/Contracts/DescriptorInterface.php: -------------------------------------------------------------------------------- 1 | info('Creating package migrations ...'); 39 | foreach ($this->stubs as $stub) { 40 | $fullPath = $this->createMigration($stub); 41 | file_put_contents($fullPath, $this->getMigrationStub($stub)); 42 | $this->comment(basename($fullPath)); 43 | } 44 | $this->info('Migrations created successfully!'); 45 | $this->call('dump-autoload'); 46 | $this->info('Don\'t forget to run "artisan migrate".'); 47 | } 48 | 49 | 50 | /** 51 | * Create a base migration file for the settings table. 52 | * 53 | * @param string $stub 54 | * @return string 55 | */ 56 | protected function createMigration($stub) { 57 | $path = $this->laravel['path'] . '/database/migrations'; 58 | 59 | return $this->laravel['migration.creator']->create($stub, $path); 60 | } 61 | 62 | 63 | /** 64 | * Get the contents of the migration stub and insert the correct table name. 65 | * 66 | * @param string $stub 67 | * @return string 68 | */ 69 | protected function getMigrationStub($stub) { 70 | $className = studly_case($stub); 71 | $data = file_get_contents(__DIR__ . '/stubs/' . $className . '.php'); 72 | 73 | return str_replace( 74 | '%PREFIX%', 75 | Config::get('resources.tablePrefix', ''), 76 | $data 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Commands/ImportCommand.php: -------------------------------------------------------------------------------- 1 | option('force'); 33 | $this->info('Importing resources' . ($force ? ' (with force)' : '')); 34 | 35 | $allResources = array_dot(Config::get('resources.resources', [])); 36 | 37 | foreach ($allResources as $key => $descriptorClass) { 38 | 39 | $resource = $this->laravel['resources.resource']->key($key); 40 | 41 | foreach ($resource->getDescriptor()->getSeedValues() as $locale => $value) { 42 | 43 | $resource->locale($locale); 44 | 45 | try { 46 | $exists = ($resource->getFromDB() !== null); 47 | } catch (ResourceNotDefinedException $e) { 48 | $exists = false; 49 | } 50 | 51 | if ($force || !$exists) { 52 | $resource->setValue($value); 53 | $this->comment('Settting key [' . $resource->getLocalizedKey() . ']'); 54 | } else { 55 | $this->comment('Skipping key [' . $resource->getLocalizedKey() . ']'); 56 | } 57 | } 58 | } 59 | 60 | $this->info('Resources imported!'); 61 | 62 | if ($this->option('clear')) { 63 | $this->info('Clearing undefined resources'); 64 | $keys = array_keys($allResources); 65 | $unusedResources = Resource::whereNotIn('resource_key', $keys)->get(); 66 | 67 | if ($unusedResources->count()) { 68 | foreach ($unusedResources as $unusedResource) { 69 | $key = $unusedResource->getAttribute('resource_key'); 70 | $unusedResource->translations()->forceDelete(); 71 | $unusedResource->forceDelete(); 72 | $this->comment('Deleting resource [' . $key . ']'); 73 | } 74 | } else { 75 | $this->comment('No unused resources found.'); 76 | } 77 | } 78 | } 79 | 80 | 81 | /** 82 | * Get the console command options. 83 | * 84 | * @return array 85 | */ 86 | protected function getOptions() { 87 | 88 | return [ 89 | ['force', '-f', InputOption::VALUE_NONE, 'Overwrite existing keys with data from configuration files.'], 90 | ['clear', '-c', InputOption::VALUE_NONE, 'Clear out unused keys from database (i.e. keys not defined in configuration files).'], 91 | ]; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | handleConfigs(); 26 | $this->handleViews(); 27 | } 28 | 29 | 30 | /** 31 | * Register the service provider. 32 | * 33 | * @return void 34 | */ 35 | public function register() { 36 | $this->registerResource(); 37 | $this->registerResourceGroup(); 38 | $this->registerCommands(); 39 | } 40 | 41 | 42 | /** 43 | * Register the Resource 44 | */ 45 | private function registerResource() { 46 | $this->app->bind('resources.resource', function ($app) { 47 | 48 | $cache = $app['cache']; 49 | $store = $cache->driver()->getStore(); 50 | if (!is_subclass_of($store, 'Illuminate\Cache\TaggableStore')) { 51 | throw new InvalidCacheDriverException; 52 | } 53 | 54 | return new Resource($cache); 55 | }); 56 | } 57 | 58 | 59 | /** 60 | * Register the ResourceGroup 61 | */ 62 | private function registerResourceGroup() { 63 | $this->app->bind('resources.group', function ($app) { 64 | 65 | return new ResourceGroup(); 66 | }); 67 | } 68 | 69 | 70 | /** 71 | * Register the Commands 72 | */ 73 | private function registerCommands() { 74 | $this->app['resources.command.table'] = $this->app->share(function ($app) { 75 | return new TableCommand(); 76 | }); 77 | 78 | $this->commands('resources.command.table'); 79 | 80 | $this->app['resources.command.import'] = $this->app->share(function ($app) { 81 | return new ImportCommand(); 82 | }); 83 | 84 | $this->commands('resources.command.import'); 85 | } 86 | 87 | 88 | /** 89 | * Get the services provided by the provider. 90 | * 91 | * @return array 92 | */ 93 | public function provides() { 94 | return [ 95 | 'resources.resource', 96 | 'resources.group', 97 | 'resources.command.table', 98 | 'resources.command.populate' 99 | ]; 100 | } 101 | 102 | private function handleConfigs() { 103 | 104 | $configPath = __DIR__ . '/../config/resources.php'; 105 | $this->publishes([$configPath => config_path('resources.php')]); 106 | $this->mergeConfigFrom($configPath, 'resources'); 107 | } 108 | 109 | private function handleViews() { 110 | 111 | $this->loadViewsFrom( __DIR__.'/../views', 'resources'); 112 | $this->publishes([__DIR__.'/../views' => base_path('resources/views/vendor/resources')]); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Descriptor.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 75 | } 76 | 77 | 78 | /** 79 | * Get the value of the resource descriptor. 80 | * 81 | * @return mixed 82 | */ 83 | public function getValue() { 84 | 85 | return $this->value; 86 | } 87 | 88 | 89 | /** 90 | * Set the value of the resource descriptor. 91 | * 92 | * @param $value 93 | * @return mixed|void 94 | */ 95 | public function setValue($value) { 96 | 97 | $this->value = $value; 98 | } 99 | 100 | 101 | /** 102 | * @return string 103 | */ 104 | public function getDescription() { 105 | 106 | return $this->description; 107 | } 108 | 109 | 110 | /** 111 | * @return array 112 | */ 113 | public function getSeedValues() { 114 | 115 | return $this->seedValues; 116 | } 117 | 118 | 119 | /** 120 | * Render the descriptor as a form input. 121 | * 122 | * @param mixed $value 123 | * @return mixed 124 | */ 125 | public function renderInput($value) { 126 | 127 | $data = $this->getInputData($value); 128 | 129 | return View::make($this->template, $data)->render(); 130 | } 131 | 132 | 133 | /** 134 | * @param $value 135 | * @return array 136 | */ 137 | protected function getInputData($value) { 138 | 139 | $data = [ 140 | 'name' => $this->getName(), 141 | 'description' => $this->getDescription(), 142 | 'id' => $this->key, 143 | 'fieldName' => 'resources[' . $this->key . ']', 144 | 'value' => $value 145 | ]; 146 | 147 | return $data; 148 | } 149 | 150 | 151 | /** 152 | * Get the name for the resource. 153 | * 154 | * @return mixed 155 | */ 156 | public function getName() { 157 | 158 | if (!$this->name) { 159 | throw (new ResourceDescriptorNameNotDefinedException)->setReference(get_called_class()); 160 | } 161 | 162 | return $this->name; 163 | } 164 | 165 | 166 | /** 167 | * Validate value against rules. 168 | * 169 | * @param $value 170 | * @return bool|MessageBag 171 | */ 172 | public function validate($value) { 173 | 174 | $key = $this->getKey(); 175 | 176 | $validator = app('validator')->make( 177 | [$key => $value], 178 | [$key => $this->getValidationRules()], 179 | [$this->getValidationMessages()] 180 | ); 181 | 182 | if ($validator->passes()) { 183 | return true; 184 | } 185 | 186 | return $validator->messages(); 187 | } 188 | 189 | 190 | /** 191 | * @return mixed 192 | */ 193 | public function getKey() { 194 | return $this->key; 195 | } 196 | 197 | 198 | /** 199 | * Set the resource key. 200 | * 201 | * @param mixed $key 202 | */ 203 | public function setKey($key) { 204 | $this->key = $key; 205 | } 206 | 207 | 208 | /** 209 | * @return array 210 | */ 211 | public function getValidationRules() { 212 | return $this->validationRules; 213 | } 214 | 215 | 216 | /** 217 | * @return array 218 | */ 219 | public function getValidationMessages() { 220 | return $this->validationMessages; 221 | } 222 | 223 | 224 | /** 225 | * @return string 226 | */ 227 | public function getLocale() { 228 | return $this->locale; 229 | } 230 | 231 | 232 | /** 233 | * Set the resource locale. 234 | * 235 | * @param string $locale 236 | */ 237 | public function setLocale($locale) { 238 | $this->locale = $locale; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/ResourceGroup.php: -------------------------------------------------------------------------------- 1 | resources; 44 | } 45 | 46 | 47 | /** 48 | * @return MessageBag|null 49 | */ 50 | public function getErrors() { 51 | return $this->errors; 52 | } 53 | 54 | 55 | /** 56 | * @return mixed 57 | */ 58 | public function getData() { 59 | return $this->data; 60 | } 61 | 62 | 63 | /** 64 | * @param mixed $data 65 | */ 66 | public function setData($data) { 67 | $this->data = $data; 68 | } 69 | 70 | 71 | /** 72 | * Set the locale. 73 | * 74 | * @param string $locale 75 | * @return $this 76 | */ 77 | public function locale($locale) { 78 | $this->locale = $locale; 79 | 80 | return $this; 81 | } 82 | 83 | 84 | /** 85 | * Get a collection of all the resources whose keys match the given 86 | * pattern. 87 | * 88 | * @param string $pattern 89 | * @return Collection 90 | */ 91 | public function getByPattern($pattern = '*') { 92 | 93 | $resourceKeys = array_keys($this->getResourceMap()); 94 | 95 | if ($pattern !== '*') { 96 | $resourceKeys = array_filter($resourceKeys, function ($key) use ($pattern) { 97 | return starts_with($key, $pattern); 98 | }); 99 | } 100 | 101 | return $this->getByKeys($resourceKeys, true); 102 | } 103 | 104 | 105 | /** 106 | * Get the resource map, or load from config. 107 | * 108 | * @return array 109 | */ 110 | public function getResourceMap() { 111 | 112 | if (!($this->resourceMap)) { 113 | $this->resourceMap = array_dot(Config::get('resources.resources')); 114 | } 115 | 116 | return $this->resourceMap; 117 | } 118 | 119 | 120 | /** 121 | * Get a collection of all the resources with the given keys, 122 | * optionally sorted into the same order as defined in the resource map. 123 | * 124 | * @param array $keys 125 | * @param bool $sort 126 | * @return Collection 127 | */ 128 | public function getByKeys(array $keys, $sort = false) { 129 | 130 | $this->resources = new Collection(); 131 | 132 | $locale = $this->getLocale(); 133 | 134 | foreach (array_reverse($keys) as $key) { 135 | $this->resources->put($key, app('resources.resource') 136 | ->locale($locale) 137 | ->key($key) 138 | ); 139 | } 140 | 141 | if ($sort) { 142 | $this->sortByResourceMap(); 143 | } 144 | 145 | return $this; 146 | } 147 | 148 | 149 | /** 150 | * Get the locale, or load from config. 151 | * 152 | * @return string 153 | */ 154 | public function getLocale() { 155 | if (!$this->locale) { 156 | $this->locale = \Config::get('app.locale'); 157 | } 158 | 159 | return $this->locale; 160 | } 161 | 162 | 163 | /** 164 | * Arrange the order of the resources so they mirror the order in the resources 165 | * config. 166 | */ 167 | protected function sortByResourceMap() { 168 | 169 | $resourceMap = $this->getResourceMap(); 170 | $sortOrder = array_flip(array_keys($resourceMap)); 171 | 172 | $this->resources->sort(function ($a, $b) use ($sortOrder) { 173 | return $sortOrder[$a->getKey()] > $sortOrder[$b->getKey()]; 174 | }); 175 | } 176 | 177 | 178 | public function validate($input) { 179 | 180 | $rules = $messages = $niceNames = []; 181 | 182 | if (!$this->getResources()) { 183 | $this->getByKeys(array_keys($input)); 184 | } 185 | 186 | foreach ($this->getResources() as $key=>$resource) { 187 | $descriptor = $resource->getDescriptor(); 188 | if ($resourceRules = $descriptor->getValidationRules()) { 189 | $rules[$key] = $resourceRules; 190 | } 191 | if ($resourceMessages = $descriptor->getValidationMessages()) { 192 | $messages[$key] = $resourceMessages; 193 | } 194 | $niceNames[$key] = $descriptor->getName(); 195 | } 196 | 197 | $validator = Validator::make($input, $rules, $messages); 198 | $validator->setAttributeNames($niceNames); 199 | 200 | return $validator; 201 | } 202 | 203 | 204 | public function setValues($input) { 205 | $keys = array_keys($input); 206 | $resources = static::getByKeys($keys); 207 | 208 | foreach ($resources as $key => $resource) { 209 | $value = array_get($input, $key); 210 | $resource->setValue($value); 211 | } 212 | 213 | return true; 214 | } 215 | 216 | 217 | public function get($key) { 218 | return $this->resources->get($key); 219 | } 220 | 221 | 222 | /** 223 | * Get the instance as an array. 224 | * 225 | * @return array 226 | */ 227 | public function toArray() { 228 | return $this->resources->toArray(); 229 | } 230 | 231 | 232 | /** 233 | * ArrayAccess offsetExists method 234 | * 235 | * @param mixed $offset 236 | * @return bool 237 | */ 238 | public function offsetExists($offset) { 239 | return $this->resources->offsetExists($offset); 240 | } 241 | 242 | 243 | /** 244 | * ArrayAccess offsetGet method 245 | * 246 | * @param mixed $offset 247 | * @return mixed 248 | */ 249 | public function offsetGet($offset) { 250 | return $this->resources->offsetGet($offset); 251 | } 252 | 253 | 254 | /** 255 | * ArrayAccess offSetSet method 256 | * 257 | * @param mixed $offset 258 | * @param mixed $value 259 | */ 260 | public function offsetSet($offset, $value) { 261 | return $this->resources->offsetSet($offset, $value); 262 | } 263 | 264 | 265 | /** 266 | * ArrayAccess offSetUnset method 267 | * 268 | * @param mixed $offset 269 | */ 270 | public function offsetUnset($offset) { 271 | return $this->resources->offsetUnset($offset); 272 | } 273 | 274 | 275 | /** 276 | * Count elements of an object 277 | * 278 | * @return int 279 | */ 280 | public function count() { 281 | return $this->resources->count(); 282 | } 283 | 284 | 285 | /** 286 | * Retrieve an external iterator 287 | * 288 | * @return Traversable 289 | */ 290 | public function getIterator() { 291 | return $this->resources->getIterator(); 292 | } 293 | 294 | } 295 | -------------------------------------------------------------------------------- /src/Resource.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 49 | } 50 | 51 | 52 | /** 53 | * Define the resource locale. 54 | * 55 | * @param string $locale 56 | * @return $this 57 | */ 58 | public function locale($locale) { 59 | 60 | $this->setLocale($locale); 61 | 62 | return $this; 63 | } 64 | 65 | 66 | /** 67 | * Get the resource value by key. 68 | * 69 | * @param null $key 70 | * @return mixed|null 71 | * @throws ResourceNotDefinedException 72 | */ 73 | public function get($key = null) { 74 | 75 | if ($key) { 76 | $this->setKey($key); 77 | } 78 | 79 | return $this->getValue(); 80 | } 81 | 82 | 83 | /** 84 | * Get the value of the resource. 85 | * 86 | * @return mixed|null 87 | * @throws ResourceKeyNotSpecifiedException 88 | * @throws ResourceNotDefinedException 89 | */ 90 | public function getValue() { 91 | 92 | $value = $this->loadValueFromCache(); 93 | 94 | if ($value !== null) { 95 | return $value; 96 | } 97 | 98 | $value = $this->loadValueFromDatabase(); 99 | 100 | if ($value !== null) { 101 | $this->storeValueToCache($value); 102 | return $value; 103 | } 104 | 105 | throw (new ResourceNotDefinedException)->setReference($this->getKey(), $this->getLocale()); 106 | } 107 | 108 | 109 | /** 110 | * Load value of the resource from cache. 111 | * 112 | * @return mixed|null 113 | */ 114 | protected function loadValueFromCache() { 115 | 116 | $cacheKey = $this->getLocalizedCacheKey(); 117 | $tags = $this->buildCacheTags($cacheKey); 118 | 119 | $value = $this->cache->tags($tags)->get($cacheKey); 120 | 121 | return $this->getDescriptor()->fromStore($value); 122 | } 123 | 124 | 125 | /** 126 | * Load value of the resource from database. 127 | * 128 | * @return mixed|null 129 | */ 130 | protected function loadValueFromDatabase() { 131 | 132 | if (!$translation = $this->findTranslationModel($this->getKey(), $this->getLocale())) { 133 | return null; 134 | } 135 | 136 | $value = $translation->getAttribute('value'); 137 | 138 | return $this->getDescriptor()->fromStore($value); 139 | } 140 | 141 | 142 | /** 143 | * Store a value to the cache. 144 | * 145 | * @param $value 146 | * @return mixed 147 | */ 148 | protected function storeValueToCache($value) { 149 | 150 | $cacheKey = $this->getLocalizedCacheKey(); 151 | $tags = $this->buildCacheTags($cacheKey); 152 | $storedValue = $this->getDescriptor()->toStore($value); 153 | 154 | return $this->cache->tags($tags)->forever($cacheKey, $storedValue); 155 | } 156 | 157 | 158 | /** 159 | * Get the current key for the resource. 160 | * 161 | * @return string 162 | * @throws ResourceKeyNotSpecifiedException 163 | */ 164 | public function getKey() { 165 | if (!$this->key) { 166 | throw new ResourceKeyNotSpecifiedException; 167 | } 168 | 169 | return $this->key; 170 | } 171 | 172 | 173 | /** 174 | * Set resource key. 175 | * 176 | * @param string $key 177 | */ 178 | public function setKey($key) { 179 | 180 | $this->key = $key; 181 | $this->clearDescriptor(); 182 | } 183 | 184 | 185 | /** 186 | * Get the current locale for the resource, or load from config. 187 | * 188 | * @return string 189 | */ 190 | public function getLocale() { 191 | if (!$this->locale) { 192 | $this->locale = Config::get('app.locale', 'en'); 193 | } 194 | 195 | return $this->locale; 196 | } 197 | 198 | 199 | /** 200 | * Set resource locale. 201 | * 202 | * @param string $locale 203 | * @return $this 204 | */ 205 | public function setLocale($locale) { 206 | 207 | $this->locale = $locale; 208 | $this->clearDescriptor(); 209 | } 210 | 211 | 212 | /** 213 | * Build key used for cache storage/lookup. 214 | * 215 | * @return string 216 | * @throws ResourceKeyNotSpecifiedException 217 | */ 218 | protected function getLocalizedCacheKey() { 219 | 220 | $cacheKey = $this->getLocalizedKey(); 221 | if ($cachePrefix = Config::get('resources.cachePrefix')) { 222 | $cacheKey = $cachePrefix . '.' . $cacheKey; 223 | } 224 | 225 | return $cacheKey; 226 | } 227 | 228 | 229 | /** 230 | * Build an array of key tags from the cache key. 231 | * 232 | * For example, the key "resources.en.homepage.title" will get converted into the array: 233 | * 234 | * [ 235 | * 'resources', 236 | * 'resources.en', 237 | * 'resources.en.homepage', 238 | * 'resources.en.homepage.title' 239 | * ] 240 | * 241 | * This will allow us to expire portions of the cache selectively (e.g. per locale). 242 | * 243 | * @param $key 244 | * @return array 245 | */ 246 | protected function buildCacheTags($key) { 247 | 248 | $tags = []; 249 | $offset = 0; 250 | 251 | while ($pos = strpos($key, '.', $offset)) { 252 | $tags[] = substr($key, 0, $pos); 253 | $offset = $pos + 1; 254 | } 255 | 256 | $tags[] = $key; 257 | 258 | return $tags; 259 | } 260 | 261 | 262 | /** 263 | * Get the resource descriptor class. 264 | * 265 | * @return Descriptor 266 | * @throws ResourceDescriptorNotDefinedException 267 | */ 268 | public function getDescriptor() { 269 | 270 | if (!$this->descriptor) { 271 | if (!$class = $this->getDescriptorClass()) { 272 | throw (new ResourceDescriptorNotDefinedException)->setKey($this->getKey()); 273 | } 274 | 275 | $this->descriptor = app($class); 276 | $this->descriptor->setKey($this->getKey()); 277 | $this->descriptor->setLocale($this->getLocale()); 278 | } 279 | 280 | return $this->descriptor; 281 | } 282 | 283 | 284 | /** 285 | * Load the translation model for a given key and locale 286 | * 287 | * @param $key 288 | * @param $locale 289 | * @return ResourceTranslation|null 290 | */ 291 | protected function findTranslationModel($key, $locale) { 292 | 293 | if (!$record = $this->findResourceModel($key)) { 294 | return null; 295 | } 296 | 297 | if (!$translation = $record->findTranslation($locale)) { 298 | return null; 299 | } 300 | 301 | return $translation; 302 | } 303 | 304 | 305 | /** 306 | * Build the localized key for the resource (locale + key) 307 | * 308 | * @return string 309 | * @throws ResourceKeyNotSpecifiedException 310 | */ 311 | public function getLocalizedKey() { 312 | 313 | if (!$this->key) { 314 | throw new ResourceKeyNotSpecifiedException; 315 | } 316 | 317 | return $this->locale . '.' . $this->key; 318 | } 319 | 320 | 321 | protected function getDescriptorClass() { 322 | return array_get($this->getResourceMap(), $this->getKey(), null); 323 | } 324 | 325 | 326 | /** 327 | * Load the resource model for a given key. 328 | * 329 | * @param $key 330 | * @return ResourceModel|null 331 | */ 332 | protected function findResourceModel($key) { 333 | 334 | return ResourceModel::firstByKey($key); 335 | } 336 | 337 | 338 | /** 339 | * Get the resource map, or load from config. 340 | * 341 | * @return array 342 | */ 343 | public function getResourceMap() { 344 | if (!$this->resourceMap) { 345 | $this->resourceMap = array_dot(Config::get('resources.resources')); 346 | } 347 | 348 | return $this->resourceMap; 349 | } 350 | 351 | 352 | public function getFromDB($key = null) { 353 | 354 | if ($key) { 355 | $this->key($key); 356 | } 357 | 358 | return $this->loadValueFromDatabase(); 359 | } 360 | 361 | 362 | /** 363 | * Define the resource key, which loads the appropriate descriptor class. 364 | * 365 | * @param string $key 366 | * @return $this 367 | */ 368 | public function key($key) { 369 | 370 | $this->setKey($key); 371 | 372 | return $this; 373 | } 374 | 375 | 376 | public function set($key, $value) { 377 | 378 | $this->key($key); 379 | 380 | return $this->setValue($value); 381 | } 382 | 383 | 384 | public function setValue($value) { 385 | 386 | $this->storeValueToDatabase($value); 387 | $this->storeValueToCache($value); 388 | 389 | return $this; 390 | } 391 | 392 | 393 | protected function storeValueToDatabase($value) { 394 | 395 | $record = $this->findResourceModel($this->getKey()); 396 | 397 | if (!$record) { 398 | $record = ResourceModel::create([ 399 | 'resource_key' => $this->getKey(), 400 | 'resource_class' => get_class($this->getDescriptor()), 401 | ]); 402 | } 403 | 404 | $translation = $this->findTranslationModel($this->getKey(), $this->getLocale()); 405 | $storedValue = $this->getDescriptor()->toStore($value); 406 | 407 | if ($translation) { 408 | $translation->update([ 409 | 'value' => $storedValue 410 | ]); 411 | } else { 412 | $translation = new ResourceTranslation([ 413 | 'locale' => $this->getLocale(), 414 | 'value' => $storedValue 415 | ]); 416 | $record->translations()->save($translation); 417 | } 418 | 419 | return true; 420 | } 421 | 422 | 423 | public function renderInput() { 424 | 425 | $value = array_get(Input::old('resources'), $this->getKey(), $this->getValue()); 426 | 427 | return $this->getDescriptor()->renderInput($value); 428 | } 429 | 430 | 431 | public function validate($value) { 432 | return $this->getDescriptor()->validate($value); 433 | } 434 | 435 | 436 | private function clearDescriptor() { 437 | $this->descriptor = null; 438 | } 439 | 440 | } 441 | --------------------------------------------------------------------------------