Skip to content

Instantly share code, notes, and snippets.

@barooney
Created August 13, 2019 23:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barooney/05d526f0b8d2cc563c968215167b4f5e to your computer and use it in GitHub Desktop.
Save barooney/05d526f0b8d2cc563c968215167b4f5e to your computer and use it in GitHub Desktop.
Create Laravel Models and Migrations from a GraphML file.
<?php /** @noinspection ALL */
namespace App\Console\Commands;
use Fhaculty\Graph\Edge\Directed;
use Fhaculty\Graph\Vertex;
use Graphp\GraphML\Loader;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
class CreateModelsFromGraphCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'graph:create-models {filename}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create Models from GraphML file';
protected $entities = [];
protected $relations = [];
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$path = resource_path('graphml/' . $this->argument('filename'));
$data = file_get_contents($path);
$this->info("reading from " . $path);
$loader = new Loader();
$graph = $loader->loadContents($data);
collect($graph->getVertices())->filter(function (Vertex $v) {
return $v->getAttribute('type') == 'entity';
})
->map(function (Vertex $v) {
$attributes = collect($v->getEdges())->filter(function (Directed $e) {
return $e->getAttribute('relationship') == 'attribute';
})
->map(function (Directed $e) {
return collect(['description', 'datatype', 'constraint', 'nullable', 'required', 'unsigned', 'verifications', 'fillable', 'primary'])->mapWithKeys(function (string $attribute) use ($e) {
return [$attribute => $e->getVertexEnd()->getAttribute($attribute)];
});
})->sort(function ($attr1, $attr2) {
if ($attr1['primary']) return -1;
if ($attr2['primary']) return 1;
return 0;
})->values()->toArray();
$relations = collect($v->getEdges())->filter(function (Directed $e) {
return in_array($e->getAttribute('relationship'), ['many-to-many', 'has-many', 'belongs-to', 'morph-many', 'morph-one', 'morph-to', 'has-one']);
})
->map(function (Directed $e) {
$currentEntity = $e->getVertexStart()->getAttribute('description');
$relationship = $e->getAttribute('relationship');
$property = $e->getAttribute('property');
$otherRelation = null;
$otherEntity = null;
switch ($relationship) {
case 'many-to-many':
$otherEntity = collect($e->getVertexEnd()->getEdges())->filter(function (Directed $ee) use ($e) {
return $e !== $ee;
})->first()->getVertexStart()->getAttribute('description');
break;
case 'morph-to':
$otherEntity = "<polymorphic>";
break;
case 'morph-many':
$otherEntity = collect($e->getVertexEnd()->getEdges())->filter(function (Directed $ee) use ($e) {
return $ee->getAttribute('relationship') == 'morph-to';
})->first();
$otherRelation = $otherEntity->getAttribute('property');
$otherEntity = $otherEntity->getVertexStart()->getAttribute('description');
break;
case 'morph-one':
$otherEntity = collect($e->getVertexEnd()->getEdges())->filter(function (Directed $ee) use ($e) {
return $e !== $ee;
})->first()->getVertexStart()->getAttribute('description');
break;
case 'has-many':
$otherEntity = collect($e->getVertexEnd()->getEdges())->filter(function (Directed $ee) use ($e) {
return $e !== $ee;
})->first()->getVertexStart()->getAttribute('description');
break;
case 'belongs-to':
$otherEntity = collect($e->getVertexEnd()->getEdges())->filter(function (Directed $ee) use ($e) {
return $e !== $ee;
})->first()->getVertexStart()->getAttribute('description');
break;
case 'has-one':
$otherEntity = collect($e->getVertexEnd()->getEdges())->filter(function (Directed $ee) use ($e) {
return $e !== $ee;
})->first()->getVertexStart()->getAttribute('description');
break;
default:
break;
}
return [
'property' => $property,
'relationship' => $relationship,
'otherEntity' => $otherEntity,
'otherRelation' => $otherRelation,
];
})->values();
return [
'name' => $v->getAttribute('description'),
'attributes' => $attributes,
'relations' => $relations,
];
})->each(function (array $entity) {
$this->createMigration($entity);
$this->createEntity($entity);
$this->createRelationMigration($entity);
});
}
private function createEntity(array $entity)
{
$name = $entity['name'];
$attributes = $entity['attributes'];
$relations = $entity['relations'];
$content = $this->modelTemplate($name, $attributes, $relations);
$filename = app_path($name . '.php');
file_put_contents($filename, $content);
return $content;
}
private function createRelationMigration(array $entity) {
$timestamp = Carbon::now()->addSeconds(1)->format('Y_m_d_His');
$entityName = $entity['name'];
$this->info($entityName);
$plural = Str::plural($entityName);
$tableName = Str::snake($plural);
$filename = database_path("migrations/{$timestamp}_add_{$tableName}_relations.php");
$className = "Add" . $entityName . "Relations";
$manyToMany = false;
$lines = collect([]);
$lines = collect($entity['relations'])->map(function($relation) use ($entityName, $lines, $manyToMany) {
// dump($relation);
list($property, $relationship, $otherEntity, $otherRelation) = [$relation['property'], $relation['relationship'], $relation['otherEntity'], $relation['otherRelation']];
$otherTableName = Str::snake(Str::plural($otherEntity));
switch($relationship) {
case 'morph-to':
return "\$table->morphTo('{$property}');";
// $this->info("We have to add a morphTo relation to the {$entityName} named {$property}");
break;
case 'belongs-to':
// dump($relation);
return "\$table->unsignedBigInteger('{$property}_id');\n\$table->foreign('{$property}_id')->references('id')->on('$otherTableName')->onDelete('cascade');";
break;
case 'many-to-many':
$manyToMany = true;
break;
case 'morph-many':
case 'has-many':
default:
$this->info($relationship);
break;
}
})->join("\n");
if (!$manyToMany) {
$content = <<<TPL
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class $className extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table("$tableName", function(Blueprint \$table) {
$lines
});
}
/**
* Reverse the migrations.
*/
public function down()
{
}
}
TPL;
} else {
$className = "Create{$entityName}{$otherEntity}Table";
$tableName = Str::snake(Str::plural($entityName)) . "_" . Str::snake(Str::plural($otherEntity));
$entityTable = Str::snake(Str::plural($entityName));
$entityId = Str::snake($entityName);
$otherTable = Str::snake(Str::plural($otherEntity));
$otherId = Str::snake($otherEntity);
$content = <<<TPL
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class $className extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table("$tableName", function(Blueprint \$table) {
\$table->unsignedBigInteger('{$entityId}_id');
\$table->foreign('{$entityId}_id')->references('id')->on('{$entityTable}');
\$table->unsignedBigInteger('{$otherId}_id');
\$table->foreign('{$otherId}_id')->references('id')->on('{$otherTable}');
});
}
/**
* Reverse the migrations.
*/
public function down()
{
}
}
TPL;
}
file_put_contents($filename, $content);
return $content;
}
private function modelTemplate($name, $attributes, $relations)
{
$relatedModels = $this->getRelatedModels($relations);
$fillableAttributes = $this->getFillableAttributes($attributes);
$gettersAndSetters = $this->getGettersAndSetters($attributes);
$relationMethods = $this->getRelationMethods($relations);
return <<<TPL
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
$relatedModels
class $name extends Model {
protected \$fillable = [$fillableAttributes];
$gettersAndSetters
$relationMethods
}
TPL;
}
private function getRelatedModels($relations)
{
return collect($relations)->pluck('otherEntity')->unique()->filter(function ($model) {
return $model !== '<polymorphic>';
})->map(function ($model) {
return "use " . $this->namespacify($model) . ";";
})->join("\n");
}
private function namespacify($model)
{
return "App\\{$model}";
}
private function getFillableAttributes($attributes)
{
return collect($attributes)->filter(function ($attribute) {
return $attribute['fillable'];
})->map(function ($attr) {
return "'" . $attr['description'] . "'";
})->join(', ');
}
private function getGettersAndSetters($attributes)
{
return collect($attributes)->map(function ($attr) {
$paramName = $attr['description'];
$methodName = Str::studly($attr['description']);
$tableName = Str::snake($attr['description']);
return <<<TPL
public function get$methodName() {
return \$this->attributes['$tableName'];
}
public function set$methodName(\$$paramName) {
\$this->attributes['$tableName'] = \$$paramName;
}
TPL;
})->join("\n");
}
private function getRelationMethods($relations)
{
return collect($relations)->map(function ($relation) {
$relationship = "NOT SUPPORTED";
$property = $relation['property'];
$otherEntity = $relation['otherEntity'] . '::class';
$otherRelation = $relation['otherRelation'];
switch ($relation['relationship']) {
case 'many-to-many':
$relationship = 'belongsToMany(' . $otherEntity . ')';
break;
case 'morph-to':
$relationship = 'morphTo()';
break;
case 'morph-many':
$relationship = 'morphMany(' . $otherEntity . ', \'' . $otherRelation . '\')';
break;
case 'morph-one':
$relationship = 'morphOne(' . $otherEntity . ')';
break;
case 'has-many':
$relationship = 'hasMany(' . $otherEntity . ')';
break;
case 'belongs-to':
$relationship = 'belongsTo(' . $otherEntity . ')';
break;
case 'has-one':
$relationship = 'hasOne(' . $otherEntity . ')';
break;
default:
break;
}
return <<<TPL
public function $property() {
return \$this->$relationship;
}
TPL;
})->join("\n\n");
}
private function createMigration(array $entity)
{
$content = $this->databaseMigrationTemplate($entity);
$tableName = Str::snake(Str::plural($entity['name']));
$timestamp = Carbon::now()->format('Y_m_d_His');
$filename = database_path("migrations/{$timestamp}_create_{$tableName}_table.php");
file_put_contents($filename, $content);
return $content;
}
private function databaseMigrationTemplate(array $entity)
{
$plural = Str::plural($entity['name']);
$tableName = Str::snake(Str::plural($entity['name']));
$tableColumns = $this->getTableColumns($entity['attributes']);
$template = <<<TPL
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class Create{$plural}Table extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('$tableName', function (Blueprint \$table) {
$tableColumns
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('$tableName');
}
}
TPL;
return $template;
}
private function getTableColumns(array $attributes)
{
return collect($attributes)->map(function ($attr) {
$type = null;
$columnName = Str::snake($attr['description']);
$primary = $attr['primary'];
$unsigned = $attr['unsigned'] == 0 ? '->unsigned()' : '';
$constraint = $attr['constraint'];
$line = "\$table->";
switch ($attr['datatype']) {
case 'string':
if ($attr['constraint']) {
$line .= 'string(\'' . $columnName . '\', ' . $constraint . ')';
} else {
$line .= 'text(\'' . $columnName . '\')';
}
break;
case 'long':
if ($primary) {
$line .= 'bigIncrements(\'' . $columnName . '\')' . $unsigned;
} else {
$line .= 'integer(\'' . $columnName . '\')' . $unsigned;
}
break;
case 'DateTime':
$line .= 'dateTime(\'' . $columnName . '\')';
break;
default:
$line = '// currently not supported type for column ' . $columnName . " (type is " . $attr['datatype'] . ')';
break;
}
return <<<TPL
$line;
TPL;
})->join("\n");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment