Skip to content

Commit 260ad45

Browse files
author
Nathan Esayeas
authored
support belongs to many relationship (#113)
1 parent eecf9c9 commit 260ad45

File tree

7 files changed

+115
-29
lines changed

7 files changed

+115
-29
lines changed

src/Generators/MigrationGenerator.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class MigrationGenerator implements Generator
2929
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
3030
private $files;
3131

32+
private $pivotTables = [];
33+
3234
public function __construct($files)
3335
{
3436
$this->files = $files;
@@ -48,6 +50,26 @@ public function output(array $tree): array
4850
$this->files->put($path, $this->populateStub($stub, $model));
4951

5052
$output['created'][] = $path;
53+
54+
if (!empty($modelPivots = $model->pivotTables())) {
55+
foreach ($modelPivots as $pivotSegments) {
56+
$pivotTable = $this->getPivotTableName($pivotSegments);
57+
if (!isset($this->pivotTables[$pivotTable])) {
58+
$this->pivotTables[$pivotTable] = [
59+
'tableName' => $pivotTable,
60+
'segments' => $pivotSegments
61+
];
62+
}
63+
}
64+
}
65+
}
66+
67+
if (!empty($this->pivotTables)) {
68+
foreach ($this->pivotTables as $pivotTable) {
69+
$path = $this->getPivotTablePath($pivotTable['tableName'], $sequential_timestamp->addSecond());
70+
$this->files->put($path, $this->populatePivotStub($stub, $pivotTable['segments']));
71+
$output['created'][] = $path;
72+
}
5173
}
5274

5375
return $output;
@@ -62,6 +84,15 @@ protected function populateStub(string $stub, Model $model)
6284
return $stub;
6385
}
6486

87+
protected function populatePivotStub(string $stub, array $segments)
88+
{
89+
$stub = str_replace('DummyClass', $this->getPivotClassName($segments), $stub);
90+
$stub = str_replace('DummyTable', $this->getPivotTableName($segments), $stub);
91+
$stub = str_replace('// definition...', $this->buildPivotTableDefinition($segments), $stub);
92+
93+
return $stub;
94+
}
95+
6596
protected function buildDefinition(Model $model)
6697
{
6798
$definition = '';
@@ -132,6 +163,18 @@ protected function buildDefinition(Model $model)
132163
return trim($definition);
133164
}
134165

166+
protected function buildPivotTableDefinition(array $segments, $dataType = 'bigIncrements')
167+
{
168+
$definition = '';
169+
170+
foreach ($segments as $segment) {
171+
$column = $segment . '_id';
172+
$definition .= self::INDENT . '$table->' . $dataType . "('{$column}');" . PHP_EOL;
173+
}
174+
175+
return trim($definition);
176+
}
177+
135178
protected function getClassName(Model $model)
136179
{
137180
return 'Create' . Str::studly($model->tableName()) . 'Table';
@@ -142,8 +185,27 @@ protected function getPath(Model $model, Carbon $timestamp)
142185
return 'database/migrations/' . $timestamp->format('Y_m_d_His') . '_create_' . $model->tableName() . '_table.php';
143186
}
144187

188+
protected function getPivotTablePath($tableName, Carbon $timestamp)
189+
{
190+
return 'database/migrations/' . $timestamp->format('Y_m_d_His') . '_create_' . $tableName . '_table.php';
191+
}
192+
145193
protected function isLaravel7orNewer()
146194
{
147195
return version_compare(App::version(), '7.0.0', '>=');
148196
}
197+
198+
protected function getPivotClassName(array $segments)
199+
{
200+
return 'Create' . Str::studly($this->getPivotTableName($segments)) . 'PivotTable';
201+
}
202+
203+
protected function getPivotTableName(array $segments)
204+
{
205+
$segments = array_map(function ($name) {
206+
return Str::snake($name);
207+
}, $segments);
208+
sort($segments);
209+
return strtolower(implode('_', $segments));
210+
}
149211
}

src/Generators/ModelGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ private function buildRelationships(Model $model)
135135
$class = Str::studly($class ?? $name);
136136
$relationship = sprintf("\$this->%s(%s::class)", $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class);
137137

138-
$method_name = $type === 'hasMany' ? Str::plural($name) : $name;
138+
$method_name = $type === 'hasMany' || $type === 'belongsToMany' ? Str::plural($name) : $name;
139139
$method = str_replace('DummyName', Str::camel($method_name), $template);
140140
$method = str_replace('null', $relationship, $method);
141141
$methods .= PHP_EOL . $method;

src/Lexers/ModelLexer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ModelLexer implements Lexer
1212
'belongsto' => 'belongsTo',
1313
'hasone' => 'hasOne',
1414
'hasmany' => 'hasMany',
15+
'belongstomany' => 'belongsToMany'
1516
];
1617

1718
private static $dataTypes = [

src/Models/Model.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Model
1313
private $softDeletes = false;
1414
private $columns = [];
1515
private $relationships = [];
16+
private $pivotTables = [];
1617

1718
/**
1819
* @param $name
@@ -143,6 +144,22 @@ public function addRelationship(string $type, string $reference)
143144
$this->relationships[$type] = [];
144145
}
145146

147+
if ($type === 'belongsToMany') {
148+
$this->addPivotTable($reference);
149+
}
150+
146151
$this->relationships[$type][] = $reference;
147152
}
153+
154+
public function addPivotTable(string $reference)
155+
{
156+
$segments = [$this->name(), strtolower($reference)];
157+
sort($segments);
158+
$this->pivotTables[] = $segments;
159+
}
160+
161+
public function pivotTables(): array
162+
{
163+
return $this->pivotTables;
164+
}
148165
}

tests/Feature/Lexers/ModelLexerTest.php

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function it_returns_nothing_without_models_token()
2626
{
2727
$this->assertEquals([
2828
'models' => [],
29-
'cache' => []
29+
'cache' => [],
3030
], $this->subject->analyze([]));
3131
}
3232

@@ -39,11 +39,11 @@ public function it_returns_models()
3939
'models' => [
4040
'ModelOne' => [
4141
'id' => 'id',
42-
'name' => 'string nullable'
42+
'name' => 'string nullable',
4343
],
4444
'ModelTwo' => [
4545
'count' => 'integer',
46-
'timestamps' => 'timestamps'
46+
'timestamps' => 'timestamps',
4747
],
4848
'ModelThree' => [
4949
'id' => 'increments',
@@ -104,8 +104,8 @@ public function it_defaults_the_id_column()
104104
$tokens = [
105105
'models' => [
106106
'Model' => [
107-
'title' => 'string nullable'
108-
]
107+
'title' => 'string nullable',
108+
],
109109
],
110110
];
111111

@@ -140,7 +140,7 @@ public function it_disables_the_id_column()
140140
'models' => [
141141
'Model' => [
142142
'id' => false,
143-
]
143+
],
144144
],
145145
];
146146

@@ -165,7 +165,7 @@ public function it_disables_timestamps()
165165
'models' => [
166166
'Model' => [
167167
'timestamps' => false,
168-
]
168+
],
169169
],
170170
];
171171

@@ -188,8 +188,8 @@ public function it_defaults_to_string_datatype()
188188
$tokens = [
189189
'models' => [
190190
'Model' => [
191-
'title' => 'nullable'
192-
]
191+
'title' => 'nullable',
192+
],
193193
],
194194
];
195195

@@ -225,8 +225,8 @@ public function it_accepts_lowercase_keywords()
225225
'Model' => [
226226
'sequence' => 'unsignedbiginteger autoincrement',
227227
'content' => 'longtext',
228-
'saved_at' => 'timestamptz usecurrent'
229-
]
228+
'saved_at' => 'timestamptz usecurrent',
229+
],
230230
],
231231
];
232232

@@ -260,7 +260,6 @@ public function it_accepts_lowercase_keywords()
260260
$this->assertEquals(['useCurrent'], $columns['saved_at']->modifiers());
261261
}
262262

263-
264263
/**
265264
* @test
266265
* @dataProvider dataTypeAttributesDataProvider
@@ -270,8 +269,8 @@ public function it_handles_data_type_attributes($definition, $data_type, $attrib
270269
$tokens = [
271270
'models' => [
272271
'Model' => [
273-
'column' => $definition
274-
]
272+
'column' => $definition,
273+
],
275274
],
276275
];
277276

@@ -305,8 +304,8 @@ public function it_handles_modifier_attributes($definition, $modifier, $attribut
305304
$tokens = [
306305
'models' => [
307306
'Model' => [
308-
'column' => $definition . ' nullable'
309-
]
307+
'column' => $definition.' nullable',
308+
],
310309
],
311310
];
312311

@@ -339,8 +338,8 @@ public function it_handles_attributes_and_modifiers_with_attributes()
339338
$tokens = [
340339
'models' => [
341340
'Model' => [
342-
'column' => 'string:100 unique charset:utf8'
343-
]
341+
'column' => 'string:100 unique charset:utf8',
342+
],
344343
],
345344
];
346345

@@ -352,7 +351,6 @@ public function it_handles_attributes_and_modifiers_with_attributes()
352351
$this->assertEquals(['100'], $actual->attributes());
353352
}
354353

355-
356354
/**
357355
* @test
358356
*/
@@ -361,8 +359,8 @@ public function it_enables_soft_deletes()
361359
$tokens = [
362360
'models' => [
363361
'Model' => [
364-
'softdeletes' => 'softdeletes'
365-
]
362+
'softdeletes' => 'softdeletes',
363+
],
366364
],
367365
];
368366

@@ -392,19 +390,19 @@ public function it_returns_traced_models()
392390
'models' => [
393391
'NewModel' => [
394392
'id' => 'id',
395-
'name' => 'string nullable'
393+
'name' => 'string nullable',
396394
],
397395
],
398396
'cache' => [
399397
'CachedModelOne' => [
400398
'count' => 'integer',
401-
'timestamps' => 'timestamps'
399+
'timestamps' => 'timestamps',
402400
],
403401
'CachedModelTwo' => [
404402
'id' => 'id',
405-
'name' => 'string nullable'
403+
'name' => 'string nullable',
406404
],
407-
]
405+
],
408406
];
409407

410408
$actual = $this->subject->analyze($tokens);
@@ -470,10 +468,11 @@ public function it_stores_relationships()
470468
'title' => 'string',
471469
'price' => 'float',
472470
'relationships' => [
471+
'belongsToMany' => 'Team',
473472
'hasmany' => 'Order',
474473
'hasOne' => 'Duration, Transaction:tid',
475474
],
476-
]
475+
],
477476
],
478477
];
479478

@@ -493,9 +492,10 @@ public function it_stores_relationships()
493492
$this->assertArrayHasKey('price', $columns);
494493

495494
$relationships = $model->relationships();
496-
$this->assertCount(3, $relationships);
495+
$this->assertCount(4, $relationships);
497496
$this->assertEquals(['user:different_id'], $relationships['belongsTo']);
498497
$this->assertEquals(['Order'], $relationships['hasMany']);
498+
$this->assertEquals(['Team'], $relationships['belongsToMany']);
499499
$this->assertEquals(['Duration', 'Transaction:tid'], $relationships['hasOne']);
500500
}
501501

@@ -518,7 +518,7 @@ public function modifierAttributesProvider()
518518
return [
519519
['default:5', 'default', 5],
520520
['default:0.00', 'default', 0.00],
521-
["default:string", 'default', 'string'],
521+
['default:string', 'default', 'string'],
522522
["default:'empty'", 'default', "'empty'"],
523523
['default:""', 'default', '""'],
524524
['charset:utf8', 'charset', 'utf8'],

tests/fixtures/definitions/model-relationships.bp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ models:
22
Subscription:
33
user_id: id
44
relationships:
5+
belongsToMany: Team
56
hasMany: Order
67
hasOne: Duration, Transaction

tests/fixtures/models/model-relationships.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ class Subscription extends Model
2626
];
2727

2828

29+
public function teams()
30+
{
31+
return $this->belongsToMany(\App\Team::class);
32+
}
33+
2934
public function orders()
3035
{
3136
return $this->hasMany(\App\Order::class);

0 commit comments

Comments
 (0)