Skip to content

Commit 18ae1e1

Browse files
Register custom type to support MySQL enums (#279)
1 parent a46bb70 commit 18ae1e1

File tree

8 files changed

+161
-2
lines changed

8 files changed

+161
-2
lines changed

src/Commands/TraceCommand.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Blueprint\Commands;
44

55
use Blueprint\Blueprint;
6+
use Blueprint\EnumType;
7+
use Doctrine\DBAL\Types\Type;
68
use Illuminate\Console\Command;
79
use Illuminate\Database\Eloquent\Model;
810
use Illuminate\Filesystem\Filesystem;
@@ -119,13 +121,35 @@ private function extractColumns(Model $model)
119121
$table = $model->getConnection()->getTablePrefix() . $model->getTable();
120122
$schema = $model->getConnection()->getDoctrineSchemaManager();
121123

124+
if (!Type::hasType('enum')) {
125+
Type::addType('enum', EnumType::class);
126+
$databasePlatform = $schema->getDatabasePlatform();
127+
$databasePlatform->registerDoctrineTypeMapping('enum', 'enum');
128+
}
129+
122130
$database = null;
123131
if (strpos($table, '.')) {
124-
list($database, $table) = explode('.', $table);
132+
[$database, $table] = explode('.', $table);
125133
}
126134

127135
$columns = $schema->listTableColumns($table, $database);
128136

137+
$uses_enums = collect($columns)->contains(function ($column) {
138+
return $column->getType() instanceof \Blueprint\EnumType;
139+
});
140+
141+
if ($uses_enums) {
142+
$definitions = $model->getConnection()->getDoctrineConnection()->fetchAll($schema->getDatabasePlatform()->getListTableColumnsSQL($table, $database));
143+
144+
collect($columns)->filter(function ($column) {
145+
return $column->getType() instanceof \Blueprint\EnumType;
146+
})->each(function (&$column, $key) use ($definitions) {
147+
$definition = collect($definitions)->where('Field', $key)->first();
148+
149+
$column->options = \Blueprint\EnumType::extractOptions($definition['Type']);
150+
});
151+
}
152+
129153
return $columns;
130154
}
131155

@@ -160,9 +184,11 @@ public static function columns(\Doctrine\DBAL\Schema\Column $column, string $key
160184
if ($column->getLength() > 65535) {
161185
$type = 'longtext';
162186
}
187+
} elseif ($type === 'enum' && !empty($column->options)) {
188+
$type .= ':' . implode(',', $column->options);
163189
}
164190

165-
// TODO: enums, guid/uuid
191+
// TODO: guid/uuid
166192

167193
$attributes[] = $type;
168194

@@ -201,6 +227,7 @@ private static function translations(string $type)
201227
'datetimetz' => 'datetimetz',
202228
'datetimetz_immutable' => 'datetimetz',
203229
'decimal' => 'decimal',
230+
'enum' => 'enum',
204231
'float' => 'float',
205232
'guid' => 'string',
206233
'integer' => 'integer',

src/EnumType.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace Blueprint;
4+
5+
use Doctrine\DBAL\Platforms\AbstractPlatform;
6+
use Doctrine\DBAL\Types\Type;
7+
8+
class EnumType extends Type
9+
{
10+
const ENUM = 'enum';
11+
12+
protected $values = [];
13+
14+
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
15+
{
16+
$values = array_map(function ($val) {
17+
return "'" . $val . "'";
18+
}, $this->values);
19+
20+
return "ENUM(" . implode(", ", $values) . ")";
21+
}
22+
23+
public function convertToPHPValue($value, AbstractPlatform $platform)
24+
{
25+
return $value;
26+
}
27+
28+
public function convertToDatabaseValue($value, AbstractPlatform $platform)
29+
{
30+
if (!in_array($value, $this->values)) {
31+
throw new \InvalidArgumentException("Invalid '" . $this->getName() . "' value.");
32+
}
33+
return $value;
34+
}
35+
36+
public function getName()
37+
{
38+
return self::ENUM;
39+
}
40+
41+
public static function extractOptions($definition)
42+
{
43+
$options = explode(',', preg_replace('/enum\((?P<options>(.*))\)/', '$1', $definition));
44+
45+
return array_map(function ($option) {
46+
$raw_value = str_replace("''", "'", trim($option, "'"));
47+
48+
if (!preg_match('/\s/', $raw_value)) {
49+
return $raw_value;
50+
}
51+
52+
return sprintf('"%s"', $raw_value);
53+
}, $options);
54+
}
55+
}

src/Lexers/ModelLexer.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ private function buildColumn(string $name, string $definition)
221221
$data_type = self::$dataTypes[strtolower($value)];
222222
if (!empty($attributes)) {
223223
$attributes = explode(',', $attributes);
224+
225+
if ($data_type === 'enum') {
226+
$attributes = array_map(function ($attribute) {
227+
return trim($attribute, '"');
228+
}, $attributes);
229+
}
224230
}
225231
}
226232

tests/Feature/Generator/MigrationGeneratorTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ public function modelTreeDataProvider()
640640
['drafts/uuid-shorthand.yaml', 'database/migrations/timestamp_create_people_table.php', 'migrations/uuid-shorthand.php'],
641641
['drafts/unconventional-foreign-key.yaml', 'database/migrations/timestamp_create_states_table.php', 'migrations/unconventional-foreign-key.php'],
642642
['drafts/resource-statements.yaml', 'database/migrations/timestamp_create_users_table.php', 'migrations/resource-statements.php'],
643+
['drafts/enum-options.yaml', 'database/migrations/timestamp_create_messages_table.php', 'migrations/enum-options.php'],
643644
];
644645
}
645646
}

tests/Feature/Lexers/ModelLexerTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ public function dataTypeAttributesDataProvider()
652652
['char:10', 'char', [10]],
653653
['string:1000', 'string', [1000]],
654654
['enum:one,two,three,four', 'enum', ['one', 'two', 'three', 'four']],
655+
['enum:"Jason McCreary",Shift,O\'Doul', 'enum', ['Jason McCreary', 'Shift', 'O\'Doul']],
655656
['set:1,2,3,4', 'set', [1, 2, 3, 4]],
656657
];
657658
}

tests/Unit/EnumTypeTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Tests\Unit;
4+
5+
use Blueprint\Blueprint;
6+
use Blueprint\Builder;
7+
use Illuminate\Filesystem\Filesystem;
8+
use Tests\TestCase;
9+
10+
/**
11+
* @covers \Blueprint\EnumType
12+
*/
13+
class EnumTypeTest extends TestCase
14+
{
15+
/**
16+
* @test
17+
* @dataProvider enumOptionsDataProvider
18+
*/
19+
public function it_returns_options_for_enum($definition, $expected)
20+
{
21+
$this->assertEquals($expected, \Blueprint\EnumType::extractOptions($definition));
22+
}
23+
24+
public function enumOptionsDataProvider()
25+
{
26+
return [
27+
["enum('1','2','3')", [1, 2, 3]],
28+
["enum('One','Two','Three')", ['One', 'Two', 'Three']],
29+
["enum('Spaced and quoted names','John Doe','Connon O''Brien','O''Doul')", ['"Spaced and quoted names"', '"John Doe"','"Connon O\'Brien"', 'O\'Doul']],
30+
];
31+
}
32+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
models:
2+
Message:
3+
type: enum:SMS,Email,Push
4+
name: enum:1,"Jason McCreary",O'Brein,4 nullable
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
class CreateMessagesTable extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
Schema::create('messages', function (Blueprint $table) {
17+
$table->id();
18+
$table->enum('type', ["SMS","Email","Push"]);
19+
$table->enum('name', ["1","Jason McCreary","O'Brein","4"])->nullable();
20+
$table->timestamps();
21+
});
22+
}
23+
24+
/**
25+
* Reverse the migrations.
26+
*
27+
* @return void
28+
*/
29+
public function down()
30+
{
31+
Schema::dropIfExists('messages');
32+
}
33+
}

0 commit comments

Comments
 (0)