├── .gitignore ├── README.md ├── composer.json └── src ├── BaseType.php ├── Checker.php ├── Command.php ├── DependencyResolver.php ├── Destroyer.php ├── Field.php ├── JablesServiceProvider.php ├── Loader.php ├── Prettifyer.php ├── Runner.php ├── TagIndexer.php ├── commands ├── Check.php ├── CreateFolder.php ├── CreateTable.php ├── Dependencies.php ├── Destroy.php ├── Diff.php ├── Jables.php ├── Prettify.php ├── Refresh.php ├── Tags.php └── traits │ ├── Checks.php │ └── CreatesTable.php ├── config └── jables.php ├── exceptions ├── NameCollisionException.php ├── ParseException.php ├── SchemaException.php └── TagNotFoundException.php ├── schemas ├── base_field.json ├── big-integer.json ├── binary.json ├── boolean.json ├── char.json ├── date-time.json ├── date.json ├── decimal.json ├── double.json ├── enum.json ├── field_type.json ├── float.json ├── integer.json ├── json.json ├── jsonb.json ├── long-text.json ├── medium-integer.json ├── medium-text.json ├── morphs.json ├── small-integer.json ├── string.json ├── table.json ├── text.json ├── time.json ├── timestamp.json └── tiny-integer.json └── types ├── BigInteger.php ├── Binary.php ├── Boolean.php ├── Char.php ├── Date.php ├── DateTime.php ├── Decimal.php ├── Double.php ├── Enum.php ├── Float.php ├── Integer.php ├── Json.php ├── Jsonb.php ├── LongText.php ├── MediumInteger.php ├── MediumText.php ├── Morphs.php ├── SmallInteger.php ├── String.php ├── Text.php ├── Time.php ├── Timestamp.php └── TinyInteger.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .fuse* 3 | .subl* 4 | composer.lock 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jables - BETA 2 | > For Laravel 5 3 | 4 | Write your Database Schema in JSON, with clean naming conventions and store them nicely in Version Control. 5 | 6 | # Features 7 | ✓ Schema in JSON 8 | ✓ Laravel Integration 9 | ✓ All Laravel Field Types supported. 10 | ✓ Checking JSON Syntax 11 | ✓ Checking Foreign key References. 12 | ✓ Checking Unique key Constraints. 13 | ✓ Automatic Table Deconstruction. 14 | 15 | Soon to come: 16 | - Schema Diff (build changes not complete reconstructions) 17 | - Automatic Documentation Builder 18 | - JSON to Migration Transpiler 19 | 20 | # Installation 21 | Grab it through Composer. 22 | ``` 23 | composer require hedronium/jables 24 | ``` 25 | 26 | You must add Jables to Laravel as a service provider. To do this, open up your `config/app.php` and add `hedronium\Jables\JablesServiceProvider` to the Service Providers list. Like: 27 | 28 | ```PHP 29 | 'providers' => [ 30 | // .... other providers and stuff ... 31 | hedronium\Jables\JablesServiceProvider::class 32 | ] 33 | ``` 34 | 35 | ## Testing Installation 36 | On the command line run 37 | ``` 38 | php artisan 39 | ``` 40 | and check to see if the `jables` command and the `jables` section shows up. 41 | 42 | # Usage 43 | ## Commands 44 | `php artisan jables` - Checks your JSON files then creates your Database Tables 45 | `php artisan jables:check` - Checks your JSON files and reports errors. 46 | `php artisan jables:refresh` - Destroys all the tables then recreates them from your (possibly updated) JSON files. (warning: risk of data loss) 47 | `php artisan jables:destroy` - Removes all the tables that jables created from Database. 48 | `php artisan jables:create-folder` - Creates the folder to store your Schema based on your configuration. 49 | `php artisan jables:create-table` - Creates Jables own tracking table in database. 50 | 51 | All the commands accept the `--database=[connection]` option. You can use it to override which connection Jables uses to do its business. 52 | 53 | example 54 | ``` 55 | php artisan jables --database=memory 56 | ``` 57 | 58 | ## Writing Schemas 59 | The Schema files are usually stored in the `database/jables` (you need to create it) folder unless you configure it to be otherwise. The Filename **is** your table name. So if you were to create a `users` table, your file name would be `users.json` nested under `database/jables` 60 | 61 | ### Getting Started 62 | 63 | `food.json` 64 | 65 | ```JSON 66 | { 67 | "fields": { 68 | "name": { 69 | "type": "string" 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | now run `php artisan jables`. This will create a table named `food` with a field named `name` with the type being `varchar`. 76 | 77 | ### The Formal Breakdown 78 | Well you define all your fields in the `fields` property on your root object of your json file. The `fields` property itself is an object and every property of the `fields` object corresponds to a table field. 79 | 80 | Each property (which are the field definitions) within the `fields` object is once again another object. The only hard requirement for it all is the `type` property on them. This tells jables what is the type of the field. 81 | 82 | in our 'hello world' example the type of `string` corresponds to `varchar` just like in Laravel migrations (Jables uses Laravel's [Schema Builder](http://laravel.com/docs/5.1/migrations)). 83 | 84 | ## Types Available 85 | Heres a list 86 | 87 | - [big-integer](#big-integer) 88 | - [binary](#binary) 89 | - [boolean](#boolean) 90 | - [char](#char) 91 | - [date](#date) 92 | - [date-time](#date-time) 93 | - [decimal](#decimal) 94 | - [double](#double) 95 | - [enum](#enum) 96 | - [float](#float) 97 | - [integer](#integer) 98 | - [json](#json) 99 | - [jsonb](#jsonb) 100 | - [long-text](#long-text) 101 | - [medium-integer](#medium-integer) 102 | - [medium-text](#medium-text) 103 | - [morphs](#morphs) 104 | - [small-integer](#small-integer) 105 | - [string](#string) 106 | - [text](#text) 107 | - [time](#time) 108 | - [tiny-integer](#tiny-integer) 109 | - [timestamp](#timestamp) 110 | 111 | ### integer 112 | ```JSON 113 | "awesomeness": { 114 | "type": "integer" 115 | } 116 | ``` 117 | 118 | You can write `attributes` which is a list. Currently only supports one attribute, the `unsigned` attribute 119 | 120 | like... 121 | ```JSON 122 | { 123 | "type": "integer", 124 | "attributes": [ 125 | "unsigned" 126 | ] 127 | } 128 | ``` 129 | 130 | You can only set it to auto-increment with the `ai` (`boolean`) property like... 131 | ```JSON 132 | { 133 | "type": "integer", 134 | "ai" : true 135 | } 136 | ``` 137 | 138 | ### big-integer 139 | Same as the average integer just write the type different like... 140 | ```JSON 141 | { 142 | "type": "big-integer" 143 | } 144 | ``` 145 | 146 | ### medium-integer 147 | Same as the average integer just write the type different like... 148 | ```JSON 149 | { 150 | "type": "medium-integer" 151 | } 152 | ``` 153 | 154 | ### small-integer 155 | Same as the average integer just write the type different like... 156 | ```JSON 157 | { 158 | "type": "small-integer" 159 | } 160 | ``` 161 | 162 | ### tiny-integer 163 | Same as the average integer just write the type different like... 164 | ```JSON 165 | { 166 | "type": "tiny-integer" 167 | } 168 | ``` 169 | 170 | ### float 171 | The `FLOAT` type equivalent. 172 | ```JSON 173 | { 174 | "type": "float" 175 | } 176 | ``` 177 | 178 | ### double 179 | The `DOUBLE` type equivalent. It requires you to set the `digits` & `precision` properties. 180 | 181 | ```JSON 182 | { 183 | "type": "double", 184 | "digits": 10, 185 | "precision": 5 186 | } 187 | ``` 188 | 189 | ### decimal 190 | The `DECIMAL` type. Properties same as `double`. 191 | 192 | ```JSON 193 | { 194 | "type": "decimal", 195 | "digits": 10, 196 | "precision": 5 197 | } 198 | ``` 199 | 200 | ### string 201 | `string` is the `VARCHAR` type, and it accepts a `length` property like... 202 | 203 | ```JSON 204 | { 205 | "type": "string", 206 | "length": 50 207 | } 208 | ``` 209 | 210 | but the `length` property isn't required. 211 | 212 | ### char 213 | Its exactly like string it just uses the `CHAR` type but the `length` property is absolutely required. 214 | 215 | ```JSON 216 | { 217 | "type": "char", 218 | "length": 10 219 | } 220 | ``` 221 | 222 | 223 | ### text 224 | Text doesn't require any special properties. 225 | 226 | ```JSON 227 | { 228 | "type": "text" 229 | } 230 | ``` 231 | 232 | ### long-text 233 | Same as `text`. 234 | ```JSON 235 | { 236 | "type": "long-text" 237 | } 238 | ``` 239 | 240 | ### medium-text 241 | Same as `text`. 242 | ```JSON 243 | { 244 | "type": "medium-text" 245 | } 246 | ``` 247 | 248 | ### date 249 | No Special Properties. 250 | ```JSON 251 | { 252 | "type": "date" 253 | } 254 | ``` 255 | 256 | ### time 257 | No Special Properties. 258 | ```JSON 259 | { 260 | "type": "time" 261 | } 262 | ``` 263 | 264 | ### date-time 265 | No Special Properties. 266 | ```JSON 267 | { 268 | "type": "date-time" 269 | } 270 | ``` 271 | 272 | ### timestamp 273 | No Special Properties. 274 | ```JSON 275 | { 276 | "type": "timestamp" 277 | } 278 | ``` 279 | 280 | ### enum 281 | for the `ENUM` type. It is required that you set the `values`(`list`) property. 282 | ```JSON 283 | { 284 | "type": "enum", 285 | "values": ["wizard", "muggle"] 286 | } 287 | ``` 288 | 289 | ### boolean 290 | No special properties. 291 | ```JSON 292 | { 293 | "type": "boolean" 294 | } 295 | ``` 296 | 297 | ### json 298 | No special properties. 299 | ```JSON 300 | { 301 | "type": "json" 302 | } 303 | ``` 304 | 305 | ### jsonb 306 | No special properties. 307 | ```JSON 308 | { 309 | "type": "jsonb" 310 | } 311 | ``` 312 | 313 | ### morphs 314 | No special properties. 315 | ```JSON 316 | { 317 | "type": "morphs" 318 | } 319 | ``` 320 | 321 | ### binary 322 | No special properties. 323 | ```JSON 324 | { 325 | "type": "binary" 326 | } 327 | ``` 328 | 329 | ## Timestamps 330 | Just like in Schema Builder, you can create the two fields `created_at` and `updated_at` in a simple way. 331 | 332 | Create a special `timestamps` property in yours `fields` object and set it to true. 333 | 334 | Like: 335 | ```JSON 336 | { 337 | "fields": { 338 | "user_id": { 339 | "type": "integer", 340 | "attributes": [ 341 | "unsigned" 342 | ] 343 | }, 344 | "burger_id": { 345 | "type": "integer", 346 | "attributes": [ 347 | "unsigned" 348 | ] 349 | }, 350 | "timestamps": true 351 | } 352 | } 353 | ``` 354 | 355 | ## softDeletes 356 | Same with softDeletes. 357 | Create a special `soft-deletes` property in yours `fields` object and set it to true. 358 | 359 | Like: 360 | ```JSON 361 | { 362 | "fields": { 363 | "user_id": { 364 | "type": "integer", 365 | "attributes": [ 366 | "unsigned" 367 | ] 368 | }, 369 | "soft-deletes": true 370 | } 371 | } 372 | ``` 373 | 374 | ## Default Values 375 | All field definitions accept the `default` property for when you want to set the default value of a field. 376 | 377 | Used like... 378 | ```JSON 379 | { 380 | "type": "string", 381 | "default": "cake" 382 | } 383 | ``` 384 | 385 | ## Nullable Fields 386 | All field definitions accept the `nullable`(`boolean`) property. If set to true, the field can be left null. 387 | 388 | Used like... 389 | ```JSON 390 | { 391 | "type": "string", 392 | "nullable": true 393 | } 394 | ``` 395 | 396 | ## Primary Keys 397 | if you set the `ai` to true on a `integer` type or similar field. That field automatically becomes the primary key (its a Laravel thing). 398 | 399 | Apart from that, you can set the `primary` property on any field to true like... 400 | ```JSON 401 | { 402 | "type": "string", 403 | "primary": true 404 | } 405 | ``` 406 | 407 | ### Composite Primary Keys 408 | More that one field makes your primary key? No Problem! Just create a `primary`(`list`) property on your root object (sibling to your `fields` property) like... 409 | 410 | ```JSON 411 | { 412 | "fields": { 413 | "region": { 414 | "type": "string" 415 | }, 416 | "house": { 417 | "type": "string" 418 | } 419 | }, 420 | "primary": ["region", "house"] 421 | } 422 | ``` 423 | 424 | ## Unique Constraints 425 | All field definitions accept the `unique` property. set it to `true` to make it an unique field like... 426 | 427 | ```JSON 428 | { 429 | "type": "string", 430 | "length": 20, 431 | "unique": true 432 | } 433 | ``` 434 | 435 | ### Composite Unique Constraint 436 | You can created unique constraints across many fields. Just create a `unique`(`list`) property on your root object (sibling to your `fields` property) like... 437 | 438 | ```JSON 439 | { 440 | "fields": { 441 | "region": { 442 | "type": "string" 443 | }, 444 | "house": { 445 | "type": "string" 446 | } 447 | }, 448 | "unique": [ 449 | ["region", "house"] 450 | ] 451 | } 452 | ``` 453 | 454 | Yes, it is a list inside a list, for when you want multiple composite constraints. 455 | 456 | ## Foreign Key Constraints 457 | Got you covered! All fields accept the `foreign` property. You can set it to a string containing the name of the table and the name of the field of that table separated by a dot. (eg. `users.id`) 458 | 459 | ```JSON 460 | "user_id": { 461 | "type": "integer", 462 | "attributes": [ 463 | "unsigned" 464 | ], 465 | "foreign": "users.id" 466 | } 467 | ``` 468 | 469 | this `user_id` field will now reference `id` on the `users` table. 470 | 471 | You could also define them like you define unique constraints like... 472 | ```JSON 473 | { 474 | "fields": { 475 | "user_id": { 476 | "type": "integer", 477 | "attributes": [ 478 | "unsigned" 479 | ] 480 | }, 481 | "burger_id": { 482 | "type": "integer", 483 | "attributes": [ 484 | "unsigned" 485 | ] 486 | } 487 | }, 488 | "foreign": { 489 | "user_id": "users.id", 490 | "burger_id": "burgers.id" 491 | } 492 | } 493 | ``` 494 | This will work totally fine. 495 | 496 | # Documenting Tables 497 | All field and table definitions accept `title` and `description` properties that can be used to document your database schema. 498 | 499 | We're also working on a feature that generates HTML documentation from your JSON files but for now, you gotta create it on your own. (Maybe even send us a pull request. PLEASE. WE'RE DESPERATE.) 500 | 501 | example: 502 | ```JSON 503 | { 504 | "title": "Food", 505 | "description": "FOOOD! GLORIOUS FOOD!", 506 | "fields": { 507 | "name": { 508 | "type": "string", 509 | "title": "Name", 510 | "description": "Name of the food." 511 | } 512 | } 513 | } 514 | ``` 515 | 516 | # Configuration 517 | Jables usually works right out of the box with no configuration required, but two option does exist. 518 | 519 | First publish the configuration files. with 520 | ``` 521 | php artisan vendor:publish 522 | ``` 523 | 524 | after running that a `jables.php` should show up in your `config` folder with the following contents... 525 | 526 | ```PHP 527 | 'jables', 530 | 'folder' => 'jables' 531 | ]; 532 | ``` 533 | 534 | ## Options 535 | - `table` - The name of the special table jables creates for tracking. 536 | - `folder` - The name of the folder within which you store your table schemas. The name is relative to your Laravel installation's `database` folder. 537 | 538 | > It is recommend that you store your schema in a folder within your application's `database` folder. 539 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hedronium/jables", 3 | "description": "Write your Laravel Database Schema in JSON.", 4 | "type": "library", 5 | "keywords": ["laravel", "database", "schema", "json"], 6 | "require": { 7 | "seld/jsonlint": "^1.3", 8 | "justinrainbow/json-schema": "~1.3", 9 | "symfony/yaml": "~3.1" 10 | }, 11 | "autoload": { 12 | "psr-4": { 13 | "hedronium\\Jables\\": "src/" 14 | } 15 | }, 16 | "license": "MIT", 17 | "authors": [ 18 | { 19 | "name": "Omran Jamal", 20 | "email": "o.jamal97@gmail.com" 21 | }, 22 | { 23 | "name": "Aniruddha Chakraborty", 24 | "email": "project.anik@gmail.com" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/BaseType.php: -------------------------------------------------------------------------------- 1 | name = $name; 15 | } 16 | 17 | public function setSchema($schema) 18 | { 19 | $this->schema = $schema; 20 | } 21 | 22 | public function setTable(Blueprint $table) 23 | { 24 | $this->table = $table; 25 | } 26 | 27 | public function initField() 28 | { 29 | $this->field = $this->init($this->table, $this->name); 30 | } 31 | 32 | protected function definition($definition) 33 | { 34 | if (isset($this->schema->$definition)) { 35 | return $this->schema->$definition; 36 | } else { 37 | return null; 38 | } 39 | } 40 | 41 | protected function unique() 42 | { 43 | if($this->definition('unique') === true){ 44 | $this->field->unique(); 45 | } 46 | } 47 | 48 | protected function nullable() 49 | { 50 | if($this->definition('nullable') === true){ 51 | $this->field->nullable(); 52 | } 53 | } 54 | 55 | protected function defaultValue() 56 | { 57 | if($defaultValue = $this->definition('default')){ 58 | $this->field->default($defaultValue); 59 | } 60 | } 61 | 62 | public function base() 63 | { 64 | $this->unique(); 65 | $this->nullable(); 66 | $this->defaultValue(); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Checker.php: -------------------------------------------------------------------------------- 1 | ['attributes'], 28 | 'big-integer' => 'integer', 29 | 'medium-integer' => 'integer', 30 | 'small-integer' => 'integer', 31 | 'tiny-integer' => 'integer', 32 | 'string' => ['length'], 33 | 'char' => 'string', 34 | 'decimal' => ['digits', 'prescision'], 35 | 'double' => 'decimal', 36 | 'enum' => ['values'] 37 | ]; 38 | 39 | public function getFileList() 40 | { 41 | return $this->files; 42 | } 43 | 44 | public function __construct ($app, Filesystem $fs, Loader $loader) 45 | { 46 | $this->fs = $fs; 47 | $this->app = $app; 48 | $this->loader = $loader; 49 | 50 | $this->files = array_values($loader->paths()); 51 | 52 | $this->parser = new JsonParser(); 53 | $this->schema_retriever = new \JsonSchema\Uri\UriRetriever; 54 | $this->schema_resolver = new \JsonSchema\RefResolver($this->schema_retriever); 55 | $this->schema_validator = new \JsonSchema\Validator; 56 | } 57 | 58 | protected function loadSchema($file) 59 | { 60 | if (isset($this->schemas[$file])) { 61 | return $this->schemas[$file]; 62 | } 63 | 64 | $retriever = $this->schema_retriever; 65 | $refResolver = $this->schema_resolver; 66 | 67 | 68 | $resolve_path = 'file://'.__DIR__.'/schemas/'; 69 | 70 | $schema = $retriever->retrieve('file://'.__DIR__.'/schemas/'.$file); 71 | $refResolver->resolve($schema, $resolve_path); 72 | 73 | $this->schemas[$file] = $schema; 74 | 75 | return $schema; 76 | } 77 | 78 | protected function fieldSchematicLimitError($table_name, $field_name, $field_schema, $field_data) 79 | { 80 | $errors = []; 81 | 82 | if (!isset($field_schema->allOf)) { 83 | return null; 84 | } 85 | 86 | $permitted = []; 87 | $available = []; 88 | 89 | foreach($field_schema->allOf as $subschema) { 90 | foreach($subschema->properties as $attr_name => $property) { 91 | $permitted[] = $attr_name; 92 | } 93 | } 94 | 95 | foreach($field_data as $attr_name => $value) { 96 | $available[] = $attr_name; 97 | } 98 | 99 | $diff = array_diff($available, $permitted); 100 | 101 | foreach ($diff as $property) { 102 | $errors[] = [ 103 | 'table' => $table_name, 104 | 'path' => $this->loader->path($table_name), 105 | 'property' => "$table_name.fields.$field_name", 106 | 'message' => "The property - $property - is not defined and the definition does not allow additional properties" 107 | ]; 108 | } 109 | 110 | if (count($diff)) { 111 | throw new SchemaException($errors); 112 | } 113 | 114 | return null; 115 | } 116 | 117 | protected function fieldSchematicError($table_name, $table_data) 118 | { 119 | $errors = []; 120 | 121 | $validator = $this->schema_validator; 122 | 123 | $fields = $table_data->fields; 124 | 125 | foreach ($fields as $name => $field) { 126 | if ($name === 'timestamps' || $name === 'soft-deletes') { 127 | continue; 128 | } 129 | 130 | $schema_file = $field->type.'.json'; 131 | 132 | $field_schema = $this->loadSchema($schema_file); 133 | 134 | $field_data = $field; 135 | $validator->check($field_data, $field_schema); 136 | 137 | if (!$validator->isValid()) { 138 | foreach ($validator->getErrors() as $error) { 139 | $errors[] = [ 140 | 'table' => $table_name, 141 | 'path' => $this->loader->path($table_name), 142 | 'property' => "fields.$name.".$error['property'], 143 | 'message' => $error['message'] 144 | ]; 145 | } 146 | 147 | throw new SchemaException($errors); 148 | } 149 | 150 | $this->fieldSchematicLimitError($table_name, $name, $field_schema, $field_data); 151 | } 152 | 153 | return null; 154 | } 155 | 156 | public function schemaError() 157 | { 158 | $errors = []; 159 | 160 | $validator = $this->schema_validator; 161 | $table_schema = $this->loadSchema('table.json'); 162 | 163 | foreach ($this->loader->names() as $table_name) { 164 | $table_data = $this->loader->get($table_name); 165 | $validator->check($table_data, $table_schema); 166 | 167 | if (!$validator->isValid()) { 168 | foreach ($validator->getErrors() as $error) { 169 | $errors[] = [ 170 | 'table' => $table_name, 171 | 'path' => $this->loader->path($table_name), 172 | 'property' => $error['property'], 173 | 'message' => $error['message'] 174 | ]; 175 | } 176 | 177 | throw new SchemaException($errors); 178 | } 179 | 180 | $this->fieldSchematicError($table_name, $table_data); 181 | } 182 | 183 | return null; 184 | } 185 | 186 | public function resolveRefferenceChecks() 187 | { 188 | $checks = &$this->refference_checks; 189 | 190 | foreach ($checks as &$check) { 191 | if (is_string($check)) { 192 | $check = $checks[$check]; 193 | } else { 194 | array_push($check, 'type'); 195 | } 196 | } 197 | } 198 | 199 | public function foreignKeyError() 200 | { 201 | $this->resolveRefferencechecks(); 202 | 203 | $fields = []; 204 | $foreigns = []; 205 | 206 | foreach ($this->datas as $file => $table) { 207 | $table_name = $this->getName($file); 208 | 209 | foreach ($table->fields as $field_name => $field) { 210 | $fields[$table_name.'.'.$field_name] = $field; 211 | 212 | if (isset($field->foreign)) { 213 | $foreigns[$table_name.'.'.$field_name] = $field->foreign; 214 | } 215 | } 216 | 217 | if (isset($table->foreign)) { 218 | $table_foreigns = (array) $table->foreign; 219 | $table_new_foreigns = []; 220 | 221 | foreach ($table_foreigns as $field=>$parent) { 222 | if (!isset($table->fields->$field)) { 223 | return "The field $field does not exist in table $table_name"; 224 | } 225 | 226 | $table_new_foreigns[$table_name.'.'.$field] = $parent; 227 | } 228 | 229 | $foreigns = array_merge( 230 | $foreigns, 231 | $table_new_foreigns 232 | ); 233 | } 234 | } 235 | 236 | foreach ($foreigns as $child=>$parent) { 237 | if (!isset($fields[$parent])) { 238 | return "$parent does not exist, in $child"; 239 | } 240 | 241 | 242 | $fields[$child]; 243 | 244 | if (isset($this->refference_checks[$fields[$child]->type])) { 245 | $checks = $this->refference_checks[$fields[$child]->type]; 246 | } else { 247 | $checks = ['type']; 248 | } 249 | 250 | foreach ($checks as $check) { 251 | if (!(isset($fields[$child]->$check) && isset($fields[$parent]->$check))) { 252 | return "The fields definitions $child & $parent don't match. ($check missing)"; 253 | } 254 | 255 | if (is_array($fields[$child]->$check) && is_array($fields[$parent]->$check)) { 256 | if (!empty(array_diff($fields[$child]->$check, $fields[$parent]->$check)) || !empty(array_diff($fields[$parent]->$check, $fields[$child]->$check))) { 257 | return "The fields definitions $child & $parent don't match. ($check)"; 258 | } 259 | } elseif ($fields[$child]->$check !== $fields[$parent]->$check) { 260 | return "The fields definitions $child & $parent don't match. ($check)"; 261 | } 262 | } 263 | } 264 | } 265 | 266 | public function uniqueError() 267 | { 268 | foreach ($this->datas as $name => $table) { 269 | $name = $this->getName($name); 270 | 271 | if (isset($table->unique)) { 272 | foreach ($table->unique as $compound) { 273 | foreach ($compound as $field) { 274 | if (!isset($table->fields->$field)) { 275 | return "The $field field is missing in table $name. (unique constraints)"; 276 | } 277 | } 278 | } 279 | } 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/Command.php: -------------------------------------------------------------------------------- 1 | app = $app; 16 | $this->loader = $loader; 17 | } 18 | 19 | protected function resolve($table) 20 | { 21 | $definition = $this->loader->get($table); 22 | 23 | if (!$definition) { 24 | throw new \Exception("$table definition doesn't exist."); 25 | } 26 | 27 | $sub_tree = [ 28 | 'name' => $table, 29 | 'relations' => [] 30 | ]; 31 | 32 | $foreign_keys = isset($definition->foreign) ? $definition->foreign : []; 33 | 34 | foreach ($definition->fields as $field_name => $field_def) { 35 | if (isset($field_def->foreign)) { 36 | $foreign_keys[$field_name] = $field_def->foreign; 37 | } 38 | } 39 | 40 | foreach ($foreign_keys as $field_name => $foreign) { 41 | list($foreign_table, $foreign_field) = explode('.', $foreign); 42 | 43 | $sub_tree['relations'][] = [ 44 | 'from_table' => $table, 45 | 'to_table' => $foreign_table, 46 | 'from_field' => $field_name, 47 | 'to_field' => $foreign_field, 48 | 'table' => $this->resolve($foreign_table) 49 | ]; 50 | } 51 | 52 | return $sub_tree; 53 | } 54 | 55 | public function resolveDependencyTree($table) 56 | { 57 | $tree = [ 58 | 'table' => $this->resolve($table) 59 | ]; 60 | 61 | return $tree; 62 | } 63 | 64 | protected function resolveList($relations, &$list) 65 | { 66 | foreach ($relations as $relation) { 67 | $temp = $relation; 68 | unset($temp['table']); 69 | 70 | $list[] = $temp; 71 | 72 | $this->resolveList($relation['table']['relations'], $list); 73 | } 74 | } 75 | 76 | public function resolveDependencyList($table) 77 | { 78 | $tree = $this->resolveDependencyTree($table); 79 | $list = []; 80 | 81 | $this->resolveList($tree['table']['relations'], $list); 82 | 83 | return $list; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Destroyer.php: -------------------------------------------------------------------------------- 1 | db->table($this->app['config']['jables.table'])->orderBy('id', 'desc')->where('type', '=', 'table')->get(); 25 | 26 | foreach ($tables as $raw) { 27 | $defs = json_decode($raw->data); 28 | 29 | foreach ($defs as $table => &$x) { 30 | $this->tables[] = $table; 31 | } 32 | } 33 | 34 | $foreigns = $this->db->table($this->app['config']['jables.table'])->orderBy('id', 'desc')->where('type', '=', 'foreign')->get(); 35 | 36 | foreach ($foreigns as $raw) { 37 | $tabs = json_decode($raw->data, true); 38 | 39 | $this->foreigns = array_merge_recursive($this->foreigns, $tabs); 40 | } 41 | 42 | return true; 43 | } 44 | 45 | public function __construct($app, DatabaseManager $db) 46 | { 47 | $this->app = $app; 48 | $this->db_manager = $db; 49 | $this->parser = new JsonParser; 50 | } 51 | 52 | public function connection($connection = null) 53 | { 54 | $this->db = $this->db_manager->connection($connection); 55 | } 56 | 57 | public function destroyJablesTable() 58 | { 59 | $builder = $this->db->getSchemaBuilder(); 60 | $builder->dropIfExists(config('jables.table')); 61 | } 62 | 63 | public function destroyUserTables() 64 | { 65 | $builder = $this->db->getSchemaBuilder(); 66 | 67 | if (!$builder->hasTable($this->app['config']['jables.table'])) { 68 | return false; 69 | } 70 | 71 | $this->buildLists(); 72 | 73 | foreach ($this->foreigns as $table_name => $foreigns) { 74 | if (count($foreigns) === 0) { 75 | continue; 76 | } 77 | 78 | $builder->table($table_name, function(Blueprint $table) use ($table_name, $foreigns) { 79 | foreach ($foreigns as $foreign) { 80 | $table->dropForeign($foreign); 81 | } 82 | }); 83 | } 84 | 85 | foreach ($this->tables as $table) { 86 | $builder->dropIfExists($table); 87 | } 88 | 89 | return true; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Field.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom( 13 | __DIR__.'/config/jables.php', 'jables' 14 | ); 15 | 16 | $this->app->singleton('jables.loader', function($app){ 17 | return new Loader($app, $app['files']); 18 | }); 19 | 20 | $this->app->singleton('jables.checker', function($app){ 21 | return new Checker($app, $app['files'], $app['jables.loader']); 22 | }); 23 | 24 | $this->app->singleton('jables.runner', function($app){ 25 | return new Runner($app, $app['files'], $app['db'], $app['jables.loader']); 26 | }); 27 | 28 | $this->app->singleton('jables.dependency-resolver', function($app){ 29 | return new DependencyResolver($app, $app['jables.loader']); 30 | }); 31 | 32 | $this->app->singleton('jables.tag-indexer', function($app){ 33 | return new TagIndexer($app, $app['jables.loader']); 34 | }); 35 | 36 | $this->app->singleton('jables.destroyer', function($app){ 37 | return new Destroyer($app, $app['db']); 38 | }); 39 | 40 | $this->app['jables.commands.jables'] = $this->app->share(function($app){ 41 | return new commands\Jables( 42 | $app['jables.runner'], 43 | $app['jables.loader'], 44 | $app['jables.dependency-resolver'], 45 | $app['jables.tag-indexer'] 46 | ); 47 | }); 48 | 49 | $this->app['jables.commands.check'] = $this->app->share(function($app){ 50 | return new commands\Check($app['jables.checker']); 51 | }); 52 | 53 | $this->app['jables.commands.refresh'] = $this->app->share(function($app){ 54 | return new commands\Refresh( 55 | $app, 56 | $app['jables.checker'], 57 | $app['jables.destroyer'], 58 | $app['jables.runner'] 59 | ); 60 | }); 61 | 62 | $this->app['jables.commands.dependencies'] = $this->app->share(function($app){ 63 | return new commands\Dependencies($app, $app['jables.dependency-resolver']); 64 | }); 65 | 66 | $this->app['jables.commands.tags'] = $this->app->share(function($app){ 67 | return new commands\Tags($app, $app['jables.tag-indexer']); 68 | }); 69 | 70 | $this->app['jables.commands.destroy'] = $this->app->share(function($app){ 71 | return new commands\Destroy($app, $app['jables.destroyer']); 72 | }); 73 | 74 | $this->app['jables.commands.diff'] = $this->app->share(function($app){ 75 | return new commands\Diff(); 76 | }); 77 | 78 | $this->app['jables.commands.create-table'] = $this->app->share(function($app){ 79 | return new commands\CreateTable($app['jables.runner']); 80 | }); 81 | 82 | $this->app['jables.commands.create-folder'] = $this->app->share(function($app){ 83 | return new commands\CreateFolder($app, $app['files']); 84 | }); 85 | 86 | $this->app['jables.commands.prettify'] = $this->app->share(function($app){ 87 | return new commands\Prettify($app['jables.checker'], $app['jables.prettifyer']); 88 | }); 89 | } 90 | 91 | public function boot() 92 | { 93 | $this->publishes([ 94 | __DIR__.'/config/jables.php' => config_path('jables.php'), 95 | ]); 96 | 97 | $this->commands([ 98 | 'jables.commands.jables', 99 | 'jables.commands.check', 100 | 'jables.commands.refresh', 101 | 'jables.commands.destroy', 102 | 'jables.commands.dependencies', 103 | 'jables.commands.tags', 104 | 'jables.commands.create-table', 105 | 'jables.commands.create-folder', 106 | ]); 107 | } 108 | 109 | public function provides() 110 | { 111 | return [ 112 | 'jables.loader', 113 | 'jables.runner', 114 | 'jables.checker', 115 | 'jables.dependency-resolver', 116 | 'jables.tag-indexer', 117 | 'jables.destroyer', 118 | 'jables.commands.jables', 119 | 'jables.commands.check', 120 | 'jables.commands.refresh', 121 | 'jables.commands.dependencies', 122 | 'jables.commands.tags', 123 | 'jables.commands.destroy', 124 | 'jables.commands.diff', 125 | 'jables.commands.create-table', 126 | 'jables.commands.create-folder', 127 | 'jables.commands.prettify', 128 | ]; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Loader.php: -------------------------------------------------------------------------------- 1 | app = $app; 26 | $this->fs = $fs; 27 | 28 | $json_parser = new JsonParser(); 29 | 30 | $this->extensions = [ 31 | 'json' => function ($raw) use ($json_parser) { 32 | $parsed = $json_parser->parse($raw); 33 | return $parsed; 34 | }, 35 | 'yml' => function ($raw) { 36 | $parsed = Yaml::parse($raw, false, false, true); 37 | return $parsed; 38 | } 39 | ]; 40 | 41 | $this->index(); 42 | $this->parse(); 43 | } 44 | 45 | public function names() 46 | { 47 | return $this->names; 48 | } 49 | 50 | public function paths() 51 | { 52 | return $this->paths; 53 | } 54 | 55 | public function path($name) 56 | { 57 | return $this->paths[$name]; 58 | } 59 | 60 | public function get($name) 61 | { 62 | return isset($this->parsed[$name]) ? $this->parsed[$name] : false; 63 | } 64 | 65 | public function exists($name) 66 | { 67 | return isset($this->parsed[$name]); 68 | } 69 | 70 | public function parse() 71 | { 72 | foreach ($this->paths as $name => $path) { 73 | $ext = $this->fs->extension($path); 74 | $raw = $this->fs->get($path); 75 | 76 | try { 77 | $this->parsed[$name] = $this->extensions[$ext]($raw); 78 | } catch (\Seld\JsonLint\ParsingException $e) { 79 | throw new ParseException($name, $path, $e->getMessage()); 80 | } 81 | } 82 | } 83 | 84 | public function index($dir = 'jables') 85 | { 86 | $files = $this->fs->allFiles($this->app->databasePath().'/'.$dir); 87 | 88 | $paths = []; 89 | $names = []; 90 | 91 | foreach ($files as $file) { 92 | if (!isset($this->extensions[$file->getExtension()])) { 93 | continue; 94 | } 95 | 96 | $table_name = $this->fs->name($file->getRealPath()); 97 | 98 | if (isset($paths[$table_name])) { 99 | 100 | throw new NameCollisionException( 101 | $paths[$table_name], 102 | $file->getRealPath() 103 | ); 104 | 105 | 106 | } else { 107 | $paths[$table_name] = $file->getRealPath(); 108 | $names[] = $table_name; 109 | } 110 | } 111 | 112 | $this->paths = $paths; 113 | $this->names = $names; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Prettifyer.php: -------------------------------------------------------------------------------- 1 | app = $app; 18 | $this->fs = $fs; 19 | $this->loader = $loader; 20 | 21 | $path = $this->app->databasePath().'/'.config('jables.folder'); 22 | 23 | if (!$this->fs->isWritable($path)) { 24 | throw new \Exception($path.' isn\' writable.'); 25 | } 26 | 27 | $this->buildFileList(); 28 | } 29 | 30 | protected function buildFileList() 31 | { 32 | $files = $this->fs->files($this->app->databasePath().'/'.config('jables.folder')); 33 | 34 | foreach ($files as $file) { 35 | if ($this->fs->extension($file) == 'json') { 36 | $this->files[] = $file; 37 | } 38 | } 39 | } 40 | 41 | public function prettify() 42 | { 43 | $parser = new JsonParser(); 44 | 45 | foreach ($this->files as $file) { 46 | $raw = $this->fs->get($file); 47 | $data = $parser->parse($raw); 48 | $pretty = json_encode($data, JSON_PRETTY_PRINT); 49 | 50 | $tmp_name = $file.'.tmp'; 51 | 52 | if ($this->fs->put($tmp_name, $pretty) === false) { 53 | throw new \Exception('Couldn\'t write to '.$tmp_name); 54 | } 55 | 56 | if (!$this->fs->delete($file)) { 57 | throw new \Exception('Couldn\'t delete '.$file); 58 | } 59 | 60 | if (!$this->fs->move($tmp_name, $file)) { 61 | throw new \Exception('Couldn\'t rename '.$tmp_name); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/Runner.php: -------------------------------------------------------------------------------- 1 | app = $app; 26 | $this->fs = $fs; 27 | $this->db_manager = $db; 28 | $this->loader = $loader; 29 | $this->parser = new JsonParser; 30 | } 31 | 32 | public function createTable() 33 | { 34 | $builder = $this->db->getSchemaBuilder(); 35 | 36 | $table = $this->app['config']['jables.table']; 37 | 38 | if ($builder->hasTable($table)) { 39 | return null; 40 | } 41 | 42 | $builder->create($table, function(Blueprint $table){ 43 | $table->increments('id'); 44 | $table->enum('type', ['table', 'delta', 'foreign']); 45 | $table->longText('data'); 46 | 47 | $table->timestamps(); 48 | }); 49 | 50 | return true; 51 | } 52 | 53 | public function connection($connection = null) 54 | { 55 | $this->db = $this->db_manager->connection($connection); 56 | } 57 | 58 | protected function fieldTypeObject($type) 59 | { 60 | if (isset($this->types[$type])) { 61 | return $this->types[$type]; 62 | } 63 | 64 | $obj = null; 65 | 66 | switch ($type) { 67 | case 'big-integer': 68 | $obj = new types\BigInteger; 69 | break; 70 | case 'binary': 71 | $obj = new types\Binary; 72 | break; 73 | case 'boolean': 74 | $obj = new types\Boolean; 75 | break; 76 | case 'char': 77 | $obj = new types\Char; 78 | break; 79 | case 'date-time': 80 | $obj = new types\DateTime; 81 | break; 82 | case 'date': 83 | $obj = new types\Date; 84 | break; 85 | case 'decimal': 86 | $obj = new types\Decimal; 87 | break; 88 | case 'double': 89 | $obj = new types\Double; 90 | break; 91 | case 'enum': 92 | $obj = new types\Enum; 93 | break; 94 | case 'float': 95 | $obj = new types\Float; 96 | break; 97 | case 'integer': 98 | $obj = new types\Integer; 99 | break; 100 | case 'json': 101 | $obj = new types\Json; 102 | break; 103 | case 'jsonb': 104 | $obj = new types\Jsonb; 105 | break; 106 | case 'long-text': 107 | $obj = new types\LongText; 108 | break; 109 | case 'medium-integer': 110 | $obj = new types\MediumInteger; 111 | break; 112 | case 'medium-text': 113 | $obj = new types\MediumText; 114 | break; 115 | case 'morphs': 116 | $obj = new types\Morphs; 117 | break; 118 | case 'small-integer': 119 | $obj = new types\SmallInteger; 120 | break; 121 | case 'string': 122 | $obj = new types\String; 123 | break; 124 | case 'text': 125 | $obj = new types\Text; 126 | break; 127 | case 'timestamp': 128 | $obj = new types\Timestamp; 129 | break; 130 | case 'time': 131 | $obj = new types\Time; 132 | break; 133 | case 'tiny-integer': 134 | $obj = new types\TinyInteger; 135 | break; 136 | } 137 | 138 | $this->types[$type] = $obj; 139 | 140 | return $obj; 141 | } 142 | 143 | protected function field($table, $name, $field) 144 | { 145 | $obj = $this->fieldTypeObject($field->type); 146 | $obj->setSchema($field); 147 | $obj->setTable($table); 148 | $obj->setName($name); 149 | 150 | $obj->initField(); 151 | $obj->render(); 152 | $obj->base(); 153 | } 154 | 155 | public function foreigns() 156 | { 157 | $builder = $this->db->getSchemaBuilder(); 158 | 159 | $created_foreigns = []; 160 | 161 | foreach ($this->foreigns as $table => $foreigns) { 162 | $table_name = $table; 163 | $created_foreigns[$table] = []; 164 | 165 | $builder->table($table, function($table) use ($foreigns, $table_name, $builder, &$created_foreigns) { 166 | foreach ($foreigns as $field => $foreign) { 167 | 168 | list($foreign_table, $foreign_field) = explode('.', $foreign); 169 | 170 | if ($builder->hasTable($foreign_table)) { 171 | $created_foreigns[$table_name][] = $table_name.'_'.$field.'_foreign'; 172 | $table->foreign($field)->references($foreign_field)->on($foreign_table); 173 | } 174 | } 175 | }); 176 | } 177 | 178 | $table = $this->app['config']['jables.table']; 179 | 180 | $this->db->table($table)->insert([ 181 | 'type' => 'foreign', 182 | 'data' => json_encode($created_foreigns) 183 | ]); 184 | } 185 | 186 | public function up(array $table_names = [], $engine = null, $onerror = null) 187 | { 188 | $creator = function(Blueprint $table, $table_name, $definition, $uniques) use ($engine) { 189 | if ($engine) { 190 | $table->engine = $engine; 191 | } 192 | 193 | foreach ($definition->fields as $name => $field) { 194 | 195 | if ($name === 'timestamps') { 196 | $table->timestamps(); 197 | } elseif ($name === 'soft-deletes') { 198 | $table->softDeletes(); 199 | } else { 200 | $this->field($table, $name, $field); 201 | 202 | if (isset($field->foreign)) { 203 | $this->foreigns[$table_name][$name] = $field->foreign; 204 | } 205 | 206 | } 207 | 208 | } 209 | 210 | foreach ($uniques as $unique) { 211 | $table->unique($unique); 212 | } 213 | 214 | }; 215 | 216 | $creator->bindTo($this); 217 | 218 | if (!$table_names) { 219 | $table_names = $this->loader->names(); 220 | } 221 | 222 | $tables = []; 223 | 224 | foreach ($table_names as $name) { 225 | $definition = $this->loader->get($name); 226 | 227 | if (!$definition) { 228 | $msg = "table definition for '$name' doesn't exist."; 229 | 230 | if ($onerror) { 231 | $onerror($msg); 232 | } else { 233 | throw new \Exception($msg); 234 | } 235 | 236 | continue; 237 | } 238 | 239 | $tables[$name] = $definition; 240 | $builder = $this->db->getSchemaBuilder(); 241 | 242 | $this->foreigns[$name] = []; 243 | 244 | if (isset($definition->foreign)) { 245 | $this->foreigns[$name] = (array) $definition->foreign; 246 | } 247 | 248 | $uniques = []; 249 | 250 | if (isset($definition->unique)) { 251 | $uniques = (array) $definition->unique; 252 | } 253 | 254 | $builder->setConnection($this->db); 255 | 256 | $builder->create($name, function(Blueprint $table) use ($creator, $name, $definition, $uniques) { 257 | $creator($table, $name, $definition, $uniques); 258 | }); 259 | } 260 | 261 | $table = $this->app['config']['jables.table']; 262 | 263 | $this->db->table($table)->insert([ 264 | 'type' => 'table', 265 | 'data' => json_encode($tables) 266 | ]); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/TagIndexer.php: -------------------------------------------------------------------------------- 1 | app = $app; 19 | $this->loader = $loader; 20 | } 21 | 22 | public function indexTags() 23 | { 24 | foreach ($this->loader->names() as $table_name) { 25 | $definition = $this->loader->get($table_name); 26 | 27 | if (!isset($definition->tags)) { 28 | $this->untagged[] = $table_name; 29 | continue; 30 | } 31 | 32 | foreach ($definition->tags as $tag) { 33 | if (!isset($this->tags[$tag])) { 34 | $this->tags[$tag] = []; 35 | } 36 | 37 | $this->tags[$tag][] = $table_name; 38 | } 39 | } 40 | 41 | $this->indexed = true; 42 | } 43 | 44 | public function tags() 45 | { 46 | if (!$this->indexed) { 47 | $this->indexTags(); 48 | } 49 | 50 | return array_keys($this->tags); 51 | } 52 | 53 | public function untagged() 54 | { 55 | if (!$this->indexed) { 56 | $this->indexTags(); 57 | } 58 | 59 | return $this->untagged; 60 | } 61 | 62 | public function get($tag) 63 | { 64 | if (!$this->indexed) { 65 | $this->indexTags(); 66 | } 67 | 68 | if (isset($this->tags[$tag])) { 69 | return $this->tags[$tag]; 70 | } else { 71 | throw new TagNotFoundException($tag); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/commands/Check.php: -------------------------------------------------------------------------------- 1 | checker = $checker; 21 | } 22 | 23 | public function check() 24 | { 25 | $this->info('Checking for Schema Errors...'); 26 | dump($this->checker->schemaError()); 27 | 28 | $this->info('Checking for Foreign key Constraint Errors...'); 29 | $this->checker->foreignKeyError(); 30 | 31 | $this->info('Checking for Unique Constraint Errors...'); 32 | $this->checker->uniqueError(); 33 | 34 | $this->info('--------------------------'); 35 | $this->info('Looks Fine.'); 36 | return true; 37 | } 38 | 39 | public function handle() 40 | { 41 | $this->check(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/CreateFolder.php: -------------------------------------------------------------------------------- 1 | fs = $fs; 17 | $this->app = $app; 18 | } 19 | 20 | public function handle() 21 | { 22 | $path = $this->app->databasePath().'/'.config('jables.folder'); 23 | 24 | $this->info('Checking Jables Directory...'); 25 | 26 | if (!$this->fs->exists($path)) { 27 | $this->info('Creating Directory...'); 28 | $this->fs->makeDirectory($path); 29 | $this->info('Directory Created.'); 30 | 31 | return; 32 | } 33 | 34 | if (!$this->fs->isdirectory($path)) { 35 | $this->error("$path is not a directory."); 36 | 37 | return; 38 | } 39 | 40 | $this->info('Directory already exists.'); 41 | } 42 | } -------------------------------------------------------------------------------- /src/commands/CreateTable.php: -------------------------------------------------------------------------------- 1 | runner = $runner; 17 | } 18 | 19 | public function createTable() 20 | { 21 | $this->info('Creating Jables Tracker table...'); 22 | 23 | if ($this->runner->createTable() === null) { 24 | $this->info('Tracker table already exists.'); 25 | return; 26 | } 27 | 28 | $this->info('Tracker table created.'); 29 | } 30 | 31 | public function handle() 32 | { 33 | $connection = $this->option('database'); 34 | $this->createTable($connection); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/Dependencies.php: -------------------------------------------------------------------------------- 1 | app = $app; 21 | $this->dependency = $dependency; 22 | } 23 | 24 | protected function listDeps() 25 | { 26 | $deps = $this->dependency->resolveDependencyList($this->argument('table_name')); 27 | 28 | $name_lengths = []; 29 | $from_lengths = []; 30 | $to_lengths = []; 31 | 32 | foreach ($deps as $dep) { 33 | $from_table = $dep['from_table']; 34 | $to_table = $dep['to_table']; 35 | 36 | $from_field = $dep['from_field']; 37 | $to_field = $dep['to_field']; 38 | 39 | $name_lengths[] = strlen($to_table); 40 | $from_lengths[] = strlen($from_table) + strlen($from_field) + 1; 41 | $to_lengths[] = strlen($to_table) + strlen($to_field) + 1; 42 | } 43 | 44 | $max_name = max($name_lengths); 45 | $max_from = max($from_lengths); 46 | $max_to = max($to_lengths); 47 | 48 | foreach ($deps as $dep) { 49 | $from_table = $dep['from_table']; 50 | $to_table = $dep['to_table']; 51 | 52 | $from_field = $dep['from_field']; 53 | $to_field = $dep['to_field']; 54 | 55 | $table = str_pad($to_table, $max_name); 56 | $from = str_pad("$from_table.$from_field", $max_from, ' ', STR_PAD_LEFT); 57 | $to = str_pad("$to_table.$to_field", $max_to); 58 | 59 | if ($this->option('relations')) { 60 | $this->line("- $table | $from --> $to"); 61 | } else { 62 | $this->line("- $table"); 63 | } 64 | } 65 | } 66 | 67 | protected function recurse($subtree, $relation, $depth, $last) 68 | { 69 | $padding = ''; 70 | if ($depth > 1) { 71 | for ($i = 0; $i < $depth-1; $i++) { 72 | $padding .= ($last ? ' ' : '│').' '; 73 | } 74 | } 75 | 76 | if ($depth === 0) { 77 | $line = ""; 78 | } else { 79 | $line = ($last ? '└─' : '├─')." "; 80 | } 81 | 82 | $table_name = $subtree['name']; 83 | $relations = $subtree['relations']; 84 | 85 | $rel = ''; 86 | if ($this->option('relations') && $relation) { 87 | $from_field = $relation['from_field']; 88 | $to_field = $relation['to_field']; 89 | $rel = " [$from_field --> $to_field]"; 90 | } 91 | 92 | $this->line("$padding$line$table_name$rel"); 93 | 94 | foreach ($relations as $i => $relation) { 95 | $this->recurse($relation['table'], $relation, $depth + 1, $i === count($relations) - 1); 96 | } 97 | } 98 | 99 | protected function treeDeps() 100 | { 101 | $deps = $this->dependency->resolveDependencyTree($this->argument('table_name')); 102 | $this->recurse($deps['table'], null, 0, true); 103 | } 104 | 105 | public function handle() 106 | { 107 | $this->callSilent('jables:check'); 108 | $this->comment('Dependencies for "'.$this->argument('table_name').'" table'); 109 | 110 | if (!$this->option('list')) { 111 | $this->treeDeps(); 112 | } else { 113 | $this->listDeps(); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/commands/Destroy.php: -------------------------------------------------------------------------------- 1 | app = $app; 19 | $this->destroyer = $destroyer; 20 | } 21 | 22 | public function destroy() 23 | { 24 | $this->info('Removing User Defined Tables...'); 25 | $this->destroyer->connection($this->option('database')); 26 | 27 | if (!$this->destroyer->destroyUserTables()) { 28 | $this->comment('Jables have not been run. Nothing to destroy.'); 29 | return false; 30 | } 31 | 32 | $this->info('Removing Jables Tracking table...'); 33 | $this->destroyer->destroyJablesTable(); 34 | 35 | return true; 36 | } 37 | 38 | public function handle() 39 | { 40 | $this->destroy(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/Diff.php: -------------------------------------------------------------------------------- 1 | runner = $runner; 25 | $this->loader = $loader; 26 | $this->dependency = $dependency; 27 | $this->tags = $tags; 28 | } 29 | 30 | public function createTable() 31 | { 32 | $this->info('Creating Jables Tracker table...'); 33 | 34 | if ($this->runner->createTable() === null) { 35 | $this->info('Tracker table already exists.'); 36 | return; 37 | } 38 | 39 | $this->info('Tracker table created.'); 40 | } 41 | 42 | public function create() 43 | { 44 | $database = $this->option('database'); 45 | $this->runner->connection($database); 46 | 47 | $this->createTable(); 48 | 49 | $engine = $this->option('engine'); 50 | 51 | $this->info('Creating Database Tables...'); 52 | 53 | $tables = $this->argument('tables') ? $this->argument('tables') : []; 54 | 55 | if ($this->option('tag')) { 56 | $tags = explode(',', $this->option('tag')); 57 | 58 | foreach ($tags as $tag) { 59 | $tag_tables = $this->tags->get($tag); 60 | $tables = array_merge($tables, $tag_tables); 61 | } 62 | } 63 | 64 | $tables = array_unique($tables); 65 | 66 | $tabs = $tables; 67 | foreach ($tabs as $table) { 68 | if ($this->loader->exists($table)) { 69 | if (!$this->option('nodeps')) { 70 | $deps = $this->dependency->resolveDependencyList($table); 71 | 72 | foreach ($deps as $dep) { 73 | $tables[] = $dep['to_table']; 74 | } 75 | } 76 | } else { 77 | throw new \Exception("$table definition doesn't exist."); 78 | } 79 | } 80 | 81 | $command = $this; 82 | $this->runner->up(array_unique($tables), $engine, function ($msg) use ($command) { 83 | $command->error($msg); 84 | }); 85 | 86 | $this->info('Creating Foreign Key Constraints...'); 87 | $this->runner->foreigns(); 88 | 89 | $this->info('DONE.'); 90 | 91 | return true; 92 | } 93 | 94 | public function handle() 95 | { 96 | $code = $this->call('jables:check'); 97 | $this->create(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/commands/Prettify.php: -------------------------------------------------------------------------------- 1 | checker = $checker; 20 | $this->prettifyer = $prettifyer; 21 | } 22 | 23 | public function handle() 24 | { 25 | $this->info('Checking for JSON syntax Errors...'); 26 | $errors = $this->checker->syntaxError(); 27 | 28 | if ($errors !== null) { 29 | $this->error($errors); 30 | return false; 31 | } 32 | 33 | $this->info('Prettifying your JSON files...'); 34 | $this->prettifyer->prettify(); 35 | $this->info('Done.'); 36 | } 37 | } -------------------------------------------------------------------------------- /src/commands/Refresh.php: -------------------------------------------------------------------------------- 1 | app = $app; 24 | $this->checker = $checker; 25 | $this->runner = $runner; 26 | $this->destroyer = $destroyer; 27 | } 28 | 29 | public function handle() 30 | { 31 | $this->call('jables:destroy', [ 32 | '--database' => $this->option('database') 33 | ]); 34 | 35 | $this->call('jables', [ 36 | '--engine' => $this->option('engine'), 37 | '--database' => $this->option('database') 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/Tags.php: -------------------------------------------------------------------------------- 1 | app = $app; 19 | $this->tags = $tags; 20 | } 21 | 22 | public function handle() 23 | { 24 | if ($this->argument('tag_name')) { 25 | $this->comment('Tables tagged "'.$this->argument('tag_name').'"'); 26 | 27 | foreach ($this->tags->get($this->argument('tag_name')) as $table) { 28 | $this->line($table); 29 | } 30 | } else { 31 | $this->comment('All Tags'); 32 | 33 | foreach ($this->tags->tags() as $tag) { 34 | if ($this->option('tables')) { 35 | $tables = implode(', ', $this->tags->get($tag)); 36 | $this->line("$tag: $tables"); 37 | } else { 38 | $this->line($tag); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/traits/Checks.php: -------------------------------------------------------------------------------- 1 | info('Checking for JSON syntax Errors...'); 8 | $errors = $this->checker->syntaxError(); 9 | 10 | if ($errors !== null) { 11 | $this->error($errors); 12 | return false; 13 | } 14 | 15 | $this->info('Checking for Schema Errors...'); 16 | $errors = $this->checker->schemaError(); 17 | 18 | if ($errors !== null) { 19 | $this->error(print_r($errors, true)); 20 | return false; 21 | } 22 | 23 | $this->info('Checking for Foreign key Constraint Errors...'); 24 | $errors = $this->checker->foreignKeyError(); 25 | 26 | if ($errors !== null) { 27 | $this->error(print_r($errors, true)); 28 | return false; 29 | } 30 | 31 | $this->info('Checking for Unique Constraint Errors...'); 32 | $errors = $this->checker->uniqueError(); 33 | 34 | if ($errors !== null) { 35 | $this->error(print_r($errors, true)); 36 | return false; 37 | } 38 | 39 | $this->info('--------------------------'); 40 | $this->info('Looks OK! :D'); 41 | echo PHP_EOL; 42 | 43 | return true; 44 | } 45 | } -------------------------------------------------------------------------------- /src/commands/traits/CreatesTable.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hedronium/Jables/f1a16a3bae5b3ce5246a66f72aff5dbae89838d5/src/commands/traits/CreatesTable.php -------------------------------------------------------------------------------- /src/config/jables.php: -------------------------------------------------------------------------------- 1 | 'jables', 4 | 'folder' => 'jables' 5 | ]; -------------------------------------------------------------------------------- /src/exceptions/NameCollisionException.php: -------------------------------------------------------------------------------- 1 | file_1 = $file_1; 12 | $this->file_2 = $file_2; 13 | 14 | parent::__construct("Two files result in the same table name:\n$file_1\n$file_2"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/exceptions/ParseException.php: -------------------------------------------------------------------------------- 1 | table = $table; 12 | $this->exception_message = $exception_message; 13 | 14 | $str = ''; 15 | $str .= 'Table : '.$table. "\n"; 16 | $str .= 'Path : '.$path. "\n"; 17 | $str .= 'Error : '.$exception_message. "\n"; 18 | 19 | parent::__construct($str); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/exceptions/SchemaException.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 11 | 12 | $str = ''; 13 | 14 | foreach ($errors as $error) { 15 | $str .= "-----#!#!#!#!#!#!#!#!----\n"; 16 | $str .= 'Table : '.$error['table']."\n"; 17 | $str .= 'Path : '.$error['path']."\n"; 18 | $str .= 'Property : '.$error['property']."\n"; 19 | $str .= 'Error : '.$error['message']."\n"; 20 | $str .= "\n"; 21 | } 22 | 23 | parent::__construct($str); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/exceptions/TagNotFoundException.php: -------------------------------------------------------------------------------- 1 | tag = $tag; 11 | 12 | parent::__construct("The tag '$tag' was not found."); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/schemas/base_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | 4 | "type": "object", 5 | "properties": { 6 | "type": { 7 | "type": "string" 8 | }, 9 | "title": { 10 | "type": "string" 11 | }, 12 | "description": { 13 | "type": "string" 14 | }, 15 | "primary": { 16 | "type": "boolean" 17 | }, 18 | "unique": { 19 | "type": "boolean" 20 | }, 21 | "index": { 22 | "type": "boolean" 23 | }, 24 | "nullable": { 25 | "type": "boolean" 26 | }, 27 | "foreign": { 28 | "type": "string", 29 | "pattern": "^.*\\..*$" 30 | }, 31 | "default": { 32 | "type": [ 33 | "string", 34 | "number", 35 | "array", 36 | "object", 37 | "boolean", 38 | "null" 39 | ] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/schemas/big-integer.json: -------------------------------------------------------------------------------- 1 | {"$ref":"integer.json#"} -------------------------------------------------------------------------------- /src/schemas/binary.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/boolean.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/char.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "allOf": [ 4 | { "$ref": "string.json#" }, 5 | { 6 | "required": ["length"] 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /src/schemas/date-time.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/date.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/decimal.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "allOf": [ 4 | {"$ref":"base_field.json#"}, 5 | { 6 | "properties": { 7 | "digits": { 8 | "type": "number" 9 | }, 10 | "precision": { 11 | "type": "number" 12 | } 13 | }, 14 | "required": ["digits", "precision"] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /src/schemas/double.json: -------------------------------------------------------------------------------- 1 | {"$ref": "decimal.json#"} -------------------------------------------------------------------------------- /src/schemas/enum.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "allOf": [ 4 | {"$ref":"base_field.json#"}, 5 | { 6 | "properties": { 7 | "values": { 8 | "type": "array", 9 | "minItems": 1, 10 | "uniqueItems": true 11 | } 12 | }, 13 | "required": ["values"] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/schemas/field_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": { 3 | "type": "string", 4 | "enum": [ 5 | "big-integer", 6 | "binary", 7 | "boolean", 8 | "char", 9 | "date-time", 10 | "date", 11 | "decimal", 12 | "double", 13 | "enum", 14 | "float", 15 | "integer", 16 | "json", 17 | "jsonb", 18 | "long-text", 19 | "medium-integer", 20 | "medium-text", 21 | "morphs", 22 | "small-integer", 23 | "string", 24 | "text", 25 | "time", 26 | "tiny-integer", 27 | "timestamp" 28 | ] 29 | } 30 | } -------------------------------------------------------------------------------- /src/schemas/float.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "allOf": [ 4 | {"$ref":"base_field.json#"}, 5 | { 6 | "properties": { 7 | "attributes": { 8 | "type": "array", 9 | "items": { 10 | "type": "string", 11 | "enum": ["unsigned"] 12 | }, 13 | "uniqueItems": true 14 | }, 15 | "ai": { 16 | "type": "boolean" 17 | } 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /src/schemas/json.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/jsonb.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/long-text.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/medium-integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "allOf": [ 4 | {"$ref":"base_field.json#"}, 5 | { 6 | "properties": { 7 | "attributes": { 8 | "type": "array", 9 | "items": { 10 | "type": "string", 11 | "enum": ["unsigned"] 12 | }, 13 | "uniqueItems": true 14 | } 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /src/schemas/medium-text.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/morphs.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/small-integer.json: -------------------------------------------------------------------------------- 1 | {"$ref": "medium-integer.json#"} -------------------------------------------------------------------------------- /src/schemas/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "allOf": [ 4 | {"$ref":"base_field.json#"}, 5 | { 6 | "properties": { 7 | "length": { 8 | "type": "number" 9 | } 10 | } 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /src/schemas/table.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | 4 | "type": "object", 5 | "additionalProperties": false, 6 | "properties": { 7 | "tags": { 8 | "type": "array", 9 | "items": { 10 | "type": "string" 11 | }, 12 | "minItems": 1 13 | }, 14 | "fields": { 15 | "type": "object", 16 | "patternProperties": { 17 | "^soft-deletes$": { 18 | "type": "boolean" 19 | }, 20 | "^timestamps$": { 21 | "type": "boolean" 22 | }, 23 | "^(?!timestamps|soft-deletes).*$": { 24 | "type": "object", 25 | "properties": { 26 | "type": {"$ref": "field_type.json#/type"} 27 | }, 28 | "required": ["type"] 29 | } 30 | }, 31 | "minProperties": 1 32 | }, 33 | "foreign": { 34 | "type": "object", 35 | "patternProperties": { 36 | "^.*$": { 37 | "type": ["string", "object"], 38 | "pattern": "^.*\\..*$", 39 | "additionalProperties": false, 40 | "properties": { 41 | "field": { 42 | "type": "string", 43 | "pattern": "^.*\\..*$" 44 | }, 45 | "onDelete": { 46 | "type": "string", 47 | "enum": ["restrict", "cascade", "no-action", "null"] 48 | }, 49 | "onUpdate": { 50 | "type": "string", 51 | "enum": ["restrict", "cascade", "no-action", "null"] 52 | } 53 | }, 54 | "required": ["field"] 55 | } 56 | }, 57 | "minProperties": 1 58 | }, 59 | "unique": { 60 | "type": "array", 61 | "items": { 62 | "type": "array", 63 | "items": { 64 | "type": "string" 65 | }, 66 | "minItems": 2 67 | }, 68 | "minItems": 1 69 | }, 70 | "primary": { 71 | "type": "array", 72 | "minItems": 2 73 | }, 74 | "title": { 75 | "type": "string" 76 | }, 77 | "description": { 78 | "type": "string" 79 | } 80 | }, 81 | "required": ["fields"] 82 | } 83 | -------------------------------------------------------------------------------- /src/schemas/text.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/time.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/timestamp.json: -------------------------------------------------------------------------------- 1 | {"$ref": "base_field.json#"} -------------------------------------------------------------------------------- /src/schemas/tiny-integer.json: -------------------------------------------------------------------------------- 1 | {"$ref": "medium-integer.json#"} -------------------------------------------------------------------------------- /src/types/BigInteger.php: -------------------------------------------------------------------------------- 1 | definition('ai') === true) { 10 | return $table->bigIncrements($name); 11 | } 12 | 13 | return $table->bigInteger($name); 14 | } 15 | } -------------------------------------------------------------------------------- /src/types/Binary.php: -------------------------------------------------------------------------------- 1 | binary($name); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/Boolean.php: -------------------------------------------------------------------------------- 1 | boolean($name); 11 | } 12 | 13 | public function render(){} 14 | } -------------------------------------------------------------------------------- /src/types/Char.php: -------------------------------------------------------------------------------- 1 | char($name, $this->schema->length); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/Date.php: -------------------------------------------------------------------------------- 1 | date($name); 11 | } 12 | 13 | public function render(){} 14 | 15 | } -------------------------------------------------------------------------------- /src/types/DateTime.php: -------------------------------------------------------------------------------- 1 | dateTime($name); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/Decimal.php: -------------------------------------------------------------------------------- 1 | decimal( 10 | $name, 11 | $this->schema->digits, 12 | $this->schema->precision 13 | ); 14 | } 15 | 16 | public function render(){} 17 | } -------------------------------------------------------------------------------- /src/types/Double.php: -------------------------------------------------------------------------------- 1 | double( 10 | $name, 11 | $this->schema->digits, 12 | $this->schema->precision 13 | ); 14 | } 15 | 16 | public function render(){} 17 | } -------------------------------------------------------------------------------- /src/types/Enum.php: -------------------------------------------------------------------------------- 1 | enum($name, $this->schema->values); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/Float.php: -------------------------------------------------------------------------------- 1 | float($name); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/Integer.php: -------------------------------------------------------------------------------- 1 | definition('ai') === true) { 12 | return $table->increments($name); 13 | } 14 | 15 | return $table->integer($name); 16 | } 17 | 18 | public function attributes() 19 | { 20 | $attributes = $this->definition('attributes'); 21 | if (is_array($attributes) && in_array('unsigned', $attributes)) { 22 | $this->field->unsigned(); 23 | } 24 | } 25 | 26 | public function render() 27 | { 28 | $this->attributes(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/types/Json.php: -------------------------------------------------------------------------------- 1 | json($name); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/Jsonb.php: -------------------------------------------------------------------------------- 1 | jsonb($name); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/LongText.php: -------------------------------------------------------------------------------- 1 | longText($name); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/MediumInteger.php: -------------------------------------------------------------------------------- 1 | mediumInteger($name); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/types/MediumText.php: -------------------------------------------------------------------------------- 1 | mediumText($name); 10 | } 11 | 12 | 13 | public function render() 14 | { 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /src/types/Morphs.php: -------------------------------------------------------------------------------- 1 | morphs($name); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/SmallInteger.php: -------------------------------------------------------------------------------- 1 | smallInteger($name); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/types/String.php: -------------------------------------------------------------------------------- 1 | definition('length')) { 11 | return $table->string($name, $length); 12 | } 13 | 14 | return $table->string($name); 15 | } 16 | 17 | 18 | public function render() 19 | { 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /src/types/Text.php: -------------------------------------------------------------------------------- 1 | text($name); 10 | } 11 | 12 | 13 | public function render(){} 14 | } -------------------------------------------------------------------------------- /src/types/Time.php: -------------------------------------------------------------------------------- 1 | time($name); 11 | } 12 | 13 | public function render(){} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/types/Timestamp.php: -------------------------------------------------------------------------------- 1 | timestamp($name); 10 | } 11 | 12 | public function render(){} 13 | } -------------------------------------------------------------------------------- /src/types/TinyInteger.php: -------------------------------------------------------------------------------- 1 | tinyInteger($name); 9 | } 10 | 11 | } --------------------------------------------------------------------------------