├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Authenticable.php ├── ConfigGenerator.php ├── Field.php ├── FieldRules.php ├── FieldTypes.php ├── Generator.php ├── MigrationAnalyzer.php ├── MigrationCommand.php ├── MigrationGenerator.php ├── Model.php ├── ModelCommand.php ├── ModelGenerator.php ├── SmartModel.php └── SmartServiceProvider.php └── tests ├── AuthenticableTest.php ├── FieldLabelTest.php ├── FieldRulesTest.php ├── FieldTypesTest.php ├── GeneratorTest.php ├── MigrationGeneratorTest.php ├── ModelGeneratorTest.php ├── ModelTest.php ├── Models ├── BigBang.php ├── Duplicity.php ├── FieldCaching.php ├── OhRule.php ├── Product.php └── User.php ├── Snapshots ├── join-tree.txt ├── migration-1.txt ├── migration-2.txt └── model.txt └── TestCase.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .phpunit.result.cache -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.2 5 | 6 | env: 7 | matrix: 8 | - COMPOSER_FLAGS="--prefer-lowest" 9 | - COMPOSER_FLAGS="" 10 | 11 | before_script: 12 | - travis_retry composer self-update 13 | - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source 14 | - composer dump-autoload 15 | 16 | script: 17 | - vendor/bin/phpunit -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Smart 2 | 3 | ![Laravel Smart Demo](https://i.imgur.com/MTFSm9b.gif) 4 | 5 | **Do not use this package in production!** 6 | 7 | 1. This is a proof of concept. (update: this package is used successfully in an app that has 25 models) 8 | 2. The code is not unit tested. (update: thanks to @robsontenorio we have basic unit testing) 9 | 3. The API is not stable. 10 | 11 | I release this alpha version to get feedback on the API and to make sure I'm not missing important use cases. 12 | 13 | Also, I cannot build this alone! I'm more than happy to receive help from the community. Don't be shy! 14 | 15 | - join the discussions (even if it's not your issue) 16 | - improve the documentation (if you found something hard to understand, raise an issue, send a pull-request, etc) 17 | - contribute with code (I will guide you as much as needed) 18 | 19 | --- 20 | 21 | ## Idea 22 | 23 | If you define the fields in the model, you can get: 24 | 25 | - **automatic migrations** 26 | - automatic validation on `save()` 27 | - grouped field definition (name, schema type, casting, fillable/guarded, validation rules, schema modifiers, schema indexes) 28 | 29 | ```php 30 | increments(), 43 | Field::make('sku')->string()->fillable()->required()->unique()->label("Sku Number"), 44 | Field::make('name')->string()->fillable()->required(), 45 | Field::make('price')->decimal(6, 2)->fillable()->required()->min(0), 46 | Field::make('description')->text()->fillable()->nullable(), 47 | Field::make('created_at')->timestamp()->nullable()->index(), 48 | Field::make('updated_at')->timestamp()->nullable()->index(), 49 | ]; 50 | } 51 | } 52 | ``` 53 | 54 | ## Installation 55 | 56 | To install the package, just run: 57 | 58 | ``` 59 | composer require deiucanta/smart 60 | ``` 61 | 62 | ## Usage 63 | 64 | Create a Smart model: 65 | 66 | ``` 67 | php artisan smart:model Product 68 | ``` 69 | 70 | Include the model in the configuration file (`config/smart.php`): 71 | 72 | > Note: this is now done automatically when you create the model. 73 | 74 | ```php 75 | [ 79 | \App\Product::class, 80 | ] 81 | ]; 82 | ``` 83 | 84 | Create a Smart migration: 85 | 86 | ``` 87 | php artisan smart:migration 88 | ``` 89 | 90 | You should be able to see a new migration in your migration directory. To apply the migration, you just run `php artisan migrate` as usual. 91 | 92 | You will find a new file called `smart.json` in the `database` directory of your app. That is where the package stores the current state of the database/models. If you delete that file and run `php artisan smart:migration`, it will create a new migration as if the schema was empty. 93 | 94 | ## API 95 | 96 | These are the primitives with which you should be able to create any kind of field. However, there are some smarter methods which you can use. 97 | 98 | ### Base methods 99 | 100 | | Method | Description | 101 | | - | - | 102 | | `type($type, $args)` | Set the `type` to be used in migrations | 103 | | `cast($cast)` | Set the `cast` type | 104 | | `rule($rule)` | Add a validation rule (Laravel format) | 105 | | `guarded()` | Make the field guarded | 106 | | `fillable()` | Make the field fillable | 107 | | `index()` | Add a simple index on this field in migrations | 108 | | `unique($where_closure = null)` | Add a unique index in migrations and a smart validation rule that ignores the current model id | 109 | | `primary()` | Add a primary key on this field in migrations | 110 | | `default($default)` | Set the default value in migrations and set the model attribute when you instantiate the model | 111 | | `nullable()` | Make the field nullable in migrations and also add the `nullable` validation rule | 112 | | `unsigned()` | Add the `unsigned` modifier in migrations | 113 | | `label($value)` | Set the field label for validation errors bag | 114 | 115 | ### Type Methods 116 | 117 | These are Laravel types that are currently supported. 118 | 119 | | Method | Type | Cast | Rule | 120 | | - | - | - | - | 121 | | `bigIncrements()` | `bigIncrements` | `integer` | - | 122 | | `bigInteger()` | `bigInteger` | `integer` | `numeric` | 123 | | `binary()` | `binary` | - | - | 124 | | `boolean()` | `boolean` | `boolean` | `boolean` | 125 | | `char($size = null)` | `char` | - | - | 126 | | `date()` | `date` | `date` | - | 127 | | `dateTime()` | `dateTime` | `datetime` | - | 128 | | `dateTimeTz()` | `dateTimeTz` | `datetime` | - | 129 | | `decimal($total, $decimal)` | `decimal` | `double` | `numeric` | 130 | | `double($total, $decimal)` | `double` | `double` | `numeric` | 131 | | `enum($values)` | `enum` | - | `in:value1,value2,...` | 132 | | `float($total, $decimal)` | `float` | `float` | `numeric` | 133 | | `geometry()` | `geometry` | - | - | 134 | | `geometryCollection()` | `geometryCollection` | - | - | 135 | | `increments()` | `increments` | `integer` | - | 136 | | `integer()` | `integer` | `integer` | `numeric` | 137 | | `ipAddress()` | `ipAddress` | - | `ip` | 138 | | `json()` | `json` | `array` | `array` | 139 | | `jsonb()` | `jsonb` | `array` | `array` | 140 | | `lineString()` | `lineString` | - | - | 141 | | `longText()` | `longText` | - | - | 142 | | `macAddress()` | `macAddress` | - | - | 143 | | `mediumIncrements()` | `mediumIncrements` | `integer` | - | 144 | | `mediumInteger()` | `mediumInteger` | `integer` | `numeric` | 145 | | `mediumText()` | `mediumText` | - | - | 146 | | `multiLineString()` | `multiLineString` | - | - | 147 | | `multiPoint()` | `multiPoint` | - | - | 148 | | `multiPolygon()` | `multiPolygon` | - | - | 149 | | `point()` | `point` | - | - | 150 | | `polygon()` | `polygon` | - | - | 151 | | `smallIncrements()` | `smallIncrements` | `integer` | - | 152 | | `smallInteger()` | `smallInteger` | `integer` | `numeric` | 153 | | `softDeletes()` | `softDeletes` | `datetime` | - | 154 | | `softDeletesTz()` | `softDeletesTz` | `datetime` | - | 155 | | `string($size = null)` | `string` | - | - | 156 | | `text()` | `text` | - | - | 157 | | `time()` | `time` | - | - | 158 | | `timeTz()` | `timeTz` | - | - | 159 | | `timestamp()` | `timestamp` | `datetime` | - | 160 | | `timestampTz()` | `timestampTz` | `datetime` | - | 161 | | `tinyIncrements()` | `tinyIncrements` | `integer` | - | 162 | | `tinyInteger()` | `tinyInteger` | `integer` | `numeric` | 163 | | `unsignedBigInteger()` | `unsignedBigInteger` | `integer` | `numeric` | 164 | | `unsignedDecimal($total, $decimal)` | `unsignedDecimal` | `double` | `numeric` | 165 | | `unsignedInteger()` | `unsignedInteger` | `integer` | `numeric` | 166 | | `unsignedMediumInteger()` | `unsignedMediumInteger` | `integer` | `numeric` | 167 | | `unsignedSmallInteger()` | `unsignedSmallInteger` | `integer` | `numeric` | 168 | | `unsignedTinyInteger()` | `unsignedTinyInteger` | `integer` | `numeric` | 169 | | `uuid()` | `uuid` | - | - | 170 | | `year()` | `year` | `integer` | `numeric` | 171 | 172 | Here are some custom types just as an example of what can be done. 173 | 174 | | Method | Type | Cast | Rule | 175 | | - | - | - | - | 176 | | `email()` | `string` | - | `email` | 177 | | `url()` | `string` | - | `url` | 178 | | `slug()` | `string` | - | `regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/` | 179 | 180 | ### Rule Methods 181 | 182 | | Method | Type | Cast | Rule | 183 | | - | - | - | - | 184 | | `accepted()` | - | - | `accepted` | 185 | | `activeUrl()` | - | - | `active_url` | 186 | | `after($date)` | - | - | `after:$date` | 187 | | `afterOrEqual($date)` | - | - | `after_or_equal:$date` | 188 | | `alpha()` | - | - | `alpha` | 189 | | `alphaDash()` | - | - | `alpha_dash` | 190 | | `alphaNum()` | - | - | `alpha_num` | 191 | | `array()` | - | - | `array` | 192 | | `bail()` | - | - | `bail` | 193 | | `before($date)` | - | - | `before:$date` | 194 | | `beforeOrEqual($date)` | - | - | `before_or_equal:$date` | 195 | | `between($min, $max)` | - | - | `between:$min,$max` | 196 | | `confirmed()` | - | - | `confirmed` | 197 | | `dateEquals($date)` | - | - | `date_equals:$date` | 198 | | `dateFormat($format)` | - | - | `date_format:$format` | 199 | | `different($field)` | - | - | `different:$field` | 200 | | `digits($value)` | - | - | `digits:$value` | 201 | | `digitsBetween($min, $max)` | - | - | `digits_between:$min,$max` | 202 | | `exists($table [, $column [, $where_closure ]])` | - | - | `Rule::exists(...)` | 203 | | `filled()` | - | - | `filled` | 204 | | `gt($field)` | - | - | `gt:$field` | 205 | | `gte($field)` | - | - | `gte:$field` | 206 | | `in($values)` | - | - | `Rule::in($values)` | 207 | | `inArray($field)` | - | - | `in_array:$field` | 208 | | `ip()` | - | - | `ip` | 209 | | `ipv4()` | - | - | `ipv4` | 210 | | `ipv6()` | - | - | `ipv6` | 211 | | `lt($field)` | - | - | `lt:$field` | 212 | | `lte($field)` | - | - | `lte:$field` | 213 | | `max($value)` | - | - | `max:$value` | 214 | | `min($value)` | - | - | `min:$value` | 215 | | `notIn($values)` | - | - | `value` | 216 | | `notRegex($pattern)` | - | - | `not_regex:$pattern` | 217 | | `numeric()` | - | - | `numeric` | 218 | | `present()` | - | - | `present` | 219 | | `regex($pattern)` | - | - | `regex:$pattern` | 220 | | `required()` | - | - | `required` | 221 | | `requiredIf($field, $value)` | - | - | `required_if:$field,$value` | 222 | | `requiredUnless($field, $value)` | - | - | `required_unless:$field,$value` | 223 | | `requiredWith($fields)` | - | - | `required_with:$fields[0],$fields[1],...` | 224 | | `requiredWithAll($fields)` | - | - | `required_with_all:$fields[0],$fields[1],...` | 225 | | `requiredWithout($fields)` | - | - | `required_without:$fields[0],$fields[1],...` | 226 | | `requiredWithoutAll($fields)` | - | - | `required_without_all:$fields[0],$fields[1],...` | 227 | | `same($field)` | - | - | `same:$field` | 228 | | `timezone()` | - | - | `timezone` | 229 | 230 | ## Extra 231 | 232 | - an article I wrote about this idea a couple of months ago (https://medium.com/@deiucanta/make-laravel-fluent-again-with-smartfields-7d543e725365) 233 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deiucanta/smart", 3 | "description": "Laravel Smart", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Andrei Canta", 8 | "email": "deiucanta@gmail.com" 9 | } 10 | ], 11 | "minimum-stability": "dev", 12 | "require": { 13 | "illuminate/support": "^5.6", 14 | "doctrine/dbal": "^2.8", 15 | "illuminate/validation": "^5.6", 16 | "illuminate/console": "^5.6" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Deiucanta\\Smart\\": "src" 21 | } 22 | }, 23 | "autoload-dev": { 24 | "psr-4": { 25 | "Deiucanta\\Smart\\Tests\\": "tests/" 26 | } 27 | }, 28 | "scripts": { 29 | "test": "vendor/bin/phpunit --colors=always" 30 | }, 31 | "extra": { 32 | "laravel": { 33 | "providers": [ 34 | "Deiucanta\\Smart\\SmartServiceProvider" 35 | ] 36 | } 37 | }, 38 | "require-dev": { 39 | "orchestra/testbench": "^3.8@dev", 40 | "phpunit/phpunit": "^7.4@dev" 41 | } 42 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Authenticable.php: -------------------------------------------------------------------------------- 1 | initSmartFields(); 12 | 13 | parent::__construct($attributes); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ConfigGenerator.php: -------------------------------------------------------------------------------- 1 | joinTree([ 10 | ' [", 15 | $this->printModelNames($models), 16 | '],', 17 | ], 18 | '];', 19 | ]); 20 | } 21 | 22 | protected function printModelNames($models) 23 | { 24 | return array_map(function ($model) { 25 | return var_export($model, true).','; 26 | }, $models); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Field.php: -------------------------------------------------------------------------------- 1 | name = $name; 38 | } 39 | 40 | public static function make($name) 41 | { 42 | return new static($name); 43 | } 44 | 45 | public function type($type, $typeArgs = []) 46 | { 47 | $this->type = $type; 48 | $this->typeArgs = $typeArgs; 49 | 50 | return $this; 51 | } 52 | 53 | public function cast($cast) 54 | { 55 | $this->cast = $cast; 56 | 57 | return $this; 58 | } 59 | 60 | public function rule($rule) 61 | { 62 | $this->rules[] = $rule; 63 | 64 | return $this; 65 | } 66 | 67 | public function guarded() 68 | { 69 | $this->guarded = true; 70 | 71 | return $this; 72 | } 73 | 74 | public function fillable() 75 | { 76 | $this->fillable = true; 77 | 78 | return $this; 79 | } 80 | 81 | public function label(string $value) 82 | { 83 | $this->label = $value; 84 | 85 | return $this; 86 | } 87 | 88 | public function index() 89 | { 90 | $this->index = true; 91 | 92 | return $this; 93 | } 94 | 95 | public function unique($uniqueClosure = null) 96 | { 97 | $this->unique = true; 98 | $this->uniqueClosure = $uniqueClosure; 99 | 100 | return $this; 101 | } 102 | 103 | public function primary() 104 | { 105 | $this->primary = true; 106 | 107 | return $this; 108 | } 109 | 110 | public function default($default) 111 | { 112 | $this->default = $default; 113 | 114 | return $this; 115 | } 116 | 117 | public function nullable() 118 | { 119 | $this->nullable = true; 120 | $this->rule('nullable'); 121 | 122 | return $this; 123 | } 124 | 125 | public function unsigned() 126 | { 127 | $this->unsigned = true; 128 | $this->rule('min:0'); 129 | 130 | return $this; 131 | } 132 | 133 | public function validateRawValue() 134 | { 135 | $this->validateRawValue = true; 136 | 137 | return $this; 138 | } 139 | 140 | public function getValidationRules($model) 141 | { 142 | $rules = $this->rules; 143 | 144 | if ($this->unique) { 145 | $rules[] = $this->makeUniqueRule($model); 146 | } 147 | 148 | return $rules; 149 | } 150 | 151 | protected function makeUniqueRule($model) 152 | { 153 | $rule = Rule::unique($model->getTable(), $this->name); 154 | 155 | if ($model->getKey()) { 156 | $rule->ignore($model->getKey(), $model->getKeyName()); 157 | } 158 | 159 | if ($this->uniqueClosure instanceof Closure) { 160 | $rule->where($this->uniqueClosure); 161 | } 162 | 163 | return $rule; 164 | } 165 | 166 | public function getSchemaData() 167 | { 168 | $output = []; 169 | 170 | if ($this->type === null) { 171 | throw new Exception("Field `{$this->name}` doesn't have a type."); 172 | } 173 | 174 | $output['type'] = $this->type; 175 | $output['typeArgs'] = $this->typeArgs; 176 | 177 | if ($this->index) { 178 | $output['index'] = $this->index; 179 | } 180 | if ($this->unique) { 181 | $output['unique'] = $this->unique; 182 | } 183 | if ($this->primary) { 184 | $output['primary'] = $this->primary; 185 | } 186 | 187 | if ($this->default) { 188 | $output['default'] = $this->default; 189 | } 190 | 191 | if ($this->nullable) { 192 | $output['nullable'] = $this->nullable; 193 | } 194 | if ($this->unsigned) { 195 | $output['unsigned'] = $this->unsigned; 196 | } 197 | 198 | return $output; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/FieldRules.php: -------------------------------------------------------------------------------- 1 | rule('accepted'); 12 | } 13 | 14 | public function activeUrl() 15 | { 16 | return $this->rule('active_url'); 17 | } 18 | 19 | public function after($date) 20 | { 21 | return $this->rule("after:$date"); 22 | } 23 | 24 | public function afterOrEqual($date) 25 | { 26 | return $this->rule("after_or_equal:$date"); 27 | } 28 | 29 | public function alpha() 30 | { 31 | return $this->rule('alpha'); 32 | } 33 | 34 | public function alphaDash() 35 | { 36 | return $this->rule('alpha_dash'); 37 | } 38 | 39 | public function alphaNum() 40 | { 41 | return $this->rule('alpha_num'); 42 | } 43 | 44 | public function array() 45 | { 46 | return $this->rule('array'); 47 | } 48 | 49 | public function bail() 50 | { 51 | return $this->rule('bail'); 52 | } 53 | 54 | public function before($date) 55 | { 56 | return $this->rule("before:$date"); 57 | } 58 | 59 | public function beforeOrEqual($date) 60 | { 61 | return $this->rule("before_or_equal:$date"); 62 | } 63 | 64 | public function between($min, $max) 65 | { 66 | return $this->rule("between:$min,$max"); 67 | } 68 | 69 | // boolean is in FieldTypes 70 | 71 | public function confirmed() 72 | { 73 | return $this->rule('confirmed'); 74 | } 75 | 76 | // date is in FieldTypes 77 | 78 | public function dateEquals($date) 79 | { 80 | return $this->rule("date_equals:$date"); 81 | } 82 | 83 | public function dateFormat($format) 84 | { 85 | return $this->rule("date_format:$format"); 86 | } 87 | 88 | public function different($field) 89 | { 90 | return $this->rule("different:$field"); 91 | } 92 | 93 | public function digits($value) 94 | { 95 | return $this->rule("digits:$value"); 96 | } 97 | 98 | public function digitsBetween($min, $max) 99 | { 100 | return $this->rule("digits_between:$min,$max"); 101 | } 102 | 103 | public function dimensions() 104 | { 105 | throw new \Exception('`dimensions` is not supported'); 106 | } 107 | 108 | public function distinct() 109 | { 110 | throw new \Exception('`distinct` is not supported'); 111 | } 112 | 113 | // email is in FieldTypes 114 | 115 | public function exists($table, $column = null, $where = null) 116 | { 117 | if (strpos($table, '\\') !== false) { 118 | $instance = new $table(); 119 | $table = $instance->getTable(); 120 | } 121 | 122 | if ($column === null) { 123 | $column = $this->name; 124 | } 125 | 126 | $rule = Rule::exists($table, $column); 127 | 128 | if ($where) { 129 | $rule->where($where); 130 | } 131 | 132 | return $this->rule($rule); 133 | } 134 | 135 | public function file() 136 | { 137 | throw new \Exception('`file` is not supported'); 138 | } 139 | 140 | public function filled() 141 | { 142 | return $this->rule('filled'); 143 | } 144 | 145 | public function gt($field) 146 | { 147 | return $this->rule("gt:$field"); 148 | } 149 | 150 | public function gte($field) 151 | { 152 | return $this->rule("gte:$field"); 153 | } 154 | 155 | public function image() 156 | { 157 | throw new \Exception('`image` is not supported'); 158 | } 159 | 160 | public function in($values) 161 | { 162 | return $this->rule(Rule::in($values)); 163 | } 164 | 165 | public function inArray($field) 166 | { 167 | return $this->rule("in_array:$field"); 168 | } 169 | 170 | // integer is in FieldTypes 171 | 172 | public function ip() 173 | { 174 | return $this->rule('ip'); 175 | } 176 | 177 | public function ipv4() 178 | { 179 | return $this->rule('ipv4'); 180 | } 181 | 182 | public function ipv6() 183 | { 184 | return $this->rule('ipv6'); 185 | } 186 | 187 | // json is in FieldTypes 188 | 189 | public function lt($field) 190 | { 191 | return $this->rule("lt:$field"); 192 | } 193 | 194 | public function lte($field) 195 | { 196 | return $this->rule("lte:$field"); 197 | } 198 | 199 | public function max($value) 200 | { 201 | return $this->rule("max:$value"); 202 | } 203 | 204 | public function mimeTypes() 205 | { 206 | throw new \Exception('`mimetypes` is not supported'); 207 | } 208 | 209 | public function mimes() 210 | { 211 | throw new \Exception('`mimes` is not supported'); 212 | } 213 | 214 | public function min($value) 215 | { 216 | return $this->rule("min:$value"); 217 | } 218 | 219 | public function notIn($values) 220 | { 221 | return $this->rule(Rule::notIn($values)); 222 | } 223 | 224 | public function notRegex($pattern) 225 | { 226 | return $this->rule("not_regex:$pattern"); 227 | } 228 | 229 | // nullable is in Field 230 | 231 | public function numeric() 232 | { 233 | return $this->rule('numeric'); 234 | } 235 | 236 | public function present() 237 | { 238 | return $this->rule('present'); 239 | } 240 | 241 | public function regex($pattern) 242 | { 243 | return $this->rule("regex:$pattern"); 244 | } 245 | 246 | public function required() 247 | { 248 | return $this->rule('required'); 249 | } 250 | 251 | public function requiredIf($field, $value) 252 | { 253 | return $this->rule("required_if:$field,$value"); 254 | } 255 | 256 | public function requiredUnless($field, $value) 257 | { 258 | return $this->rule("required_unless:$field,$value"); 259 | } 260 | 261 | public function requiredWith($fields) 262 | { 263 | return $this->rule('required_with:'.implode(',', $fields)); 264 | } 265 | 266 | public function requiredWithAll($fields) 267 | { 268 | return $this->rule('required_with_all:'.implode(',', $fields)); 269 | } 270 | 271 | public function requiredWithout($fields) 272 | { 273 | return $this->rule('required_without:'.implode(',', $fields)); 274 | } 275 | 276 | public function requiredWithoutAll($fields) 277 | { 278 | return $this->rule('required_without_all:'.implode(',', $fields)); 279 | } 280 | 281 | public function same($field) 282 | { 283 | return $this->rule("same:$field"); 284 | } 285 | 286 | // string is in FieldTypes 287 | 288 | public function timezone() 289 | { 290 | return $this->rule('timezone'); 291 | } 292 | 293 | // unique is in Field 294 | 295 | // url is in FieldTypes 296 | } 297 | -------------------------------------------------------------------------------- /src/FieldTypes.php: -------------------------------------------------------------------------------- 1 | type('bigIncrements')->cast('integer'); 10 | } 11 | 12 | public function bigInteger() 13 | { 14 | return $this->type('bigInteger')->cast('integer')->rule('numeric'); 15 | } 16 | 17 | public function binary() 18 | { 19 | return $this->type('binary'); 20 | } 21 | 22 | public function boolean() 23 | { 24 | return $this->type('boolean')->cast('boolean')->rule('boolean'); 25 | } 26 | 27 | public function char($size = null) 28 | { 29 | return $this->type('char', $size ? [$size] : []); 30 | } 31 | 32 | public function date() 33 | { 34 | return $this->type('date')->cast('date'); 35 | } 36 | 37 | public function dateTime() 38 | { 39 | return $this->type('dateTime')->cast('datetime'); 40 | } 41 | 42 | public function dateTimeTz() 43 | { 44 | return $this->type('dateTimeTz')->cast('datetime'); 45 | } 46 | 47 | public function decimal($total, $decimal) 48 | { 49 | return $this->type('decimal', [$total, $decimal])->cast('double')->rule('numeric'); 50 | } 51 | 52 | public function double($total, $decimal) 53 | { 54 | return $this->type('double', [$total, $decimal])->cast('double')->rule('numeric'); 55 | } 56 | 57 | public function enum($values) 58 | { 59 | return $this->type('enum', $values)->rule('in:'.implode(',', $values)); 60 | } 61 | 62 | public function float($total, $decimal) 63 | { 64 | return $this->type('float', [$total, $decimal])->cast('float')->rule('numeric'); 65 | } 66 | 67 | public function geometry() 68 | { 69 | return $this->type('geometry'); 70 | } 71 | 72 | public function geometryCollection() 73 | { 74 | return $this->type('geometryCollection'); 75 | } 76 | 77 | public function increments() 78 | { 79 | return $this->type('increments')->cast('integer'); 80 | } 81 | 82 | public function integer() 83 | { 84 | return $this->type('integer')->cast('integer')->rule('numeric'); 85 | } 86 | 87 | public function ipAddress() 88 | { 89 | return $this->type('ipAddress')->rule('ip'); 90 | } 91 | 92 | public function json() 93 | { 94 | return $this->type('json')->cast('array')->rule('array'); 95 | } 96 | 97 | public function jsonb() 98 | { 99 | return $this->type('jsonb')->cast('array')->rule('array'); 100 | } 101 | 102 | public function lineString() 103 | { 104 | return $this->type('lineString'); 105 | } 106 | 107 | public function longText() 108 | { 109 | return $this->type('longText'); 110 | } 111 | 112 | public function macAddress() 113 | { 114 | return $this->type('macAddress'); 115 | } 116 | 117 | public function mediumIncrements() 118 | { 119 | return $this->type('mediumIncrements')->cast('integer'); 120 | } 121 | 122 | public function mediumInteger() 123 | { 124 | return $this->type('mediumInteger')->cast('integer')->rule('numeric'); 125 | } 126 | 127 | public function mediumText() 128 | { 129 | return $this->type('mediumText'); 130 | } 131 | 132 | public function morphs() 133 | { 134 | throw new \Exception('`morphs` is not supported'); 135 | } 136 | 137 | public function multiLineString() 138 | { 139 | return $this->type('multiLineString'); 140 | } 141 | 142 | public function multiPoint() 143 | { 144 | return $this->type('multiPoint'); 145 | } 146 | 147 | public function multiPolygon() 148 | { 149 | return $this->type('multiPolygon'); 150 | } 151 | 152 | public function point() 153 | { 154 | return $this->type('point'); 155 | } 156 | 157 | public function polygon() 158 | { 159 | return $this->type('polygon'); 160 | } 161 | 162 | public function smallIncrements() 163 | { 164 | return $this->type('smallIncrements')->cast('integer'); 165 | } 166 | 167 | public function smallInteger() 168 | { 169 | return $this->type('smallInteger')->cast('integer')->rule('numeric'); 170 | } 171 | 172 | public function softDeletes() 173 | { 174 | return $this->type('softDeletes')->cast('datetime'); 175 | } 176 | 177 | public function softDeletesTz() 178 | { 179 | return $this->type('softDeletesTz')->cast('datetime'); 180 | } 181 | 182 | public function string($size = null) 183 | { 184 | return $this->type('string', $size ? [$size] : []); 185 | } 186 | 187 | public function text() 188 | { 189 | return $this->type('text'); 190 | } 191 | 192 | public function time() 193 | { 194 | return $this->type('time'); 195 | } 196 | 197 | public function timeTz() 198 | { 199 | return $this->type('timeTz'); 200 | } 201 | 202 | public function timestamp() 203 | { 204 | return $this->type('timestamp')->cast('datetime'); 205 | } 206 | 207 | public function timestampTz() 208 | { 209 | return $this->type('timestampTz')->cast('datetime'); 210 | } 211 | 212 | public function timestamps() 213 | { 214 | throw new \Exception('`timestamps` is not supported'); 215 | } 216 | 217 | public function timestampsTz() 218 | { 219 | throw new \Exception('`timestampsTz` is not supported'); 220 | } 221 | 222 | public function tinyIncrements() 223 | { 224 | return $this->type('tinyIncrements')->cast('integer'); 225 | } 226 | 227 | public function tinyInteger() 228 | { 229 | return $this->type('tinyInteger')->cast('integer')->rule('numeric'); 230 | } 231 | 232 | public function unsignedBigInteger() 233 | { 234 | return $this->type('unsignedBigInteger')->cast('integer')->rule('numeric'); 235 | } 236 | 237 | public function unsignedDecimal($total, $decimal) 238 | { 239 | return $this->type('unsignedDecimal', [$total, $decimal])->cast('double')->rule('numeric'); 240 | } 241 | 242 | public function unsignedInteger() 243 | { 244 | return $this->type('unsignedInteger')->cast('integer')->rule('numeric'); 245 | } 246 | 247 | public function unsignedMediumInteger() 248 | { 249 | return $this->type('unsignedMediumInteger')->cast('integer')->rule('numeric'); 250 | } 251 | 252 | public function unsignedSmallInteger() 253 | { 254 | return $this->type('unsignedSmallInteger')->cast('integer')->rule('numeric'); 255 | } 256 | 257 | public function unsignedTinyInteger() 258 | { 259 | return $this->type('unsignedTinyInteger')->cast('integer')->rule('numeric'); 260 | } 261 | 262 | public function uuid() 263 | { 264 | return $this->type('uuid'); 265 | } 266 | 267 | public function year() 268 | { 269 | return $this->type('year')->cast('integer')->rule('numeric'); 270 | } 271 | 272 | // extra 273 | 274 | public function email($size = null) 275 | { 276 | return $this->type('string', $size ? [$size] : [])->rule('email'); 277 | } 278 | 279 | public function url($size = null) 280 | { 281 | return $this->type('string', $size ? [$size] : [])->rule('url'); 282 | } 283 | 284 | public function slug($size = null) 285 | { 286 | return $this->type('string', $size ? [$size] : [])->rule('regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/'); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/Generator.php: -------------------------------------------------------------------------------- 1 | joinTree($item, $level + 1); 14 | } elseif ($item !== '') { 15 | $output .= str_repeat(' ', $level * $indent).$item."\n"; 16 | } else { 17 | $output .= "\n"; 18 | } 19 | } 20 | 21 | return $output; 22 | } 23 | 24 | public function joinSections($sections) 25 | { 26 | $output = []; 27 | 28 | // filter out empty sections 29 | $sections = array_values(array_filter($sections, function ($section) { 30 | return $section !== null && count($section) > 0; 31 | })); 32 | 33 | for ($i = 0; $i < count($sections); $i++) { 34 | $output = array_merge($output, $sections[$i]); 35 | if ($i < count($sections) - 1) { 36 | $output[] = ''; 37 | } 38 | } 39 | 40 | return $output; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/MigrationAnalyzer.php: -------------------------------------------------------------------------------- 1 | getSmartFields(); 16 | 17 | foreach ($fields as $field) { 18 | $name = $field->name; 19 | $data[$model][$name] = $field->getSchemaData(); 20 | } 21 | } 22 | 23 | return $data; 24 | } 25 | 26 | public function diff($oldData, $newData) 27 | { 28 | $created = $updated = $deleted = []; 29 | 30 | foreach ($newData as $model => $fields) { 31 | if (isset($oldData[$model]) === false) { 32 | $created[$model] = $this->modelDiff([], $fields); 33 | } else { 34 | $diff = $this->modelDiff($oldData[$model], $newData[$model]); 35 | if ($diff) { 36 | $updated[$model] = $diff; 37 | } 38 | } 39 | } 40 | 41 | foreach ($oldData as $model => $fields) { 42 | if (isset($newData[$model]) === false) { 43 | $deleted[$model] = true; 44 | } 45 | } 46 | 47 | // 48 | 49 | $result = []; 50 | 51 | if (count($created)) { 52 | $result['created'] = $created; 53 | } 54 | if (count($updated)) { 55 | $result['updated'] = $updated; 56 | } 57 | if (count($deleted)) { 58 | $result['deleted'] = $deleted; 59 | } 60 | 61 | return count($result) ? $result : null; 62 | } 63 | 64 | protected function modelDiff($oldFields, $newFields) 65 | { 66 | $created = $updated = $deleted = []; 67 | 68 | foreach ($newFields as $name => $field) { 69 | if (isset($oldFields[$name]) === false) { 70 | $created[$name] = $field; 71 | } else { 72 | $diff = $this->fieldDiff($oldFields[$name], $field); 73 | if ($diff) { 74 | $updated[$name] = $diff; 75 | } 76 | } 77 | } 78 | 79 | foreach ($oldFields as $name => $field) { 80 | if (isset($newFields[$name]) === false) { 81 | $deleted[$name] = true; 82 | } 83 | } 84 | 85 | // 86 | 87 | $result = []; 88 | 89 | if (count($created)) { 90 | $result['created'] = $created; 91 | } 92 | if (count($updated)) { 93 | $result['updated'] = $updated; 94 | } 95 | if (count($deleted)) { 96 | $result['deleted'] = $deleted; 97 | } 98 | 99 | return count($result) ? $result : null; 100 | } 101 | 102 | protected function fieldDiff($oldField, $newField) 103 | { 104 | ksort($oldField); 105 | ksort($newField); 106 | 107 | if ($oldField === $newField) { 108 | return; 109 | } 110 | 111 | $output = []; 112 | 113 | if (isset($newField['type'])) { 114 | $output['type'] = $newField['type']; 115 | } 116 | if (isset($newField['typeArgs'])) { 117 | $output['typeArgs'] = $newField['typeArgs']; 118 | } 119 | 120 | if (isset($newField['default'])) { 121 | $output['default'] = $newField['default']; 122 | } 123 | if (isset($newField['nullable'])) { 124 | $output['nullable'] = $newField['nullable']; 125 | } 126 | if (isset($newField['unsigned'])) { 127 | $output['unsigned'] = $newField['unsigned']; 128 | } 129 | 130 | if (isset($newField['index']) && !isset($oldField['index'])) { 131 | $output['index'] = true; 132 | } 133 | if (!isset($newField['index']) && isset($oldField['index'])) { 134 | $output['index'] = false; 135 | } 136 | 137 | if (isset($newField['unique']) && !isset($oldField['unique'])) { 138 | $output['unique'] = true; 139 | } 140 | if (!isset($newField['unique']) && isset($oldField['unique'])) { 141 | $output['unique'] = false; 142 | } 143 | 144 | if (isset($newField['primary']) && !isset($oldField['primary'])) { 145 | $output['primary'] = true; 146 | } 147 | if (!isset($newField['primary']) && isset($oldField['primary'])) { 148 | $output['primary'] = false; 149 | } 150 | 151 | return $output; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/MigrationCommand.php: -------------------------------------------------------------------------------- 1 | analyzer = new MigrationAnalyzer(); 19 | $this->generator = new MigrationGenerator(); 20 | 21 | $newData = $this->getNewData(); 22 | $oldData = $this->getOldData(); 23 | 24 | $up = $this->analyzer->diff($oldData, $newData); 25 | $down = $this->analyzer->diff($newData, $oldData); 26 | 27 | if ($up && $down) { 28 | $time = time(); 29 | File::put( 30 | database_path('migrations/'.date('Y_m_d_His').'_smart_migration_'.$time.'.php'), 31 | $this->generator->print($up, $down, 'SmartMigration'.$time) 32 | ); 33 | $this->saveData($newData); 34 | } else { 35 | $this->info('No changes.'); 36 | } 37 | } 38 | 39 | protected function getNewData() 40 | { 41 | $models = config('smart.models'); 42 | 43 | return $this->analyzer->scan($models); 44 | } 45 | 46 | protected function getOldData() 47 | { 48 | $path = database_path('smart.json'); 49 | 50 | if (File::exists($path)) { 51 | $content = File::get($path); 52 | 53 | return json_decode($content, true); 54 | } 55 | 56 | return []; 57 | } 58 | 59 | protected function saveData($data) 60 | { 61 | $path = database_path('smart.json'); 62 | $content = json_encode($data, JSON_PRETTY_PRINT); 63 | 64 | File::put($path, $content); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/MigrationGenerator.php: -------------------------------------------------------------------------------- 1 | joinTree([ 10 | 'joinSections([ 19 | $this->printMethod('up', $up), 20 | $this->printMethod('down', $down), 21 | ]), 22 | '}', 23 | ]); 24 | } 25 | 26 | protected function printMethod($name, $data) 27 | { 28 | return [ 29 | "public function {$name}()", 30 | '{', 31 | $this->joinSections([ 32 | $this->printCreatedModels($data), 33 | $this->printUpdatedModels($data), 34 | $this->printDeletedModels($data), 35 | ]), 36 | '}', 37 | ]; 38 | } 39 | 40 | protected function printCreatedModels($data) 41 | { 42 | if (!isset($data['created'])) { 43 | return; 44 | } 45 | 46 | $output = []; 47 | 48 | foreach ($data['created'] as $model => $fields) { 49 | $instance = new $model(); 50 | $table = $instance->getTable(); 51 | 52 | $output[] = [ 53 | "Schema::create('{$table}', function (Blueprint \$table) {", 54 | $this->printFields($fields), 55 | '});', 56 | ]; 57 | } 58 | 59 | return $this->joinSections($output); 60 | } 61 | 62 | protected function printUpdatedModels($data) 63 | { 64 | if (!isset($data['updated'])) { 65 | return; 66 | } 67 | 68 | $output = []; 69 | 70 | foreach ($data['updated'] as $model => $fields) { 71 | $instance = new $model(); 72 | $table = $instance->getTable(); 73 | 74 | $output[] = [ 75 | "Schema::table('{$table}', function (Blueprint \$table) {", 76 | $this->printFields($fields), 77 | '});', 78 | ]; 79 | } 80 | 81 | return $this->joinSections($output); 82 | } 83 | 84 | protected function printDeletedModels($data) 85 | { 86 | if (!isset($data['deleted'])) { 87 | return; 88 | } 89 | 90 | $output = []; 91 | 92 | foreach ($data['deleted'] as $model => $fields) { 93 | $instance = new $model(); 94 | $table = $instance->getTable(); 95 | 96 | $output[] = [ 97 | "Schema::drop('{$table}');", 98 | ]; 99 | } 100 | 101 | return $this->joinSections($output); 102 | } 103 | 104 | protected function printFields($fields) 105 | { 106 | $output = []; 107 | 108 | if (isset($fields['created'])) { 109 | foreach ($fields['created'] as $name => $data) { 110 | $output[] = $this->printField($name, $data).';'; 111 | } 112 | } 113 | 114 | if (isset($fields['updated'])) { 115 | foreach ($fields['updated'] as $name => $data) { 116 | $output[] = $this->printField($name, $data).'->change();'; 117 | } 118 | } 119 | 120 | if (isset($fields['deleted'])) { 121 | foreach ($fields['deleted'] as $name => $field) { 122 | $output[] = "\$table->dropColumn('{$name}');"; 123 | } 124 | } 125 | 126 | return $output; 127 | } 128 | 129 | protected function printField($name, $data) 130 | { 131 | $args = isset($data['typeArgs']) ? $data['typeArgs'] : []; 132 | $output = '$table->'.$this->printFieldType($data['type'], $name, $args); 133 | 134 | if (isset($data['index'])) { 135 | $output .= '->index('.json_encode($data['index']).')'; 136 | } 137 | if (isset($data['unique'])) { 138 | $output .= '->unique('.json_encode($data['unique']).')'; 139 | } 140 | if (isset($data['default'])) { 141 | $output .= '->default('.json_encode($data['default']).')'; 142 | } 143 | if (isset($data['primary'])) { 144 | $output .= '->primary('.json_encode($data['primary']).')'; 145 | } 146 | if (isset($data['unsigned'])) { 147 | $output .= '->unsigned('.json_encode($data['unsigned']).')'; 148 | } 149 | if (isset($data['nullable'])) { 150 | $output .= '->nullable('.json_encode($data['nullable']).')'; 151 | } 152 | 153 | return $output; 154 | } 155 | 156 | protected function printFieldType($type, $name, $args) 157 | { 158 | array_unshift($args, $name); 159 | 160 | return $type.'('.implode(', ', array_map('json_encode', $args)).')'; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Model.php: -------------------------------------------------------------------------------- 1 | initSmartFields(); 12 | 13 | parent::__construct($attributes); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ModelCommand.php: -------------------------------------------------------------------------------- 1 | argument('name'); 16 | 17 | if (File::exists(app_path($name.'.php'))) { 18 | $this->error('This model already exists.'); 19 | 20 | return false; 21 | } 22 | 23 | $this->generateModel($name); 24 | $this->generateConfig($name); 25 | } 26 | 27 | protected function generateModel($name) 28 | { 29 | $generator = new ModelGenerator(); 30 | 31 | File::put(app_path($name.'.php'), $generator->print($name)); 32 | } 33 | 34 | protected function generateConfig($name) 35 | { 36 | $models = config('smart.models', []); 37 | $models[] = "App\\$name"; 38 | 39 | $generator = new ConfigGenerator(); 40 | 41 | File::put(config_path('smart.php'), $generator->print($models)); 42 | 43 | $this->call('config:clear'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ModelGenerator.php: -------------------------------------------------------------------------------- 1 | joinTree([ 10 | 'increments(),", 26 | ], 27 | '];', 28 | ], 29 | '}', 30 | ], 31 | '}', 32 | ]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SmartModel.php: -------------------------------------------------------------------------------- 1 | removeUnknownAttributes(); 19 | }); 20 | 21 | static::saving(function ($model) { 22 | $validator = $model->validate(); 23 | 24 | if ($validator->fails()) { 25 | throw new ValidationException($validator); 26 | } 27 | }); 28 | 29 | static::saved(function ($model) { 30 | $model->resetValidator(); 31 | }); 32 | } 33 | 34 | public function getSmartFields() 35 | { 36 | if (!isset(static::$smartFields[static::class])) { 37 | $fields = collect(); 38 | 39 | foreach ($this->fields() as $field) { 40 | if ($fields->has($field->name)) { 41 | throw new \Exception("Field names must be unique. Duplicity on '{$field->name}'."); 42 | } 43 | 44 | $fields->put($field->name, $field); 45 | } 46 | 47 | static::$smartFields[static::class] = $fields; 48 | } 49 | 50 | return static::$smartFields[static::class]; 51 | } 52 | 53 | protected function initSmartFields() 54 | { 55 | $fields = $this->getSmartFields(); 56 | 57 | foreach ($fields as $field) { 58 | if ($field->fillable === true) { 59 | $this->fillable[] = $field->name; 60 | } 61 | 62 | if ($field->guarded === true) { 63 | $this->guarded[] = $field->name; 64 | } 65 | 66 | if ($field->cast !== null) { 67 | $this->casts[$field->name] = $field->cast; 68 | } 69 | 70 | if ($field->default !== null) { 71 | $this->setAttribute($field->name, $field->default); 72 | } 73 | } 74 | } 75 | 76 | public function setAttribute($key, $value) 77 | { 78 | // model needs to be validated again 79 | $this->resetValidator(); 80 | 81 | $fields = $this->getSmartFields(); 82 | 83 | if ($fields->has($key) && $fields->get($key)->validateRawValue) { 84 | $this->rawAttributes[$key] = $value; 85 | } 86 | 87 | return parent::setAttribute($key, $value); 88 | } 89 | 90 | protected function getValidatorData($skip = []) 91 | { 92 | $values = $rules = $labels = []; 93 | $fields = $this->getSmartFields(); 94 | 95 | foreach ($fields as $key => $field) { 96 | if (in_array($key, $skip)) { 97 | continue; 98 | } 99 | 100 | if ($field->validateRawValue && !isset($this->rawAttributes[$key])) { 101 | continue; 102 | } 103 | 104 | $fieldRules = $field->getValidationRules($this); 105 | 106 | if (count($fieldRules) === 0) { 107 | continue; 108 | } 109 | 110 | $rules[$key] = $fieldRules; 111 | 112 | $values[$key] = isset($this->rawAttributes[$key]) 113 | ? $this->rawAttributes[$key] 114 | : $this->getAttribute($key); 115 | 116 | $labels[$key] = isset($field->label) ? $field->label : $key; 117 | } 118 | 119 | return compact('values', 'rules', 'labels'); 120 | } 121 | 122 | public function validate($skip = []) 123 | { 124 | if ($this->validator === null) { 125 | $data = $this->getValidatorData($skip); 126 | $this->validator = Validator::make($data['values'], $data['rules']); 127 | $this->validator->setAttributeNames($data['labels']); 128 | } 129 | 130 | return $this->validator; 131 | } 132 | 133 | public function resetValidator() 134 | { 135 | $this->validator = null; 136 | } 137 | 138 | public function removeUnknownAttributes() 139 | { 140 | $keys = array_keys($this->attributes); 141 | $fields = $this->getSmartFields(); 142 | 143 | foreach ($keys as $key) { 144 | if (!$fields->has($key)) { 145 | unset($this->$key); 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/SmartServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 12 | $this->commands([ 13 | ModelCommand::class, 14 | MigrationCommand::class, 15 | ]); 16 | } 17 | } 18 | 19 | public function register() 20 | { 21 | // 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/AuthenticableTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(\Illuminate\Database\Eloquent\Model::class, $user); 20 | $this->assertInstanceOf(\Illuminate\Foundation\Auth\User::class, $user); 21 | } 22 | 23 | /** @test */ 24 | public function it_uses_smart_model_trait() 25 | { 26 | $user = new User(); 27 | 28 | $this->assertArraySubset(class_uses($user), ['SmartModel']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/FieldLabelTest.php: -------------------------------------------------------------------------------- 1 | getSmartFields()->firstWhere('name', 'sku'); 21 | $this->assertEquals($field->label, 'Sku Number'); 22 | } 23 | 24 | /** @test */ 25 | public function it_displays_proper_label_on_validation_errors_bag() 26 | { 27 | try { 28 | $product = new Product(); 29 | $product->save(); 30 | } catch (ValidationException $e) { 31 | $this->assertContains('Sku Number', $e->validator->errors()->first('sku')); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/FieldRulesTest.php: -------------------------------------------------------------------------------- 1 | getSmartFields()->firstWhere('name', 'accepted_field'); 21 | $this->assertEquals($field->rules, ['accepted']); 22 | } 23 | 24 | /** @test */ 25 | public function setup_activeUrl() 26 | { 27 | $model = new OhRule(); 28 | 29 | $field = $model->getSmartFields()->firstWhere('name', 'activeUrl_field'); 30 | $this->assertEquals($field->rules, ['active_url']); 31 | } 32 | 33 | /** @test */ 34 | public function setup_afterOrEqual() 35 | { 36 | $model = new OhRule(); 37 | 38 | $field = $model->getSmartFields()->firstWhere('name', 'afterOrEqual_field'); 39 | $this->assertEquals($field->rules, ['after_or_equal:2018-12-31']); 40 | } 41 | 42 | /** @test */ 43 | public function setup_alpha() 44 | { 45 | $model = new OhRule(); 46 | 47 | $field = $model->getSmartFields()->firstWhere('name', 'alpha_field'); 48 | $this->assertEquals($field->rules, ['alpha']); 49 | } 50 | 51 | /** @test */ 52 | public function setup_alphaDash() 53 | { 54 | $model = new OhRule(); 55 | 56 | $field = $model->getSmartFields()->firstWhere('name', 'alphaDash_field'); 57 | $this->assertEquals($field->rules, ['alpha_dash']); 58 | } 59 | 60 | /** @test */ 61 | public function setup_alphaNum() 62 | { 63 | $model = new OhRule(); 64 | 65 | $field = $model->getSmartFields()->firstWhere('name', 'alphaNum_field'); 66 | $this->assertEquals($field->rules, ['alpha_num']); 67 | } 68 | 69 | /** @test */ 70 | public function setup_array() 71 | { 72 | $model = new OhRule(); 73 | 74 | $field = $model->getSmartFields()->firstWhere('name', 'array_field'); 75 | $this->assertEquals($field->rules, ['array']); 76 | } 77 | 78 | /** @test */ 79 | public function setup_bail() 80 | { 81 | $model = new OhRule(); 82 | 83 | $field = $model->getSmartFields()->firstWhere('name', 'bail_field'); 84 | $this->assertEquals($field->rules, ['bail']); 85 | } 86 | 87 | /** @test */ 88 | public function setup_before() 89 | { 90 | $model = new OhRule(); 91 | 92 | $field = $model->getSmartFields()->firstWhere('name', 'before_field'); 93 | $this->assertEquals($field->rules, ['before:2018-12-31']); 94 | } 95 | 96 | /** @test */ 97 | public function setup_beforeOrEqual() 98 | { 99 | $model = new OhRule(); 100 | 101 | $field = $model->getSmartFields()->firstWhere('name', 'beforeOrEqual_field'); 102 | $this->assertEquals($field->rules, ['before_or_equal:2018-12-31']); 103 | } 104 | 105 | /** @test */ 106 | public function setup_between() 107 | { 108 | $model = new OhRule(); 109 | 110 | $field = $model->getSmartFields()->firstWhere('name', 'between_field'); 111 | $this->assertEquals($field->rules, ['between:1,20']); 112 | } 113 | 114 | /** @test */ 115 | public function setup_confirmed() 116 | { 117 | $model = new OhRule(); 118 | 119 | $field = $model->getSmartFields()->firstWhere('name', 'confirmed_field'); 120 | $this->assertEquals($field->rules, ['confirmed']); 121 | } 122 | 123 | /** @test */ 124 | public function setup_dateEquals() 125 | { 126 | $model = new OhRule(); 127 | 128 | $field = $model->getSmartFields()->firstWhere('name', 'dateEquals_field'); 129 | $this->assertEquals($field->rules, ['date_equals:2018-12-31']); 130 | } 131 | 132 | /** @test */ 133 | public function setup_dateFormat() 134 | { 135 | $model = new OhRule(); 136 | 137 | $field = $model->getSmartFields()->firstWhere('name', 'dateFormat_field'); 138 | $this->assertEquals($field->rules, ['date_format:Y-m-d']); 139 | } 140 | 141 | /** @test */ 142 | public function setup_different() 143 | { 144 | $model = new OhRule(); 145 | 146 | $field = $model->getSmartFields()->firstWhere('name', 'different_field'); 147 | $this->assertEquals($field->rules, ['different:somefield']); 148 | } 149 | 150 | /** @test */ 151 | public function setup_digits() 152 | { 153 | $model = new OhRule(); 154 | 155 | $field = $model->getSmartFields()->firstWhere('name', 'digits_field'); 156 | $this->assertEquals($field->rules, ['digits:3']); 157 | } 158 | 159 | /** @test */ 160 | public function setup_digitsBetween() 161 | { 162 | $model = new OhRule(); 163 | 164 | $field = $model->getSmartFields()->firstWhere('name', 'digitsBetween_field'); 165 | $this->assertEquals($field->rules, ['digits_between:5,9']); 166 | } 167 | 168 | /** @test */ 169 | public function it_does_not_support_dimensions() 170 | { 171 | $this->expectException(\Exception::class); 172 | 173 | Field::make('dimensions_field')->dimensions(); 174 | } 175 | 176 | /** @test */ 177 | public function it_does_not_support_distinct() 178 | { 179 | $this->expectException(\Exception::class); 180 | 181 | Field::make('distinct_field')->distinct(); 182 | } 183 | 184 | /** @test */ 185 | public function setup_exists() 186 | { 187 | $model = new OhRule(); 188 | 189 | $field = $model->getSmartFields()->firstWhere('name', 'exists_field'); 190 | 191 | $rule = $field->rules[0]->__toString(); 192 | 193 | $this->assertEquals($rule, 'exists:users,exists_field'); 194 | } 195 | 196 | /** @test */ 197 | public function setup_exists_with_column() 198 | { 199 | $model = new OhRule(); 200 | 201 | $field = $model->getSmartFields()->firstWhere('name', 'exists_field_with_column'); 202 | 203 | $rule = $field->rules[0]->__toString(); 204 | 205 | $this->assertEquals($rule, 'exists:users,username'); 206 | } 207 | 208 | /** @test */ 209 | public function setup_exists_full() 210 | { 211 | $model = new OhRule(); 212 | 213 | $field = $model->getSmartFields()->firstWhere('name', 'exists_field_full'); 214 | 215 | $rule = $field->rules[0]; 216 | 217 | $this->assertEquals($rule, 'exists:users,username'); 218 | $this->assertTrue(is_a($rule, 'Illuminate\Validation\Rules\Exists')); 219 | } 220 | 221 | /** @test */ 222 | public function setup_file() 223 | { 224 | $this->expectException(\Exception::class); 225 | 226 | Field::make('file_field')->file(); 227 | } 228 | 229 | /** @test */ 230 | public function setup_filled() 231 | { 232 | $model = new OhRule(); 233 | 234 | $field = $model->getSmartFields()->firstWhere('name', 'filled_field'); 235 | $this->assertEquals($field->rules, ['filled']); 236 | } 237 | 238 | /** @test */ 239 | public function setup_gt() 240 | { 241 | $model = new OhRule(); 242 | 243 | $field = $model->getSmartFields()->firstWhere('name', 'gt_field'); 244 | $this->assertEquals($field->rules, ['gt:5']); 245 | } 246 | 247 | /** @test */ 248 | public function setup_gte() 249 | { 250 | $model = new OhRule(); 251 | 252 | $field = $model->getSmartFields()->firstWhere('name', 'gte_field'); 253 | $this->assertEquals($field->rules, ['gte:6']); 254 | } 255 | 256 | /** @test */ 257 | public function setup_image() 258 | { 259 | $this->expectException(\Exception::class); 260 | 261 | Field::make('image_field')->image(); 262 | } 263 | 264 | /** @test */ 265 | public function setup_in() 266 | { 267 | $model = new OhRule(); 268 | 269 | $field = $model->getSmartFields()->firstWhere('name', 'in_field'); 270 | 271 | $rule = $field->rules[0]->__toString(); 272 | 273 | $this->assertEquals($rule, 'in:"D","E","F"'); 274 | } 275 | 276 | /** @test */ 277 | public function setup_inArray() 278 | { 279 | $model = new OhRule(); 280 | 281 | $field = $model->getSmartFields()->firstWhere('name', 'inArray_field'); 282 | 283 | $this->assertEquals($field->rules, ['in_array:letters']); 284 | } 285 | 286 | /** @test */ 287 | public function setup_ip() 288 | { 289 | $model = new OhRule(); 290 | 291 | $field = $model->getSmartFields()->firstWhere('name', 'ip_field'); 292 | $this->assertEquals($field->rules, ['ip']); 293 | } 294 | 295 | /** @test */ 296 | public function setup_ipv4() 297 | { 298 | $model = new OhRule(); 299 | 300 | $field = $model->getSmartFields()->firstWhere('name', 'ipv4_field'); 301 | $this->assertEquals($field->rules, ['ipv4']); 302 | } 303 | 304 | /** @test */ 305 | public function setup_ipv6() 306 | { 307 | $model = new OhRule(); 308 | 309 | $field = $model->getSmartFields()->firstWhere('name', 'ipv6_field'); 310 | $this->assertEquals($field->rules, ['ipv6']); 311 | } 312 | 313 | /** @test */ 314 | public function setup_lt() 315 | { 316 | $model = new OhRule(); 317 | 318 | $field = $model->getSmartFields()->firstWhere('name', 'lt_field'); 319 | $this->assertEquals($field->rules, ['lt:10']); 320 | } 321 | 322 | /** @test */ 323 | public function setup_lte() 324 | { 325 | $model = new OhRule(); 326 | 327 | $field = $model->getSmartFields()->firstWhere('name', 'lte_field'); 328 | $this->assertEquals($field->rules, ['lte:11']); 329 | } 330 | 331 | /** @test */ 332 | public function setup_max() 333 | { 334 | $model = new OhRule(); 335 | 336 | $field = $model->getSmartFields()->firstWhere('name', 'max_field'); 337 | $this->assertEquals($field->rules, ['max:12']); 338 | } 339 | 340 | /** @test */ 341 | public function setup_mimeTypes() 342 | { 343 | $this->expectException(\Exception::class); 344 | 345 | Field::make('mimeTypes_field')->mimeTypes(); 346 | } 347 | 348 | /** @test */ 349 | public function setup_mimes() 350 | { 351 | $this->expectException(\Exception::class); 352 | 353 | Field::make('mimes_field')->mimes(); 354 | } 355 | 356 | /** @test */ 357 | public function setup_min() 358 | { 359 | $model = new OhRule(); 360 | 361 | $field = $model->getSmartFields()->firstWhere('name', 'min_field'); 362 | $this->assertEquals($field->rules, ['min:13']); 363 | } 364 | 365 | /** @test */ 366 | public function setup_notIn() 367 | { 368 | $model = new OhRule(); 369 | 370 | $field = $model->getSmartFields()->firstWhere('name', 'notIn_field'); 371 | 372 | $rule = $field->rules[0]->__toString(); 373 | 374 | $this->assertEquals($rule, 'not_in:"Mary","May"'); 375 | } 376 | 377 | /** @test */ 378 | public function setup_notRegex() 379 | { 380 | $model = new OhRule(); 381 | 382 | $field = $model->getSmartFields()->firstWhere('name', 'notRegex_field'); 383 | $this->assertEquals($field->rules, ['not_regex:/w+/']); 384 | } 385 | 386 | /** @test */ 387 | public function setup_numeric() 388 | { 389 | $model = new OhRule(); 390 | 391 | $field = $model->getSmartFields()->firstWhere('name', 'numeric_field'); 392 | $this->assertEquals($field->rules, ['numeric']); 393 | } 394 | 395 | /** @test */ 396 | public function setup_present() 397 | { 398 | $model = new OhRule(); 399 | 400 | $field = $model->getSmartFields()->firstWhere('name', 'present_field'); 401 | $this->assertEquals($field->rules, ['present']); 402 | } 403 | 404 | /** @test */ 405 | public function setup_regex() 406 | { 407 | $model = new OhRule(); 408 | 409 | $field = $model->getSmartFields()->firstWhere('name', 'regex_field'); 410 | $this->assertEquals($field->rules, ['regex:/{abc}/']); 411 | } 412 | 413 | /** @test */ 414 | public function setup_required() 415 | { 416 | $model = new OhRule(); 417 | 418 | $field = $model->getSmartFields()->firstWhere('name', 'required_field'); 419 | $this->assertEquals($field->rules, ['required']); 420 | } 421 | 422 | /** @test */ 423 | public function setup_requiredIf() 424 | { 425 | $model = new OhRule(); 426 | 427 | $field = $model->getSmartFields()->firstWhere('name', 'requiredIf_field'); 428 | $this->assertEquals($field->rules, ['required_if:status,ACTIVE']); 429 | } 430 | 431 | /** @test */ 432 | public function setup_requiredUnless() 433 | { 434 | $model = new OhRule(); 435 | 436 | $field = $model->getSmartFields()->firstWhere('name', 'requiredUnless_field'); 437 | $this->assertEquals($field->rules, ['required_unless:status,ARCHIVED']); 438 | } 439 | 440 | /** @test */ 441 | public function setup_requiredWith() 442 | { 443 | $model = new OhRule(); 444 | 445 | $field = $model->getSmartFields()->firstWhere('name', 'requiredWith_field'); 446 | $this->assertEquals($field->rules, ['required_with:a,b,c,d']); 447 | } 448 | 449 | /** @test */ 450 | public function setup_requiredWithAll() 451 | { 452 | $model = new OhRule(); 453 | 454 | $field = $model->getSmartFields()->firstWhere('name', 'requiredWithAll_field'); 455 | $this->assertEquals($field->rules, ['required_with_all:e,f,g,h']); 456 | } 457 | 458 | /** @test */ 459 | public function setup_requiredWithout() 460 | { 461 | $model = new OhRule(); 462 | 463 | $field = $model->getSmartFields()->firstWhere('name', 'requiredWithout_field'); 464 | $this->assertEquals($field->rules, ['required_without:w,x,y,z']); 465 | } 466 | 467 | /** @test */ 468 | public function setup_requiredWithoutAll() 469 | { 470 | $model = new OhRule(); 471 | 472 | $field = $model->getSmartFields()->firstWhere('name', 'requiredWithoutAll_field'); 473 | $this->assertEquals($field->rules, ['required_without_all:l,m,n,o']); 474 | } 475 | 476 | /** @test */ 477 | public function setup_same() 478 | { 479 | $model = new OhRule(); 480 | 481 | $field = $model->getSmartFields()->firstWhere('name', 'same_field'); 482 | $this->assertEquals($field->rules, ['same:password_confirm']); 483 | } 484 | 485 | /** @test */ 486 | public function setup_timezone() 487 | { 488 | $model = new OhRule(); 489 | 490 | $field = $model->getSmartFields()->firstWhere('name', 'timezone_field'); 491 | $this->assertEquals($field->rules, ['timezone']); 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /tests/FieldTypesTest.php: -------------------------------------------------------------------------------- 1 | getSmartFields()->firstWhere('name', 'bigIncrements_field'); 20 | 21 | $this->assertEquals($field->type, 'bigIncrements'); 22 | $this->assertEquals($field->cast, 'integer'); 23 | $this->assertEquals($field->rules, []); 24 | } 25 | 26 | /** @test */ 27 | public function setup_bigInteger() 28 | { 29 | $model = new BigBang(); 30 | $field = $model->getSmartFields()->firstWhere('name', 'bigInteger_field'); 31 | 32 | $this->assertEquals($field->type, 'bigInteger'); 33 | $this->assertEquals($field->cast, 'integer'); 34 | $this->assertEquals($field->rules, ['numeric']); 35 | } 36 | 37 | /** @test */ 38 | public function setup_binary() 39 | { 40 | $model = new BigBang(); 41 | $field = $model->getSmartFields()->firstWhere('name', 'binary_field'); 42 | 43 | $this->assertEquals($field->type, 'binary'); 44 | $this->assertEquals($field->cast, null); 45 | $this->assertEquals($field->rules, []); 46 | } 47 | 48 | /** @test */ 49 | public function setup_boolean() 50 | { 51 | $model = new BigBang(); 52 | $field = $model->getSmartFields()->firstWhere('name', 'boolean_field'); 53 | 54 | $this->assertEquals($field->type, 'boolean'); 55 | $this->assertEquals($field->cast, 'boolean'); 56 | $this->assertEquals($field->rules, ['boolean']); 57 | } 58 | 59 | /** @test */ 60 | public function setup_char() 61 | { 62 | $model = new BigBang(); 63 | $field = $model->getSmartFields()->firstWhere('name', 'char_field'); 64 | 65 | $this->assertEquals($field->type, 'char'); 66 | $this->assertEquals($field->cast, null); 67 | $this->assertEquals($field->rules, []); 68 | } 69 | 70 | /** @test */ 71 | public function setup_date() 72 | { 73 | $model = new BigBang(); 74 | $field = $model->getSmartFields()->firstWhere('name', 'date_field'); 75 | 76 | $this->assertEquals($field->type, 'date'); 77 | $this->assertEquals($field->cast, 'date'); 78 | $this->assertEquals($field->rules, []); 79 | } 80 | 81 | /** @test */ 82 | public function setup_dateTime() 83 | { 84 | $model = new BigBang(); 85 | $field = $model->getSmartFields()->firstWhere('name', 'dateTime_field'); 86 | 87 | $this->assertEquals($field->type, 'dateTime'); 88 | $this->assertEquals($field->cast, 'datetime'); 89 | $this->assertEquals($field->rules, []); 90 | } 91 | 92 | /** @test */ 93 | public function setup_dateTimeTz() 94 | { 95 | $model = new BigBang(); 96 | $field = $model->getSmartFields()->firstWhere('name', 'dateTimeTz_field'); 97 | 98 | $this->assertEquals($field->type, 'dateTimeTz'); 99 | $this->assertEquals($field->cast, 'datetime'); 100 | $this->assertEquals($field->rules, []); 101 | } 102 | 103 | /** @test */ 104 | public function setup_decimal() 105 | { 106 | $model = new BigBang(); 107 | $field = $model->getSmartFields()->firstWhere('name', 'decimal_field'); 108 | 109 | $this->assertEquals($field->type, 'decimal'); 110 | $this->assertEquals($field->cast, 'double'); 111 | $this->assertEquals($field->rules, ['numeric']); 112 | } 113 | 114 | /** @test */ 115 | public function setup_double() 116 | { 117 | $model = new BigBang(); 118 | $field = $model->getSmartFields()->firstWhere('name', 'double_field'); 119 | 120 | $this->assertEquals($field->type, 'double'); 121 | $this->assertEquals($field->cast, 'double'); 122 | $this->assertEquals($field->rules, ['numeric']); 123 | } 124 | 125 | /** @test */ 126 | public function setup_email() 127 | { 128 | $model = new BigBang(); 129 | $field = $model->getSmartFields()->firstWhere('name', 'email_field'); 130 | 131 | $this->assertEquals($field->type, 'string'); 132 | $this->assertEquals($field->cast, null); 133 | $this->assertEquals($field->rules, ['email']); 134 | } 135 | 136 | /** @test */ 137 | public function setup_enum() 138 | { 139 | $model = new BigBang(); 140 | $field = $model->getSmartFields()->firstWhere('name', 'enum_field'); 141 | 142 | $this->assertEquals($field->type, 'enum'); 143 | $this->assertEquals($field->cast, null); 144 | $this->assertEquals($field->rules, ['in:A,B,C']); 145 | } 146 | 147 | /** @test */ 148 | public function setup_float() 149 | { 150 | $model = new BigBang(); 151 | $field = $model->getSmartFields()->firstWhere('name', 'float_field'); 152 | 153 | $this->assertEquals($field->type, 'float'); 154 | $this->assertEquals($field->cast, 'float'); 155 | $this->assertEquals($field->rules, ['numeric']); 156 | } 157 | 158 | /** @test */ 159 | public function setup_geometry() 160 | { 161 | $model = new BigBang(); 162 | $field = $model->getSmartFields()->firstWhere('name', 'geometry_field'); 163 | 164 | $this->assertEquals($field->type, 'geometry'); 165 | $this->assertEquals($field->cast, null); 166 | $this->assertEquals($field->rules, []); 167 | } 168 | 169 | /** @test */ 170 | public function setup_geometryCollection() 171 | { 172 | $model = new BigBang(); 173 | $field = $model->getSmartFields()->firstWhere('name', 'geometryCollection_field'); 174 | 175 | $this->assertEquals($field->type, 'geometryCollection'); 176 | $this->assertEquals($field->cast, null); 177 | $this->assertEquals($field->rules, []); 178 | } 179 | 180 | /** @test */ 181 | public function setup_increments() 182 | { 183 | $model = new BigBang(); 184 | $field = $model->getSmartFields()->firstWhere('name', 'increments_field'); 185 | 186 | $this->assertEquals($field->type, 'increments'); 187 | $this->assertEquals($field->cast, 'integer'); 188 | $this->assertEquals($field->rules, []); 189 | } 190 | 191 | /** @test */ 192 | public function setup_integer() 193 | { 194 | $model = new BigBang(); 195 | $field = $model->getSmartFields()->firstWhere('name', 'integer_field'); 196 | 197 | $this->assertEquals($field->type, 'integer'); 198 | $this->assertEquals($field->cast, 'integer'); 199 | $this->assertEquals($field->rules, ['numeric']); 200 | } 201 | 202 | /** @test */ 203 | public function setup_ipAddress() 204 | { 205 | $model = new BigBang(); 206 | $field = $model->getSmartFields()->firstWhere('name', 'ipAddress_field'); 207 | 208 | $this->assertEquals($field->type, 'ipAddress'); 209 | $this->assertEquals($field->cast, null); 210 | $this->assertEquals($field->rules, ['ip']); 211 | } 212 | 213 | /** @test */ 214 | public function setup_json() 215 | { 216 | $model = new BigBang(); 217 | $field = $model->getSmartFields()->firstWhere('name', 'json_field'); 218 | 219 | $this->assertEquals($field->type, 'json'); 220 | $this->assertEquals($field->cast, 'array'); 221 | $this->assertEquals($field->rules, ['array']); 222 | } 223 | 224 | /** @test */ 225 | public function setup_jsonb() 226 | { 227 | $model = new BigBang(); 228 | $field = $model->getSmartFields()->firstWhere('name', 'jsonb_field'); 229 | 230 | $this->assertEquals($field->type, 'jsonb'); 231 | $this->assertEquals($field->cast, 'array'); 232 | $this->assertEquals($field->rules, ['array']); 233 | } 234 | 235 | /** @test */ 236 | public function setup_lineString() 237 | { 238 | $model = new BigBang(); 239 | $field = $model->getSmartFields()->firstWhere('name', 'lineString_field'); 240 | 241 | $this->assertEquals($field->type, 'lineString'); 242 | $this->assertEquals($field->cast, null); 243 | $this->assertEquals($field->rules, []); 244 | } 245 | 246 | /** @test */ 247 | public function setup_longText() 248 | { 249 | $model = new BigBang(); 250 | $field = $model->getSmartFields()->firstWhere('name', 'longText_field'); 251 | 252 | $this->assertEquals($field->type, 'longText'); 253 | $this->assertEquals($field->cast, null); 254 | $this->assertEquals($field->rules, []); 255 | } 256 | 257 | /** @test */ 258 | public function setup_macAddress() 259 | { 260 | $model = new BigBang(); 261 | $field = $model->getSmartFields()->firstWhere('name', 'macAddress_field'); 262 | 263 | $this->assertEquals($field->type, 'macAddress'); 264 | $this->assertEquals($field->cast, null); 265 | $this->assertEquals($field->rules, []); 266 | } 267 | 268 | /** @test */ 269 | public function setup_mediumIncrements() 270 | { 271 | $model = new BigBang(); 272 | $field = $model->getSmartFields()->firstWhere('name', 'mediumIncrements_field'); 273 | 274 | $this->assertEquals($field->type, 'mediumIncrements'); 275 | $this->assertEquals($field->cast, 'integer'); 276 | $this->assertEquals($field->rules, []); 277 | } 278 | 279 | /** @test */ 280 | public function setup_mediumInteger() 281 | { 282 | $model = new BigBang(); 283 | $field = $model->getSmartFields()->firstWhere('name', 'mediumInteger_field'); 284 | 285 | $this->assertEquals($field->type, 'mediumInteger'); 286 | $this->assertEquals($field->cast, 'integer'); 287 | $this->assertEquals($field->rules, ['numeric']); 288 | } 289 | 290 | /** @test */ 291 | public function setup_mediumText() 292 | { 293 | $model = new BigBang(); 294 | $field = $model->getSmartFields()->firstWhere('name', 'mediumText_field'); 295 | 296 | $this->assertEquals($field->type, 'mediumText'); 297 | $this->assertEquals($field->cast, null); 298 | $this->assertEquals($field->rules, []); 299 | } 300 | 301 | /** @test */ 302 | public function it_does_not_support_morphs() 303 | { 304 | $this->expectException(\Exception::class); 305 | Field::make('morphs_field')->morphs(); 306 | } 307 | 308 | /** @test */ 309 | public function setup_multiLineString() 310 | { 311 | $model = new BigBang(); 312 | $field = $model->getSmartFields()->firstWhere('name', 'multiLineString_field'); 313 | 314 | $this->assertEquals($field->type, 'multiLineString'); 315 | $this->assertEquals($field->cast, null); 316 | $this->assertEquals($field->rules, []); 317 | } 318 | 319 | /** @test */ 320 | public function setup_multiPoint() 321 | { 322 | $model = new BigBang(); 323 | $field = $model->getSmartFields()->firstWhere('name', 'multiPoint_field'); 324 | 325 | $this->assertEquals($field->type, 'multiPoint'); 326 | $this->assertEquals($field->cast, null); 327 | $this->assertEquals($field->rules, []); 328 | } 329 | 330 | /** @test */ 331 | public function setup_multiPolygon() 332 | { 333 | $model = new BigBang(); 334 | $field = $model->getSmartFields()->firstWhere('name', 'multiPolygon_field'); 335 | 336 | $this->assertEquals($field->type, 'multiPolygon'); 337 | $this->assertEquals($field->cast, null); 338 | $this->assertEquals($field->rules, []); 339 | } 340 | 341 | /** @test */ 342 | public function setup_nullable() 343 | { 344 | $model = new BigBang(); 345 | $field = $model->getSmartFields()->firstWhere('name', 'nullable_field'); 346 | 347 | $this->assertEquals($field->type, null); 348 | $this->assertEquals($field->cast, null); 349 | $this->assertEquals($field->rules, ['nullable']); 350 | } 351 | 352 | /** @test */ 353 | public function setup_point() 354 | { 355 | $model = new BigBang(); 356 | $field = $model->getSmartFields()->firstWhere('name', 'point_field'); 357 | 358 | $this->assertEquals($field->type, 'point'); 359 | $this->assertEquals($field->cast, null); 360 | $this->assertEquals($field->rules, []); 361 | } 362 | 363 | /** @test */ 364 | public function setup_polygon() 365 | { 366 | $model = new BigBang(); 367 | $field = $model->getSmartFields()->firstWhere('name', 'polygon_field'); 368 | 369 | $this->assertEquals($field->type, 'polygon'); 370 | $this->assertEquals($field->cast, null); 371 | $this->assertEquals($field->rules, []); 372 | } 373 | 374 | /** @test */ 375 | public function setup_smallIncrements() 376 | { 377 | $model = new BigBang(); 378 | $field = $model->getSmartFields()->firstWhere('name', 'smallIncrements_field'); 379 | 380 | $this->assertEquals($field->type, 'smallIncrements'); 381 | $this->assertEquals($field->cast, 'integer'); 382 | $this->assertEquals($field->rules, []); 383 | } 384 | 385 | /** @test */ 386 | public function setup_smallInteger() 387 | { 388 | $model = new BigBang(); 389 | $field = $model->getSmartFields()->firstWhere('name', 'smallInteger_field'); 390 | 391 | $this->assertEquals($field->type, 'smallInteger'); 392 | $this->assertEquals($field->cast, 'integer'); 393 | $this->assertEquals($field->rules, ['numeric']); 394 | } 395 | 396 | /** @test */ 397 | public function setup_softDeletes() 398 | { 399 | $model = new BigBang(); 400 | $field = $model->getSmartFields()->firstWhere('name', 'softDeletes_field'); 401 | 402 | $this->assertEquals($field->type, 'softDeletes'); 403 | $this->assertEquals($field->cast, 'datetime'); 404 | $this->assertEquals($field->rules, []); 405 | } 406 | 407 | /** @test */ 408 | public function setup_softDeletesTz() 409 | { 410 | $model = new BigBang(); 411 | $field = $model->getSmartFields()->firstWhere('name', 'softDeletesTz_field'); 412 | 413 | $this->assertEquals($field->type, 'softDeletesTz'); 414 | $this->assertEquals($field->cast, 'datetime'); 415 | $this->assertEquals($field->rules, []); 416 | } 417 | 418 | /** @test */ 419 | public function setup_string() 420 | { 421 | $model = new BigBang(); 422 | $field = $model->getSmartFields()->firstWhere('name', 'string_field'); 423 | 424 | $this->assertEquals($field->type, 'string'); 425 | $this->assertEquals($field->cast, null); 426 | $this->assertEquals($field->rules, []); 427 | } 428 | 429 | /** @test */ 430 | public function setup_text() 431 | { 432 | $model = new BigBang(); 433 | $field = $model->getSmartFields()->firstWhere('name', 'text_field'); 434 | 435 | $this->assertEquals($field->type, 'text'); 436 | $this->assertEquals($field->cast, null); 437 | $this->assertEquals($field->rules, []); 438 | } 439 | 440 | /** @test */ 441 | public function setup_time() 442 | { 443 | $model = new BigBang(); 444 | $field = $model->getSmartFields()->firstWhere('name', 'time_field'); 445 | 446 | $this->assertEquals($field->type, 'time'); 447 | $this->assertEquals($field->cast, null); 448 | $this->assertEquals($field->rules, []); 449 | } 450 | 451 | /** @test */ 452 | public function setup_timeTz() 453 | { 454 | $model = new BigBang(); 455 | $field = $model->getSmartFields()->firstWhere('name', 'timeTz_field'); 456 | 457 | $this->assertEquals($field->type, 'timeTz'); 458 | $this->assertEquals($field->cast, null); 459 | $this->assertEquals($field->rules, []); 460 | } 461 | 462 | /** @test */ 463 | public function setup_timestamp() 464 | { 465 | $model = new BigBang(); 466 | $field = $model->getSmartFields()->firstWhere('name', 'timestamp_field'); 467 | 468 | $this->assertEquals($field->type, 'timestamp'); 469 | $this->assertEquals($field->cast, 'datetime'); 470 | $this->assertEquals($field->rules, []); 471 | } 472 | 473 | /** @test */ 474 | public function setup_timestampTz() 475 | { 476 | $model = new BigBang(); 477 | $field = $model->getSmartFields()->firstWhere('name', 'timestampTz_field'); 478 | 479 | $this->assertEquals($field->type, 'timestampTz'); 480 | $this->assertEquals($field->cast, 'datetime'); 481 | $this->assertEquals($field->rules, []); 482 | } 483 | 484 | /** @test */ 485 | public function it_does_not_support_timestampsTz() 486 | { 487 | $this->expectException(\Exception::class); 488 | Field::make('timestampsTz_field')->timestampsTz(); 489 | } 490 | 491 | /** @test */ 492 | public function it_does_not_support_timestamps() 493 | { 494 | $this->expectException(\Exception::class); 495 | Field::make('timestamps_field')->timestamps(); 496 | } 497 | 498 | /** @test */ 499 | public function setup_tinyIncrements() 500 | { 501 | $model = new BigBang(); 502 | $field = $model->getSmartFields()->firstWhere('name', 'tinyIncrements_field'); 503 | 504 | $this->assertEquals($field->type, 'tinyIncrements'); 505 | $this->assertEquals($field->cast, 'integer'); 506 | $this->assertEquals($field->rules, []); 507 | } 508 | 509 | /** @test */ 510 | public function setup_tinyInteger() 511 | { 512 | $model = new BigBang(); 513 | $field = $model->getSmartFields()->firstWhere('name', 'tinyInteger_field'); 514 | 515 | $this->assertEquals($field->type, 'tinyInteger'); 516 | $this->assertEquals($field->cast, 'integer'); 517 | $this->assertEquals($field->rules, ['numeric']); 518 | } 519 | 520 | /** @test */ 521 | public function setup_unique() 522 | { 523 | $model = new BigBang(); 524 | $model->id = 1; 525 | $field = $model->getSmartFields()->firstWhere('name', 'unique_field'); 526 | 527 | $rule = $field->getValidationRules($model)[0]->__toString(); 528 | 529 | $this->assertEquals($field->type, null); 530 | $this->assertEquals($field->cast, null); 531 | $this->assertEquals($rule, 'unique:big_bangs,unique_field,"1",id'); 532 | } 533 | 534 | /** @test */ 535 | public function setup_unsignedBigInteger() 536 | { 537 | $model = new BigBang(); 538 | $field = $model->getSmartFields()->firstWhere('name', 'unsignedBigInteger_field'); 539 | 540 | $this->assertEquals($field->type, 'unsignedBigInteger'); 541 | $this->assertEquals($field->cast, 'integer'); 542 | $this->assertEquals($field->rules, ['numeric']); 543 | } 544 | 545 | /** @test */ 546 | public function setup_unsignedDecimal() 547 | { 548 | $model = new BigBang(); 549 | $field = $model->getSmartFields()->firstWhere('name', 'unsignedDecimal_field'); 550 | 551 | $this->assertEquals($field->type, 'unsignedDecimal'); 552 | $this->assertEquals($field->cast, 'double'); 553 | $this->assertEquals($field->rules, ['numeric']); 554 | } 555 | 556 | /** @test */ 557 | public function setup_unsignedInteger() 558 | { 559 | $model = new BigBang(); 560 | $field = $model->getSmartFields()->firstWhere('name', 'unsignedInteger_field'); 561 | 562 | $this->assertEquals($field->type, 'unsignedInteger'); 563 | $this->assertEquals($field->cast, 'integer'); 564 | $this->assertEquals($field->rules, ['numeric']); 565 | } 566 | 567 | /** @test */ 568 | public function setup_unsignedMediumInteger() 569 | { 570 | $model = new BigBang(); 571 | $field = $model->getSmartFields()->firstWhere('name', 'unsignedMediumInteger_field'); 572 | 573 | $this->assertEquals($field->type, 'unsignedMediumInteger'); 574 | $this->assertEquals($field->cast, 'integer'); 575 | $this->assertEquals($field->rules, ['numeric']); 576 | } 577 | 578 | /** @test */ 579 | public function setup_unsignedSmallInteger() 580 | { 581 | $model = new BigBang(); 582 | $field = $model->getSmartFields()->firstWhere('name', 'unsignedSmallInteger_field'); 583 | 584 | $this->assertEquals($field->type, 'unsignedSmallInteger'); 585 | $this->assertEquals($field->cast, 'integer'); 586 | $this->assertEquals($field->rules, ['numeric']); 587 | } 588 | 589 | /** @test */ 590 | public function setup_unsignedTinyInteger() 591 | { 592 | $model = new BigBang(); 593 | $field = $model->getSmartFields()->firstWhere('name', 'unsignedTinyInteger_field'); 594 | 595 | $this->assertEquals($field->type, 'unsignedTinyInteger'); 596 | $this->assertEquals($field->cast, 'integer'); 597 | $this->assertEquals($field->rules, ['numeric']); 598 | } 599 | 600 | /** @test */ 601 | public function setup_slug() 602 | { 603 | $model = new BigBang(); 604 | $field = $model->getSmartFields()->firstWhere('name', 'slug_field'); 605 | 606 | $this->assertEquals($field->type, 'string'); 607 | $this->assertEquals($field->cast, null); 608 | $this->assertEquals($field->rules, ['regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/']); 609 | } 610 | 611 | /** @test */ 612 | public function setup_url() 613 | { 614 | $model = new BigBang(); 615 | $field = $model->getSmartFields()->firstWhere('name', 'url_field'); 616 | 617 | $this->assertEquals($field->type, 'string'); 618 | $this->assertEquals($field->cast, null); 619 | $this->assertEquals($field->rules, ['url']); 620 | } 621 | 622 | /** @test */ 623 | public function setup_uuid() 624 | { 625 | $model = new BigBang(); 626 | $field = $model->getSmartFields()->firstWhere('name', 'uuid_field'); 627 | 628 | $this->assertEquals($field->type, 'uuid'); 629 | $this->assertEquals($field->cast, null); 630 | $this->assertEquals($field->rules, []); 631 | } 632 | } 633 | -------------------------------------------------------------------------------- /tests/GeneratorTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($generator->joinTree($input), $expected); 22 | } 23 | 24 | /** @test */ 25 | public function it_joins_sections() 26 | { 27 | $generator = new Generator(); 28 | 29 | $section1 = ['a', 'b', 'c']; 30 | $section2 = ['d', 'e', 'f']; 31 | $expected = ['a', 'b', 'c', '', 'd', 'e', 'f']; 32 | 33 | $this->assertEquals($generator->joinSections([$section1, $section2]), $expected); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/MigrationGeneratorTest.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'Deiucanta\Smart\Tests\Models\Product' => [ 23 | 'created' => [ 24 | 'id' => [ 25 | 'type' => 'increments', 26 | ], 27 | 'created_at' => [ 28 | 'type' => 'timestamp', 29 | 'nullable' => true, 30 | ], 31 | 'updated_at' => [ 32 | 'type' => 'timestamp', 33 | 'nullable' => true, 34 | ], 35 | ], 36 | ], 37 | ], 38 | ]; 39 | 40 | $down = [ 41 | 'deleted' => [ 42 | 'Deiucanta\Smart\Tests\Models\Product' => true, 43 | ], 44 | ]; 45 | 46 | $this->assertEquals($generator->print($up, $down, 'Testing'), $expected); 47 | } 48 | 49 | /** @test */ 50 | public function it_generates_the_proper_snapshot_2() 51 | { 52 | $generator = new MigrationGenerator(); 53 | $expected = file_get_contents(__DIR__.'/Snapshots/migration-2.txt'); 54 | 55 | $up = [ 56 | 'updated' => [ 57 | 'Deiucanta\Smart\Tests\Models\Product' => [ 58 | 'updated' => [ 59 | 'name' => [ 60 | 'type' => 'string', 61 | 'nullable' => true, 62 | 'default' => 'John Doe', 63 | ], 64 | 'price' => [ 65 | 'type' => 'decimal', 66 | 'typeArgs' => [5, 2], 67 | 'index' => false, 68 | ], 69 | ], 70 | ], 71 | ], 72 | ]; 73 | 74 | $this->assertEquals($generator->print($up, [], 'Testing'), $expected); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/ModelGeneratorTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($generator->print('Product'), $expected); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/ModelTest.php: -------------------------------------------------------------------------------- 1 | assertArraySubset($product->getGuarded(), ['*', 'sku', 'name']); 23 | } 24 | 25 | /** @test */ 26 | public function it_handles_fillable_attributes() 27 | { 28 | $product = new Product(); 29 | 30 | $this->assertArraySubset($product->getFillable(), ['price', 'description']); 31 | } 32 | 33 | /** @test */ 34 | public function it_handles_default_value() 35 | { 36 | $product = new Product(); 37 | 38 | $this->assertEquals($product->status, 'ACTIVE'); 39 | } 40 | 41 | /** @test */ 42 | public function it_handles_casting() 43 | { 44 | $product = new Product(); 45 | 46 | $this->assertEquals($product->getCasts()['valid_until'], 'date'); 47 | } 48 | 49 | /** @test */ 50 | public function it_validates_when_saving() 51 | { 52 | try { 53 | $product = new Product(); 54 | $product->save(); 55 | } catch (ValidationException $e) { 56 | $this->assertArraySubset(array_keys($e->errors()), ['sku', 'name', 'price']); 57 | } 58 | } 59 | 60 | /** @test */ 61 | public function field_names_must_be_unique() 62 | { 63 | $this->expectException(\Exception::class); 64 | 65 | $model = new Duplicity(); 66 | } 67 | 68 | /** @test */ 69 | public function it_caches_field_definitions() 70 | { 71 | $modelA = new FieldCaching(); 72 | $modelB = new FieldCaching(); 73 | 74 | $this->assertEquals($modelA->getSmartFields(), $modelB->getSmartFields()); 75 | } 76 | 77 | /** @test */ 78 | public function it_caches_a_collection_indexed_by_name() 79 | { 80 | $model = new FieldCaching(); 81 | $fields = $model->getSmartFields(); 82 | 83 | $this->assertInstanceOf(\Illuminate\Support\Collection::class, $fields); 84 | $this->assertEquals($fields->keys()->toArray(), ['id']); 85 | } 86 | 87 | /** @test */ 88 | public function it_removes_unknown_attributes() 89 | { 90 | $model = new Product(); 91 | $model->junkAttribute = 'x'; 92 | $model->removeUnknownAttributes(); 93 | 94 | $this->assertNull($model->junkAttribute); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/Models/BigBang.php: -------------------------------------------------------------------------------- 1 | bigIncrements(), 14 | Field::make('bigInteger_field')->bigInteger(), 15 | Field::make('binary_field')->binary(), 16 | Field::make('boolean_field')->boolean(), 17 | Field::make('char_field')->char(2), 18 | Field::make('date_field')->date(), 19 | Field::make('dateTime_field')->dateTime(), 20 | Field::make('dateTimeTz_field')->dateTimeTz(), 21 | Field::make('decimal_field')->decimal(10, 2), 22 | Field::make('double_field')->double(6, 2), 23 | Field::make('email_field')->email(), 24 | Field::make('enum_field')->enum(['A', 'B', 'C']), 25 | Field::make('float_field')->float(3, 2), 26 | Field::make('geometry_field')->geometry(), 27 | Field::make('geometryCollection_field')->geometryCollection(), 28 | Field::make('increments_field')->increments(), 29 | Field::make('integer_field')->integer(), 30 | Field::make('ipAddress_field')->ipAddress(), 31 | Field::make('json_field')->json(), 32 | Field::make('jsonb_field')->jsonb(), 33 | Field::make('lineString_field')->lineString(), 34 | Field::make('longText_field')->longText(), 35 | Field::make('macAddress_field')->macAddress(), 36 | Field::make('mediumIncrements_field')->mediumIncrements(), 37 | Field::make('mediumInteger_field')->mediumInteger(), 38 | Field::make('mediumText_field')->mediumText(), 39 | Field::make('multiLineString_field')->multiLineString(), 40 | Field::make('multiPoint_field')->multiPoint(), 41 | Field::make('multiPolygon_field')->multiPolygon(), 42 | Field::make('nullable_field')->nullable(), 43 | Field::make('point_field')->point(), 44 | Field::make('polygon_field')->polygon(), 45 | Field::make('smallIncrements_field')->smallIncrements(), 46 | Field::make('smallInteger_field')->smallInteger(), 47 | Field::make('softDeletes_field')->softDeletes(), 48 | Field::make('softDeletesTz_field')->softDeletesTz(), 49 | Field::make('string_field')->string(20), 50 | Field::make('text_field')->text(), 51 | Field::make('time_field')->time(), 52 | Field::make('timeTz_field')->timeTz(), 53 | Field::make('timestamp_field')->timestamp(), 54 | Field::make('timestampTz_field')->timestampTz(), 55 | Field::make('tinyIncrements_field')->tinyIncrements(), 56 | Field::make('tinyInteger_field')->tinyInteger(), 57 | Field::make('unique_field')->unique(), 58 | Field::make('unsignedBigInteger_field')->unsignedBigInteger(), 59 | Field::make('unsignedDecimal_field')->unsignedDecimal(20, 2), 60 | Field::make('unsignedInteger_field')->unsignedInteger(), 61 | Field::make('unsignedMediumInteger_field')->unsignedMediumInteger(), 62 | Field::make('unsignedSmallInteger_field')->unsignedSmallInteger(), 63 | Field::make('unsignedTinyInteger_field')->unsignedTinyInteger(), 64 | Field::make('slug_field')->slug(), 65 | Field::make('url_field')->url(), 66 | Field::make('uuid_field')->uuid(), 67 | Field::make('year_field')->year(), 68 | ]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Models/Duplicity.php: -------------------------------------------------------------------------------- 1 | string(), 14 | Field::make('duplicity_field')->text(), 15 | Field::make('another_field')->text(), 16 | ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Models/FieldCaching.php: -------------------------------------------------------------------------------- 1 | fieldsMethodWasCalled === false) { 15 | $this->fieldsMethodWasCalled = true; 16 | 17 | // first time 18 | return [ 19 | Field::make('id'), 20 | ]; 21 | } else { 22 | // second time 23 | return [ 24 | Field::make('id'), 25 | Field::make('name'), 26 | ]; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Models/OhRule.php: -------------------------------------------------------------------------------- 1 | accepted(), 14 | Field::make('activeUrl_field')->activeUrl(), 15 | Field::make('after_field')->after('2018-12-31'), 16 | Field::make('afterOrEqual_field')->afterOrEqual('2018-12-31'), 17 | Field::make('alpha_field')->alpha(), 18 | Field::make('alphaDash_field')->alphaDash(), 19 | Field::make('alphaNum_field')->alphaNum(), 20 | Field::make('array_field')->array(), 21 | Field::make('bail_field')->bail(), 22 | Field::make('before_field')->before('2018-12-31'), 23 | Field::make('beforeOrEqual_field')->beforeOrEqual('2018-12-31'), 24 | Field::make('between_field')->between(1, 20), 25 | Field::make('confirmed_field')->confirmed(), 26 | Field::make('dateEquals_field')->dateEquals('2018-12-31'), 27 | Field::make('dateFormat_field')->dateFormat('Y-m-d'), 28 | Field::make('different_field')->different('somefield'), 29 | Field::make('digits_field')->digits(3), 30 | Field::make('digitsBetween_field')->digitsBetween(5, 9), 31 | Field::make('exists_field')->exists('users'), 32 | Field::make('exists_field_with_column')->exists('users', 'username'), 33 | Field::make('exists_field_full')->exists('users', 'username', function ($query) { 34 | $query->where('account_id', 1); 35 | }), 36 | Field::make('filled_field')->filled(), 37 | Field::make('gt_field')->gt(5), 38 | Field::make('gte_field')->gte(6), 39 | Field::make('in_field')->in(['D', 'E', 'F']), 40 | Field::make('inArray_field')->inArray('letters'), 41 | Field::make('ip_field')->ip(), 42 | Field::make('ipv4_field')->ipv4(), 43 | Field::make('ipv6_field')->ipv6(), 44 | Field::make('lt_field')->lt(10), 45 | Field::make('lte_field')->lte(11), 46 | Field::make('max_field')->max(12), 47 | Field::make('min_field')->min(13), 48 | Field::make('notIn_field')->notIn(['Mary', 'May']), 49 | Field::make('notRegex_field')->notRegex('/w+/'), 50 | Field::make('numeric_field')->numeric(), 51 | Field::make('present_field')->present(), 52 | Field::make('regex_field')->regex('/{abc}/'), 53 | Field::make('required_field')->required(), 54 | Field::make('requiredIf_field')->requiredIf('status', 'ACTIVE'), 55 | Field::make('requiredUnless_field')->requiredUnless('status', 'ARCHIVED'), 56 | Field::make('requiredWith_field')->requiredWith(['a', 'b', 'c', 'd']), 57 | Field::make('requiredWithAll_field')->requiredWithAll(['e', 'f', 'g', 'h']), 58 | Field::make('requiredWithout_field')->requiredWithout(['w', 'x', 'y', 'z']), 59 | Field::make('requiredWithoutAll_field')->requiredWithoutAll(['l', 'm', 'n', 'o']), 60 | Field::make('same_field')->same('password_confirm'), 61 | Field::make('timezone_field')->timezone(), 62 | ]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/Models/Product.php: -------------------------------------------------------------------------------- 1 | increments(), 14 | Field::make('sku')->string()->required()->unique()->guarded()->label('Sku Number'), 15 | Field::make('name')->string()->required()->guarded(), 16 | Field::make('price')->decimal(6, 2)->fillable()->required()->min(0), 17 | Field::make('description')->text()->fillable()->nullable(), 18 | Field::make('status')->string()->default('ACTIVE'), 19 | Field::make('valid_until')->timestamp()->cast('date'), 20 | Field::make('created_at')->timestamp()->nullable()->index(), 21 | Field::make('updated_at')->timestamp()->nullable()->index(), 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Models/User.php: -------------------------------------------------------------------------------- 1 | increments(), 14 | Field::make('name')->string()->required()->fillable(), 15 | Field::make('email')->email()->required()->fillable(), 16 | Field::make('password')->string()->required(), 17 | Field::make('created_at')->timestamp()->nullable()->index(), 18 | Field::make('updated_at')->timestamp()->nullable()->index(), 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Snapshots/join-tree.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 21 4 | 22 5 | 221 6 | 3 7 | -------------------------------------------------------------------------------- /tests/Snapshots/migration-1.txt: -------------------------------------------------------------------------------- 1 | increments("id"); 13 | $table->timestamp("created_at")->nullable(true); 14 | $table->timestamp("updated_at")->nullable(true); 15 | }); 16 | } 17 | 18 | public function down() 19 | { 20 | Schema::drop('products'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Snapshots/migration-2.txt: -------------------------------------------------------------------------------- 1 | string("name")->default("John Doe")->nullable(true)->change(); 13 | $table->decimal("price", 5, 2)->index(false)->change(); 14 | }); 15 | } 16 | 17 | public function down() 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Snapshots/model.txt: -------------------------------------------------------------------------------- 1 | increments(), 14 | ]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 |