Skip to content
Closed
6 changes: 6 additions & 0 deletions .github/ISSUE_TEMPLATE/report-a-bug.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ assignees: ''


### Steps To Reproduce:


### draft.yaml:
```yaml
# PLEASE PASTE YOUR YAML HERE
```
65 changes: 58 additions & 7 deletions src/Generators/FactoryGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class FactoryGenerator implements Generator
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $files;

private $imports = [];

public function __construct($files)
{
$this->files = $files;
Expand All @@ -27,6 +29,9 @@ public function output(array $tree): array

/** @var \Blueprint\Models\Model $model */
foreach ($tree['models'] as $model) {
$this->addImport($model, 'Faker\Generator as Faker');
$this->addImport($model, $model->fullyQualifiedClassName());

$path = $this->getPath($model);

if (!$this->files->exists(dirname($path))) {
Expand All @@ -53,9 +58,9 @@ protected function getPath(Model $model)

protected function populateStub(string $stub, Model $model)
{
$stub = str_replace('DummyModel', $model->fullyQualifiedClassName(), $stub);
$stub = str_replace('DummyClass', $model->name(), $stub);
$stub = str_replace('// definition...', $this->buildDefinition($model), $stub);
$stub = str_replace('// imports...', $this->buildImports($model), $stub);

return $stub;
}
Expand All @@ -72,15 +77,41 @@ protected function buildDefinition(Model $model)
continue;
}

if (in_array($column->dataType(), ['id', 'uuid'])) {
$foreign = $column->isForeignKey();
$foreign = $column->isForeignKey();
if ($foreign) {
$table = Str::beforeLast($column->name(), '_id');
$key = 'id';

if (Str::contains($foreign, '.')) {
[$table, $key] = explode('.', $foreign);
} elseif ($foreign !== 'foreign') {
$table = $foreign;

if (Str::startsWith($column->name(), $foreign . '_')) {
$key = Str::after($column->name(), $foreign . '_');
} elseif (Str::startsWith($column->name(), Str::snake(Str::singular($foreign)) . '_')) {
$key = Str::after($column->name(), Str::snake(Str::singular($foreign)) . '_');
} elseif (!Str::endsWith($column->name(), '_id')) {
$key = $column->name();
}
}

$class = Str::studly(Str::singular($table));

if ($foreign && $foreign !== 'foreign') {
$class = Str::studly(Str::singular($foreign));
if ($key === 'id') {
$definition .= self::INDENT . "'{$column->name()}' => ";
$definition .= sprintf('factory(%s::class)', '\\' . $model->fullyQualifiedNamespace() . '\\' . $class);
$definition .= ',' . PHP_EOL;
} else {
$name = Str::beforeLast($column->name(), '_id');
$class = Str::studly($column->attributes()[0] ?? $name);
$definition .= self::INDENT . "'{$column->name()}' => function () {";
$definition .= PHP_EOL;
$definition .= self::INDENT . ' ' . sprintf('return factory(%s::class)->create()->%s;', '\\' . $model->fullyQualifiedNamespace() . '\\' . $class, $key);
$definition .= PHP_EOL;
$definition .= self::INDENT . '},' . PHP_EOL;
}
} elseif (in_array($column->dataType(), ['id', 'uuid'])) {
$name = Str::beforeLast($column->name(), '_id');
$class = Str::studly($column->attributes()[0] ?? $name);

$definition .= self::INDENT . "'{$column->name()}' => ";
$definition .= sprintf('factory(%s::class)', '\\' . $model->fullyQualifiedNamespace() . '\\' . $class);
Expand Down Expand Up @@ -118,6 +149,11 @@ protected function buildDefinition(Model $model)
}
$definition .= sprintf('%s%s => $faker->%s,%s', self::INDENT, "'{$column->name()}_id'", self::fakerDataType('id'), PHP_EOL);
$definition .= sprintf('%s%s => $faker->%s,%s', self::INDENT, "'{$column->name()}_type'", self::fakerDataType('string'), PHP_EOL);
} elseif ($column->dataType() === 'rememberToken') {
$this->addImport($model, 'Illuminate\Support\Str');
$definition .= self::INDENT . "'{$column->name()}' => ";
$definition .= 'Str::random(10)';
$definition .= ',' . PHP_EOL;
} else {
$definition .= self::INDENT . "'{$column->name()}' => ";
$faker = self::fakerData($column->name()) ?? self::fakerDataType($column->dataType());
Expand All @@ -129,6 +165,21 @@ protected function buildDefinition(Model $model)
return trim($definition);
}

private function addImport(Model $model, $class)
{
$this->imports[$model->name()][] = $class;
}

private function buildImports(Model $model)
{
$imports = array_unique($this->imports[$model->name()]);
sort($imports);

return implode(PHP_EOL, array_map(function ($class) {
return 'use ' . $class . ';';
}, $imports));
}

private function fillableColumns(array $columns): array
{
if (config('blueprint.fake_nullables')) {
Expand Down
26 changes: 22 additions & 4 deletions src/Generators/MigrationGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ protected function buildDefinition(Model $model)
$column_definition = self::INDENT;
if ($dataType === 'bigIncrements' && $this->isLaravel7orNewer()) {
$column_definition .= '$table->id(';
} elseif ($dataType === 'rememberToken') {
$column_definition .= '$table->rememberToken(';
} else {
$column_definition .= '$table->' . $dataType . "('{$column->name()}'";
}
Expand Down Expand Up @@ -176,6 +178,11 @@ protected function buildDefinition(Model $model)
$definition .= self::INDENT . '$table->' . $model->softDeletesDataType() . '();' . PHP_EOL;
}

if ($model->morphTo()) {
$definition .= self::INDENT . sprintf('$table->unsignedBigInteger(\'%s\');', Str::lower($model->morphTo() . "_id")) . PHP_EOL;
$definition .= self::INDENT . sprintf('$table->string(\'%s\');', Str::lower($model->morphTo() . "_type")) . PHP_EOL;
}

if ($model->usesTimestamps()) {
$definition .= self::INDENT . '$table->' . $model->timestampsDataType() . '();' . PHP_EOL;
}
Expand All @@ -192,15 +199,19 @@ protected function buildPivotTableDefinition(array $segments)
$references = 'id';
$on = Str::plural($column);
$foreign = Str::singular($column) . '_' . $references;
$table = $foreign;
if (Str::contains($foreign, ':')) {
[, $table] = explode(':', $foreign);
}

if (!$this->isLaravel7orNewer()) {
$definition .= self::INDENT . '$table->unsignedBigInteger(\'' . $foreign . '\');' . PHP_EOL;
$definition .= self::INDENT . '$table->unsignedBigInteger(\'' . $table . '\');' . PHP_EOL;
}

if (config('blueprint.use_constraints')) {
$definition .= $this->buildForeignKey($foreign, $on, 'id') . ';' . PHP_EOL;
$definition .= $this->buildForeignKey($table, $on, 'id') . ';' . PHP_EOL;
} elseif ($this->isLaravel7orNewer()) {
$definition .= self::INDENT . '$table->foreignId(\'' . $foreign . '\');' . PHP_EOL;
$definition .= self::INDENT . '$table->foreignId(\'' . $table . '\');' . PHP_EOL;
}
}

Expand Down Expand Up @@ -270,9 +281,16 @@ protected function getPivotClassName(array $segments)
protected function getPivotTableName(array $segments)
{
$segments = array_map(function ($name) {
return Str::snake($name);
$table = $name;

if (Str::contains($name, ':')) {
[,$table] = explode(':', $name);
}

return Str::snake($table);
}, $segments);
sort($segments);

return strtolower(implode('_', $segments));
}

Expand Down
48 changes: 40 additions & 8 deletions src/Generators/ModelGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,49 @@ private function buildRelationships(Model $model)

foreach ($model->relationships() as $type => $references) {
foreach ($references as $reference) {
$key = null;
$class = null;

$column_name = $reference;
$method_name = Str::beforeLast($reference, '_id');

if (Str::contains($reference, ':')) {
[$class, $name] = explode(':', $reference);
} else {
$name = $reference;
$class = null;
[$foreign_reference, $column_name] = explode(':', $reference);
$method_name = Str::beforeLast($column_name, '_id');

if (Str::contains($foreign_reference, '.')) {
[$class, $key] = explode('.', $foreign_reference);

if ($key === 'id') {
$key = null;
} else {
$method_name = Str::lower($class);
}
} else {
$class = $foreign_reference;
}
}

$name = Str::beforeLast($name, '_id');
$class = Str::studly($class ?? $name);
$relationship = sprintf("\$this->%s(%s::class)", $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class);
$relationshipClass = Str::studly($class ?? $method_name);

if ($type === 'morphTo') {
$relationship = sprintf('$this->%s()', $type);
} elseif ($type === 'morphMany' || $type === 'morphOne') {
$relation = Str::lower(Str::singular($column_name)) . 'able';
$relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $relationshipClass, $relation);
} elseif (!is_null($key)) {
$relationship = sprintf('$this->%s(%s::class, \'%s\', \'%s\')', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $relationshipClass, $column_name, $key);
} elseif (!is_null($class) && $type === 'belongsToMany') {
$relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $relationshipClass, $method_name);
} else {
$relationship = sprintf('$this->%s(%s::class)', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $relationshipClass);
}

$method_name = $type === 'hasMany' || $type === 'belongsToMany' ? Str::plural($name) : $name;
if ($type === 'morphTo') {
$method_name = Str::lower($relationshipClass);
} elseif (in_array($type, ['hasMany', 'belongsToMany', 'morphMany'])) {
$method_name = Str::plural($column_name);
}
$method = str_replace('DummyName', Str::camel($method_name), $template);
$method = str_replace('null', $relationship, $method);

Expand All @@ -175,6 +206,7 @@ private function fillableColumns(array $columns)
'deleted_at',
'created_at',
'updated_at',
'remember_token',
]);
}

Expand Down
21 changes: 11 additions & 10 deletions src/Generators/RouteGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,22 @@ public function output(array $tree): array
return [];
}

$routes = '';
$path = 'routes/web.php';
$routes = ['api' => '', 'web' => ''];

/** @var \Blueprint\Models\Controller $controller */
foreach ($tree['controllers'] as $controller) {
$routes .= PHP_EOL . PHP_EOL . $this->buildRoutes($controller);

if ($controller->isApiResource()) {
$path = 'routes/api.php';
}
$type = $controller->isApiResource() ? 'api' : 'web';
$routes[$type] .= PHP_EOL . PHP_EOL . $this->buildRoutes($controller);
}
$routes .= PHP_EOL;

$this->files->append($path, $routes);
$paths = [];
foreach (array_filter($routes) as $type => $definitions) {
$path = 'routes/' . $type . '.php';
$this->files->append($path, $definitions . PHP_EOL);
$paths[] = $path;
}

return ['updated' => [$path]];
return ['updated' => $paths];
}

protected function buildRoutes(Controller $controller)
Expand Down
23 changes: 10 additions & 13 deletions src/Lexers/ControllerLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ public function analyze(array $tokens): array
foreach ($tokens['controllers'] as $name => $definition) {
$controller = new Controller($name);

if ($this->isResource($definition)) {
$original = $definition;
$definition = $this->generateResourceTokens($controller, $this->methodsForResource($definition['resource']));
// unset shorthand
unset($original['resource']);
// this gives the ability to both use a shorthand and override some methods
$definition = array_merge($definition, $original);
$controller->setApiResource(true);
if (isset($definition['resource'])) {
$resource_definition = $this->generateResourceTokens($controller, $this->methodsForResource($definition['resource']));

if ($definition['resource'] === 'api') {
$controller->setApiResource(true);
}

unset($definition['resource']);

$definition = array_merge($resource_definition, $definition);
}

foreach ($definition as $method => $body) {
Expand All @@ -49,11 +51,6 @@ public function analyze(array $tokens): array
return $registry;
}

private function isResource(array $definition)
{
return isset($definition['resource']) && is_string($definition['resource']);
}

private function generateResourceTokens(Controller $controller, array $methods)
{
return collect($this->resourceTokens())
Expand Down
Loading