Skip to content

Commit 3e23519

Browse files
Introduce meta key for customizations (#577)
1 parent 5ba6c44 commit 3e23519

19 files changed

+596
-76
lines changed

src/Generators/MigrationGenerator.php

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,12 @@ protected function buildDefinition(Model $model)
161161
if ($column->name() === 'id' && $dataType === 'id') {
162162
$dataType = 'bigIncrements';
163163
} elseif ($dataType === 'id') {
164-
$dataType = 'unsignedBigInteger';
164+
if ($model->isPivot()) {
165+
// TODO: what if constraints are enabled?
166+
$dataType = 'foreignId';
167+
} else {
168+
$dataType = 'unsignedBigInteger';
169+
}
165170
}
166171

167172
if (in_array($dataType, self::UNSIGNABLE_TYPES) && in_array('unsigned', $column->modifiers())) {
@@ -404,24 +409,18 @@ protected function getTablePath($tableName, Carbon $timestamp, $overwrite = fals
404409

405410
if ($overwrite) {
406411
$migrations = collect($this->filesystem->files($dir))
407-
->filter(
408-
fn (SplFileInfo $file) => str_contains($file->getFilename(), $name)
409-
)
412+
->filter(fn (SplFileInfo $file) => str_contains($file->getFilename(), $name))
410413
->sort();
411414

412415
if ($migrations->isNotEmpty()) {
413416
$migration = $migrations->first()->getPathname();
414417

415418
$migrations->diff($migration)
416-
->each(
417-
function (SplFileInfo $file) {
418-
$path = $file->getPathname();
419-
420-
$this->filesystem->delete($path);
421-
422-
$this->output['deleted'][] = $path;
423-
}
424-
);
419+
->each(function (SplFileInfo $file) {
420+
$path = $file->getPathname();
421+
$this->filesystem->delete($path);
422+
$this->output['deleted'][] = $path;
423+
});
425424

426425
return $migration;
427426
}
@@ -430,33 +429,18 @@ function (SplFileInfo $file) {
430429
return $dir . $timestamp->format('Y_m_d_His') . $name;
431430
}
432431

433-
protected function getPivotClassName(array $segments)
434-
{
435-
return 'Create' . Str::studly($this->getPivotTableName($segments)) . 'Table';
436-
}
437-
438-
protected function getPolyClassName(string $parentTable)
439-
{
440-
return 'Create' . Str::studly($this->getPolyTableName($parentTable)) . 'Table';
441-
}
442-
443432
protected function getPivotTableName(array $segments)
444433
{
445434
$isCustom = collect($segments)
446-
->filter(
447-
fn ($segment) => Str::contains($segment, ':')
448-
)->first();
435+
->filter(fn ($segment) => Str::contains($segment, ':'))->first();
449436

450437
if ($isCustom) {
451438
$table = Str::after($isCustom, ':');
452439

453440
return $table;
454441
}
455442

456-
$segments = array_map(
457-
fn ($name) => Str::snake($name),
458-
$segments
459-
);
443+
$segments = array_map(fn ($name) => Str::snake($name), $segments);
460444
sort($segments);
461445

462446
return strtolower(implode('_', $segments));
@@ -492,9 +476,7 @@ protected function isNumericDefault(string $type, string $value): bool
492476
}
493477

494478
return collect(self::UNSIGNABLE_TYPES)
495-
->contains(
496-
fn ($value) => strtolower($value) === strtolower($type)
497-
);
479+
->contains(fn ($value) => strtolower($value) === strtolower($type));
498480
}
499481

500482
protected function isIdOrUuid(string $dataType)

src/Generators/ModelGenerator.php

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,22 @@ public function output(Tree $tree): array
3030
return $this->output;
3131
}
3232

33+
private function pivotColumns(array $columns, array $relationships): array
34+
{
35+
// TODO: ideally restrict to only "belongsTo" columns used for pivot relationship
36+
return collect($columns)
37+
->map(fn ($column) => $column->name())
38+
->reject(fn ($column) => in_array($column, ['created_at', 'updated_at']) || in_array($column, $relationships['belongsTo'] ?? []))
39+
->all();
40+
}
41+
3342
protected function populateStub(string $stub, Model $model)
3443
{
44+
if ($model->isPivot()) {
45+
$stub = str_replace('class {{ class }} extends Model', 'class {{ class }} extends Pivot', $stub);
46+
$stub = str_replace('use Illuminate\\Database\\Eloquent\\Model;', 'use Illuminate\\Database\\Eloquent\\Relations\\Pivot;', $stub);
47+
}
48+
3549
$stub = str_replace('{{ namespace }}', $model->fullyQualifiedNamespace(), $stub);
3650
$stub = str_replace(PHP_EOL . 'class {{ class }}', $this->buildClassPhpDoc($model) . PHP_EOL . 'class {{ class }}', $stub);
3751
$stub = str_replace('{{ class }}', $model->name(), $stub);
@@ -103,34 +117,42 @@ protected function buildClassPhpDoc(Model $model)
103117

104118
protected function buildProperties(Model $model)
105119
{
106-
$properties = '';
120+
$properties = [];
121+
122+
if ($model->usesCustomTableName() || $model->isPivot()) {
123+
$properties[] = str_replace('{{ name }}', $model->tableName(), $this->filesystem->stub('model.table.stub'));
124+
}
107125

108126
if (!$model->usesTimestamps()) {
109-
$properties .= $this->filesystem->stub('model.timestamps.stub');
127+
$properties[] = $this->filesystem->stub('model.timestamps.stub');
128+
}
129+
130+
if ($model->isPivot() && $model->usesPrimaryKey()) {
131+
$properties[] = $this->filesystem->stub('model.incrementing.stub');
110132
}
111133

112134
if (config('blueprint.use_guarded')) {
113-
$properties .= $this->filesystem->stub('model.guarded.stub');
135+
$properties[] = $this->filesystem->stub('model.guarded.stub');
114136
} else {
115137
$columns = $this->fillableColumns($model->columns());
116138
if (!empty($columns)) {
117-
$properties .= PHP_EOL . str_replace('[]', $this->pretty_print_array($columns, false), $this->filesystem->stub('model.fillable.stub'));
139+
$properties[] = str_replace('[]', $this->pretty_print_array($columns, false), $this->filesystem->stub('model.fillable.stub'));
118140
} else {
119-
$properties .= $this->filesystem->stub('model.fillable.stub');
141+
$properties[] = $this->filesystem->stub('model.fillable.stub');
120142
}
121143
}
122144

123145
$columns = $this->hiddenColumns($model->columns());
124146
if (!empty($columns)) {
125-
$properties .= PHP_EOL . str_replace('[]', $this->pretty_print_array($columns, false), $this->filesystem->stub('model.hidden.stub'));
147+
$properties[] = str_replace('[]', $this->pretty_print_array($columns, false), $this->filesystem->stub('model.hidden.stub'));
126148
}
127149

128150
$columns = $this->castableColumns($model->columns());
129151
if (!empty($columns)) {
130-
$properties .= PHP_EOL . str_replace('[]', $this->pretty_print_array($columns), $this->filesystem->stub('model.casts.stub'));
152+
$properties[] = str_replace('[]', $this->pretty_print_array($columns), $this->filesystem->stub('model.casts.stub'));
131153
}
132154

133-
return trim($properties);
155+
return trim(implode(PHP_EOL, array_filter($properties, fn ($property) => !empty(trim($property)))));
134156
}
135157

136158
protected function buildRelationships(Model $model)
@@ -146,6 +168,7 @@ protected function buildRelationships(Model $model)
146168
foreach ($model->relationships() as $type => $references) {
147169
foreach ($references as $reference) {
148170
$is_model_fqn = Str::startsWith($reference, '\\');
171+
$is_pivot = false;
149172

150173
$custom_template = $template;
151174
$key = null;
@@ -157,7 +180,13 @@ protected function buildRelationships(Model $model)
157180
if (Str::contains($reference, ':')) {
158181
[$foreign_reference, $column_name] = explode(':', $reference);
159182

160-
$method_name = Str::beforeLast($column_name, '_id');
183+
if (Str::startsWith($column_name, '&')) {
184+
$is_pivot = true;
185+
$column_name = Str::after($column_name, '&');
186+
$method_name = $column_name;
187+
} else {
188+
$method_name = Str::beforeLast($column_name, '_id');
189+
}
161190

162191
if (Str::contains($foreign_reference, '.')) {
163192
[$class, $key] = explode('.', $foreign_reference);
@@ -192,7 +221,22 @@ protected function buildRelationships(Model $model)
192221
} elseif (!is_null($key)) {
193222
$relationship = sprintf('$this->%s(%s::class, \'%s\', \'%s\')', $type, $fqcn, $column_name, $key);
194223
} elseif (!is_null($class) && $type === 'belongsToMany') {
195-
$relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, $fqcn, $column_name);
224+
if ($is_pivot) {
225+
$relationship = sprintf('$this->%s(%s::class)', $type, $fqcn);
226+
$relationship .= sprintf('%s->using(%s::class)', PHP_EOL . str_pad(' ', 12), $column_name);
227+
$relationship .= sprintf('%s->as(\'%s\')', PHP_EOL . str_pad(' ', 12), Str::snake($column_name));
228+
229+
$foreign = $this->tree->modelForContext($column_name);
230+
$columns = $this->pivotColumns($foreign->columns(), $foreign->relationships());
231+
if ($columns) {
232+
$relationship .= sprintf('%s->withPivot(\'%s\')', PHP_EOL . str_pad(' ', 12), implode("', '", $columns));
233+
}
234+
if ($foreign->usesTimestamps()) {
235+
$relationship .= sprintf('%s->withTimestamps()', PHP_EOL . str_pad(' ', 12));
236+
}
237+
} else {
238+
$relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, $fqcn, $column_name);
239+
}
196240
$column_name = $class;
197241
} else {
198242
$relationship = sprintf('$this->%s(%s::class)', $type, $fqcn);

src/Lexers/ModelLexer.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,18 @@ private function buildModel(string $name, array $columns)
130130
{
131131
$model = new Model($name);
132132

133+
if (isset($columns['meta']) && is_array($columns['meta'])) {
134+
if (isset($columns['meta']['table'])) {
135+
$model->setTableName($columns['meta']['table']);
136+
}
137+
138+
if (!empty($columns['meta']['pivot'])) {
139+
$model->setPivot();
140+
}
141+
142+
unset($columns['meta']);
143+
}
144+
133145
if (isset($columns['id'])) {
134146
if ($columns['id'] === false) {
135147
$model->disablePrimaryKey();

src/Models/Model.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ class Model implements BlueprintModel
1111

1212
private $namespace;
1313

14+
private $pivot = false;
15+
1416
private $primaryKey = 'id';
1517

1618
private $timestamps = 'timestamps';
1719

1820
private $softDeletes = false;
1921

22+
private $table;
23+
2024
private $columns = [];
2125

2226
private $relationships = [];
@@ -100,9 +104,29 @@ public function disablePrimaryKey()
100104
$this->primaryKey = false;
101105
}
102106

107+
public function isPivot()
108+
{
109+
return $this->pivot;
110+
}
111+
112+
public function setPivot()
113+
{
114+
$this->pivot = true;
115+
}
116+
117+
public function usesCustomTableName()
118+
{
119+
return isset($this->table);
120+
}
121+
103122
public function tableName()
104123
{
105-
return Str::snake(Str::pluralStudly($this->name));
124+
return $this->table ?? Str::snake(Str::pluralStudly($this->name));
125+
}
126+
127+
public function setTableName($name)
128+
{
129+
$this->table = $name;
106130
}
107131

108132
public function timestampsDataType(): string
@@ -173,6 +197,10 @@ public function addPolymorphicManyToManyTable(string $reference)
173197

174198
public function addPivotTable(string $reference)
175199
{
200+
if (str_contains($reference, ':&')) {
201+
return;
202+
}
203+
176204
$segments = [$this->name(), class_basename($reference)];
177205
sort($segments);
178206
$this->pivotTables[] = $segments;

stubs/model.incrementing.stub

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Indicates if the IDs are auto-incrementing.
3+
*
4+
* @var bool
5+
*/
6+
public $incrementing = true;

stubs/model.table.stub

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* The table associated with the model.
3+
*
4+
* @var string
5+
*/
6+
protected $table = '{{ name }}';

tests/Feature/Generators/MigrationGeneratorTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,37 @@ public function output_respects_softdelete_order()
660660
$this->assertEquals(['created' => [$timestamp_path]], $this->subject->output($tree));
661661
}
662662

663+
/**
664+
* @test
665+
*/
666+
public function output_generates_custom_pivot_tables()
667+
{
668+
$this->filesystem->expects('stub')
669+
->with('migration.stub')
670+
->andReturn($this->stub('migration.stub'));
671+
672+
$now = Carbon::now();
673+
Carbon::setTestNow($now);
674+
675+
$user_migration = str_replace('timestamp', $now->copy()->subSeconds(2)->format('Y_m_d_His'), 'database/migrations/timestamp_create_users_table.php');
676+
$team_migration = str_replace('timestamp', $now->copy()->subSecond()->format('Y_m_d_His'), 'database/migrations/timestamp_create_teams_table.php');
677+
$pivot_migration = str_replace('timestamp', $now->format('Y_m_d_His'), 'database/migrations/timestamp_create_team_user_table.php');
678+
679+
$this->filesystem->expects('exists')->times(3)->andReturn(false);
680+
681+
$this->filesystem->expects('put')
682+
->with($user_migration, $this->fixture('migrations/custom_pivot_users_table.php'));
683+
$this->filesystem->expects('put')
684+
->with($team_migration, $this->fixture('migrations/custom_pivot_teams_table.php'));
685+
$this->filesystem->expects('put')
686+
->with($pivot_migration, $this->fixture('migrations/custom_pivot_team_user_table.php'));
687+
688+
$tokens = $this->blueprint->parse($this->fixture('drafts/custom-pivot.yaml'));
689+
$tree = $this->blueprint->analyze($tokens);
690+
691+
$this->assertEquals(['created' => [$user_migration, $team_migration, $pivot_migration]], $this->subject->output($tree));
692+
}
693+
663694
public function modelTreeDataProvider()
664695
{
665696
return [
@@ -686,6 +717,7 @@ public function modelTreeDataProvider()
686717
['drafts/boolean-column-default.yaml', 'database/migrations/timestamp_create_posts_table.php', 'migrations/boolean-column-default.php'],
687718
['drafts/foreign-with-class.yaml', 'database/migrations/timestamp_create_events_table.php', 'migrations/foreign-with-class.php'],
688719
['drafts/full-text.yaml', 'database/migrations/timestamp_create_posts_table.php', 'migrations/full-text.php'],
720+
['drafts/model-with-meta.yaml', 'database/migrations/timestamp_create_post_table.php', 'migrations/model-with-meta.php'],
689721
];
690722
}
691723
}

0 commit comments

Comments
 (0)