├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json └── src ├── CustomField.php ├── CustomFieldConfigProvider.php ├── CustomFieldType.php ├── ExtendableServiceProvider.php ├── ModelTrait.php ├── config └── custom-fields.php └── migrations └── 2015_07_23_134516_create_custom_fields_table.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### PhpStorm ### 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 5 | 6 | *.iml 7 | 8 | ## Directory-based project format: 9 | .idea/ 10 | # if you remove the above rule, at least ignore the following: 11 | 12 | # User-specific stuff: 13 | # .idea/workspace.xml 14 | # .idea/tasks.xml 15 | # .idea/dictionaries 16 | 17 | # Sensitive or high-churn files: 18 | # .idea/dataSources.ids 19 | # .idea/dataSources.xml 20 | # .idea/sqlDataSources.xml 21 | # .idea/dynamic.xml 22 | # .idea/uiDesigner.xml 23 | 24 | # Gradle: 25 | # .idea/gradle.xml 26 | # .idea/libraries 27 | 28 | # Mongo Explorer plugin: 29 | # .idea/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.ipr 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Crashlytics plugin (for Android Studio and IntelliJ) 47 | com_crashlytics_export_strings.xml 48 | crashlytics.properties 49 | crashlytics-build.properties 50 | 51 | 52 | ### Composer ### 53 | composer.phar 54 | vendor/ 55 | 56 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 57 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 58 | # composer.lock 59 | 60 | 61 | ### OSX ### 62 | .DS_Store 63 | .AppleDouble 64 | .LSOverride 65 | 66 | # Icon must end with two \r 67 | Icon 68 | 69 | 70 | # Thumbnails 71 | ._* 72 | 73 | # Files that might appear in the root of a volume 74 | .DocumentRevisions-V100 75 | .fseventsd 76 | .Spotlight-V100 77 | .TemporaryItems 78 | .Trashes 79 | .VolumeIcon.icns 80 | 81 | # Directories potentially created on remote AFP share 82 | .AppleDB 83 | .AppleDesktop 84 | Network Trash Folder 85 | Temporary Items 86 | .apdisk -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 IronShark GmbH 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel Extendable package 2 | ========================== 3 | 4 | [![License](https://img.shields.io/github/license/ironsharkde/laravel-extendable.svg)](https://packagist.org/packages/ironshark/laravel-extendable) 5 | [![Downloads](https://img.shields.io/packagist/dt/ironshark/laravel-extendable.svg)](https://packagist.org/packages/ironshark/laravel-extendable) 6 | [![Version-stable](https://img.shields.io/packagist/v/ironshark/laravel-extendable.svg)](https://packagist.org/packages/ironshark/laravel-extendable) 7 | 8 | 9 | ## How to install 10 | 11 | ### Composer Install 12 | 13 | ```sh 14 | composer require ironshark/laravel-extendable 15 | ``` 16 | 17 | ### Laravel Service Provider 18 | 19 | Add service provider in `app/config/app.php` 20 | 21 | ```php 22 | 'providers' => [ 23 | IronShark\Extendable\ExtendableServiceProvider::class, 24 | ]; 25 | ``` 26 | 27 | 28 | Publish configs, templates and run migrations. 29 | 30 | ```php 31 | php artisan vendor:publish --provider="IronShark\Extendable\ExtendableServiceProvider" 32 | php artisan migrate 33 | ``` 34 | 35 | ## Usage 36 | 37 | ### Add traits 38 | 39 | Add model trait to models, where you want to use custom fields. 40 | 41 | ```php 42 | class Article extends \Illuminate\Database\Eloquent\Model { 43 | use IronShark\Extendable\ModelTrait; 44 | } 45 | ``` 46 | 47 | ### Config fields 48 | 49 | Use `app/config/custom-fields.php` to configure your fields. 50 | 51 | ```php 52 | return [ 53 | 'App\Room' => [ // model name 54 | 'light' => [ // field name 55 | 'title' => 'Light', // field title (can be used in views) 56 | 'type' => \IronShark\Extendable\CustomFieldType::Radio, // field type 57 | 'options' => [ // possible values/labels 58 | 0 => 'Off', 59 | 1 => 'On' 60 | ], 61 | 'default' => 1 // default value 62 | ] 63 | ] 64 | ]; 65 | ``` 66 | 67 | ### Assign/retrieve customfield values 68 | 69 | Assign custom field values as regular values. 70 | 71 | ```php 72 | $data = [ 73 | 'title' => 'Awesome Article!!!', // regular field 74 | 'recomended' => 1 // custom filed 75 | ]; 76 | 77 | $article = new Article(); 78 | $article->fill($data); 79 | $article->save(); 80 | ``` 81 | 82 | Retrieve custom field values. 83 | 84 | ```php 85 | $article = Article::find(1); 86 | $article->recomended->value; // 1 87 | echo $article->recomended; // 1 88 | ``` 89 | 90 | ### Field types 91 | 92 | | FieldType | DB DataType | Example | 93 | |---------------------------|--------------|-----------------------| 94 | | CustomFieldType::String | VARCHAR(255) | `Lorem` | 95 | | CustomFieldType::Text | TEXT | `Lorem Ipsum...` | 96 | | CustomFieldType::Select | VARCHAR(255) | `en_us` | 97 | | CustomFieldType::Radio | VARCHAR(255) | `off` | 98 | | CustomFieldType::Checkbox | VARCHAR(255) | `0` | 99 | | CustomFieldType::DateTime | TIMESTAMP | `2015-01-19 03:14:07` | 100 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ironshark/laravel-extendable", 3 | "description": "Traits for Laravel to add and manage custom Eloquent model fields.", 4 | "keywords": ["trait", "laravel", "eloquent", "extendable", "custom fields"], 5 | "type": "library", 6 | "require": { 7 | "php": ">=5.3.0" 8 | }, 9 | "require-dev": { 10 | "phpunit/phpunit": "~4.0" 11 | }, 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Anton Pauli", 16 | "email": "pauli@ironshark.de" 17 | } 18 | ], 19 | "autoload": { 20 | "psr-4": { 21 | "IronShark\\Extendable\\": "src/" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/CustomField.php: -------------------------------------------------------------------------------- 1 | parent_type, $this->field_name); 28 | } 29 | 30 | 31 | /** 32 | * Get value for current custom field 33 | * 34 | * @return mixed 35 | */ 36 | public function getValueAttribute() 37 | { 38 | $attributeName = $this->getAttributeName(); 39 | return $this->$attributeName; 40 | } 41 | 42 | 43 | /** 44 | * @param $value 45 | * @return $this 46 | * @throws \Exception 47 | */ 48 | public function setValueAttribute($value) 49 | { 50 | if ($value instanceof self) { 51 | throw new \Exception('Invalid custom attribute value'); 52 | } 53 | 54 | $attributeName = $this->getAttributeName(); 55 | $this->$attributeName = $value; 56 | return $this; 57 | } 58 | 59 | 60 | /** 61 | * Return custom field value as string 62 | * 63 | * @return string 64 | */ 65 | public function __toString() 66 | { 67 | return (string)$this->value; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CustomFieldConfigProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 20 | __DIR__.'/migrations/2015_07_23_134516_create_custom_fields_table.php' => database_path('migrations/2015_07_23_134516_create_custom_fields_table.php'), 21 | __DIR__.'/config/custom-fields.php' => config_path('custom-fields.php'), 22 | ]); 23 | } 24 | 25 | public function register() 26 | { 27 | } 28 | 29 | public function when() 30 | { 31 | return array('artisan.start'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ModelTrait.php: -------------------------------------------------------------------------------- 1 | customFieldNames() as $name) { 24 | } 25 | }); 26 | } 27 | 28 | 29 | /** 30 | * Begin querying a model with eager loading. 31 | * 32 | * @param array|string $relations 33 | * @return \Illuminate\Database\Eloquent\Builder|static 34 | */ 35 | public static function withCustomFields($relations = null) 36 | { 37 | $instance = new static; 38 | 39 | if ($relations === null) { 40 | $relations = CustomFieldConfigProvider::fieldNames(get_class($instance)); 41 | } 42 | 43 | if (is_string($relations)) { 44 | $relations = func_get_args(); 45 | } 46 | 47 | return $instance->newQuery()->with($relations); 48 | } 49 | 50 | 51 | /** 52 | * Request custom field relations, or call model methods. 53 | * 54 | * @param $name 55 | * @param $arguments 56 | * @return mixed 57 | */ 58 | public function __call($name, $arguments) 59 | { 60 | if ($this->isCustomField($name)) { 61 | return $this->customFieldRelation($name); 62 | } 63 | 64 | $query = $this->newQuery(); 65 | return call_user_func_array(array($query, $name), $arguments); 66 | } 67 | 68 | 69 | /** 70 | * Return custom field relation for specified field name. 71 | * 72 | * @param $fieldName 73 | * @return mixed 74 | */ 75 | public function customFieldRelation($fieldName) 76 | { 77 | return $this->morphOne('IronShark\Extendable\CustomField', 'parent', 'parent_type') 78 | ->where('field_name', $fieldName); 79 | } 80 | 81 | 82 | /** 83 | * Return all custom field names for current model 84 | * 85 | * @return array 86 | */ 87 | public function customFieldNames() 88 | { 89 | return CustomFieldConfigProvider::fieldNames(get_class($this)); 90 | } 91 | 92 | 93 | /** 94 | * Return true if attribute name belongs to fields. 95 | * 96 | * @param $attributeName 97 | * @return bool 98 | */ 99 | public function isCustomField($attributeName) 100 | { 101 | return in_array($attributeName, $this->customFieldNames()); 102 | } 103 | 104 | 105 | /** 106 | * Dynamically retrieve attributes on the model or custom fields. 107 | * 108 | * @param $name 109 | * @return mixed 110 | */ 111 | public function __get($name) 112 | { 113 | // return custom field value 114 | if ($this->isCustomField($name)) { 115 | return $this->getCustomFieldModel($name)->value; 116 | } 117 | 118 | // return model attribute 119 | return $this->getAttribute($name); 120 | } 121 | 122 | 123 | /** 124 | * Dynamically set attributes on the model. 125 | * 126 | * @param string $key 127 | * @param mixed $value 128 | * @return void 129 | */ 130 | public function __set($key, $value) 131 | { 132 | // set 133 | if ($this->isCustomField($key)) { 134 | if ($value instanceof CustomField) { 135 | $this->$key = $value; 136 | } else { 137 | $this->customAttributes[$key] = $value; 138 | } 139 | } else { 140 | return parent::__set($key, $value); 141 | } 142 | } 143 | 144 | 145 | /** 146 | * Save the model to the database. 147 | * 148 | * @param array $options 149 | * @return bool 150 | */ 151 | public function save(array $options = array()) 152 | { 153 | $parentResult = parent::save($options); 154 | 155 | // save custom fields 156 | foreach ($this->customFieldNames() as $name) { 157 | // custom field model instance 158 | $customFieldModel = $this->getCustomFieldModel($name); 159 | $customFieldModel->value = isset($this->customAttributes[$name]) ? $this->customAttributes[$name] : null; 160 | $customFieldModel->save(); 161 | } 162 | 163 | return $parentResult; 164 | } 165 | 166 | 167 | /** 168 | * Fill the model with an array of attributes. 169 | * 170 | * @param array $attributes 171 | * @return $this 172 | * 173 | * @throws \Illuminate\Database\Eloquent\MassAssignmentException 174 | */ 175 | public function fill(array $attributes) 176 | { 177 | $this->fillCustomAttributes($attributes); 178 | return parent::fill($attributes); 179 | } 180 | 181 | 182 | /** 183 | * Fill custom fields 184 | * 185 | * @param array $attributes 186 | */ 187 | public function fillCustomAttributes(array $attributes) 188 | { 189 | foreach ($this->customFieldNames() as $name) { 190 | if (isset($attributes[$name])) { 191 | $this->customAttributes[$name] = $attributes[$name]; 192 | } 193 | } 194 | } 195 | 196 | 197 | /** 198 | * Delete the model from the database. 199 | * 200 | * @return bool|null 201 | * @throws \Exception 202 | */ 203 | public function delete() 204 | { 205 | 206 | // delete model 207 | $parentResult = parent::delete(); 208 | 209 | // delete custom fields 210 | if ($parentResult) { 211 | CustomField::where([ 212 | 'parent_type' => get_class($this), 213 | 'parent_id' => $this->id 214 | ])->delete(); 215 | } 216 | 217 | return $parentResult; 218 | } 219 | 220 | 221 | /** 222 | * Returns custom field model instance 223 | * 224 | * @param $key 225 | * @return mixed 226 | */ 227 | public function customFieldModel($key) 228 | { 229 | return $this->relations[$key]; 230 | } 231 | 232 | 233 | /** 234 | * Create new custom field model instance 235 | * 236 | * @param $fieldName 237 | * @return CustomField 238 | */ 239 | public function newCustomFieldModel($fieldName) 240 | { 241 | return new CustomField([ 242 | 'field_name' => $fieldName, 243 | 'parent_type' => get_class($this), 244 | 'parent_id' => $this->id 245 | ]); 246 | } 247 | 248 | 249 | /** 250 | * Returns custom field model 251 | * 252 | * @param $fieldName 253 | * @return CustomField 254 | */ 255 | public function getCustomFieldModel($fieldName) 256 | { 257 | $model = $this->getAttribute($fieldName); 258 | 259 | if ($model === null) { 260 | $model = $this->newCustomFieldModel($fieldName); 261 | //$this->$fieldName = $model; 262 | } 263 | 264 | return $model; 265 | } 266 | 267 | 268 | /** 269 | * Loads custom field model 270 | * 271 | * @param $fieldName 272 | * @return mixed 273 | */ 274 | public function getAttribute($fieldName) 275 | { 276 | $model = parent::getAttribute($fieldName); 277 | 278 | if ($model === null && $this->exists) { 279 | $model = CustomField::where([ 280 | 'parent_type' => get_class($this), 281 | 'parent_id' => $this->id, 282 | 'field_name' => $fieldName 283 | ])->first(); 284 | } 285 | 286 | return $model; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/config/custom-fields.php: -------------------------------------------------------------------------------- 1 | [ 25 | 'light' => [ 26 | 'title' => 'Light', 27 | 'type' => \IronShark\Extendable\CustomFieldType::Radio, 28 | 'options' => [ 29 | 0 => 'Off', 30 | 1 => 'On' 31 | ], 32 | 'default' => 1 33 | ] 34 | ] 35 | */ 36 | 37 | ]; 38 | -------------------------------------------------------------------------------- /src/migrations/2015_07_23_134516_create_custom_fields_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('field_name', 255); 19 | $table->string('parent_type', 255); 20 | $table->unsignedInteger('parent_id'); 21 | $table->string('stringvalue', 255)->nullable(); 22 | $table->double('numbervalue')->nullable(); 23 | $table->text('textvalue')->nullable(); 24 | $table->timestamp('datevalue')->nullable(); 25 | 26 | $table->unique(['field_name', 'parent_type', 'parent_id']); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::table('custom_fields', function (Blueprint $table) { 38 | $table->drop(); 39 | }); 40 | } 41 | } 42 | --------------------------------------------------------------------------------