Skip to content

Commit

Permalink
Merge pull request #41 from daverogers/add-config
Browse files Browse the repository at this point in the history
Use Config to Generate Better Data
  • Loading branch information
michael-mcmullen authored Oct 7, 2020
2 parents bd14fd3 + 79e3354 commit 80c9b8b
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 40 deletions.
40 changes: 40 additions & 0 deletions config/modelfromtable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

return [

/*
|--------------------------------------------------------------------------
| Laracademy Generators
|--------------------------------------------------------------------------
|
| A tool set that helps speed up the development process of a Laravel application.
|
| Documentation and support:
| https://github.com/laracademy/generators
|
*/

'connection' => '',

'namespace' => '',

'table' => '',

'primaryKey' => 'id',

'folder' => '',

'debug' => false,

'all' => false,

'singular' => false,

'overwrite' => false,

'blacklist' => ['migrations'],

'whitelist' => [],

'delimiter' => ', '
];
170 changes: 140 additions & 30 deletions src/Commands/ModelFromTableCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ class ModelFromTableCommand extends Command
protected $signature = 'generate:modelfromtable
{--table= : a single table or a list of tables separated by a comma (,)}
{--connection= : database connection to use, leave off and it will use the .env connection}
{--debug : turns on debugging}
{--debug= : turns on debugging}
{--folder= : by default models are stored in app, but you can change that}
{--namespace= : by default the namespace that will be applied to all models is App}
{--singular : class name and class file name singular or plural}
{--all : run for all tables}';
{--all= : run for all tables}
{--overwrite= : overwrite model(s) if exists}
{--timestamps= : whether to timestamp or not}';

/**
* The console command description.
Expand All @@ -30,6 +32,7 @@ class ModelFromTableCommand extends Command
*/
protected $description = 'Generate models for the given tables based on their columns';

public $fieldsDocBlock;
public $fieldsFillable;
public $fieldsHidden;
public $fieldsCast;
Expand All @@ -38,6 +41,7 @@ class ModelFromTableCommand extends Command

public $debug;
public $options;
private $delimiter;

public $databaseConnection;

Expand All @@ -52,12 +56,16 @@ public function __construct()

$this->options = [
'connection' => '',
'namespace' => '',
'table' => '',
'folder' => app()->path(),
'folder' => $this->getModelPath(),
'debug' => false,
'all' => false,
'singular' => '',
'singular' => false,
'overwrite' => false
];

$this->delimiter = config('modelfromtable.delimiter', ', ');
}

/**
Expand All @@ -72,6 +80,7 @@ public function handle()

$tables = [];
$path = $this->options['folder'];
$overwrite = $this->getOption('overwrite', false);
$modelStub = file_get_contents($this->getStub());

// can we run?
Expand All @@ -82,7 +91,7 @@ public function handle()
}

// figure out if we need to create a folder or not
if($this->options['folder'] != app()->path()) {
if($this->options['folder'] != $this->getModelPath()) {
if(! is_dir($this->options['folder'])) {
mkdir($this->options['folder']);
}
Expand All @@ -108,6 +117,12 @@ public function handle()
}

$fullPath = "$path/$filename.php";

if (!$overwrite and file_exists($fullPath)) {
$this->doComment("Skipping file: $filename.php");
continue;
}

$this->doComment("Generating file: $filename.php");

// gather information on it
Expand Down Expand Up @@ -200,58 +215,111 @@ public function replaceModuleInformation($stub, $modelInformation)
// replace table
$stub = str_replace('{{table}}', $modelInformation['table'], $stub);

$primaryKey = config('modelfromtable.primaryKey', 'id');

// allow config to apply a lamba to obtain non-ordinary primary key name
if (is_callable($primaryKey)) {
$primaryKey = $primaryKey($modelInformation['table']);
}

// replace fillable
$this->fieldsDocBlock = '';
$this->fieldsHidden = '';
$this->fieldsFillable = '';
$this->fieldsCast = '';
foreach ($modelInformation['fillable'] as $field) {
// fillable and hidden
if ($field != 'id') {
$this->fieldsFillable .= (strlen($this->fieldsFillable) > 0 ? ', ' : '')."'$field'";
if ($field != $primaryKey) {
$this->interpolate($this->fieldsFillable, "'$field'");

$fieldsFiltered = $this->columns->where('field', $field);
if ($fieldsFiltered) {
// check type
switch (strtolower($fieldsFiltered->first()['type'])) {
$type = strtolower($fieldsFiltered->first()['type']);
$type = preg_replace("/\s.*$/", '', $type);
preg_match_all("/^(\w*)\((?:(\d+)(?:,(\d+))*)\)/", $type, $matches);

$columnType = isset($matches[1][0]) ? $matches[1][0] : $type;
$columnLength = isset($matches[2][0]) ? $matches[2][0] : '';

$generateDocLine = function (string $type, string $field) { return str_pad("\n * @property {$type}", 25, ' ') . "$$field";};

switch ($columnType) {
case 'int':
case 'tinyint':
case 'boolean':
case 'bool':
$castType = ($columnLength == 1) ? 'boolean' : 'int';

$this->interpolate($this->fieldsDocBlock, $generateDocLine($castType, $field), "");
$this->interpolate($this->fieldsCast, "'$field' => '$castType'");
break;
case 'varchar':
case 'text':
case 'tinytext':
case 'mediumtext':
case 'longtext':
$this->interpolate($this->fieldsDocBlock, $generateDocLine('string', $field), "");
$this->interpolate($this->fieldsCast, "'$field' => 'string'");
break;
case 'float':
case 'double':
$this->interpolate($this->fieldsDocBlock, $generateDocLine('float', $field), "");
$this->interpolate($this->fieldsCast, "'$field' => '$columnType'");
break;
case 'timestamp':
$this->fieldsDate .= (strlen($this->fieldsDate) > 0 ? ', ' : '')."'$field'";
$this->interpolate($this->fieldsDocBlock, $generateDocLine('int', $field), "");
$this->interpolate($this->fieldsCast, "'$field' => '$columnType'");
$this->interpolate($this->fieldsDate, "'$field'");
break;
case 'datetime':
$this->fieldsDate .= (strlen($this->fieldsDate) > 0 ? ', ' : '')."'$field'";
$this->interpolate($this->fieldsDocBlock, $generateDocLine('DateTime', $field), "");
$this->interpolate($this->fieldsCast, "'$field' => '$columnType'");
$this->interpolate($this->fieldsDate, "'$field'");
break;
case 'date':
$this->fieldsDate .= (strlen($this->fieldsDate) > 0 ? ', ' : '')."'$field'";
break;
case 'tinyint(1)':
$this->fieldsCast .= (strlen($this->fieldsCast) > 0 ? ', ' : '')."'$field' => 'boolean'";
$this->interpolate($this->fieldsDocBlock, $generateDocLine('Date', $field), "");
$this->interpolate($this->fieldsCast, "'$field' => '$columnType'");
$this->interpolate($this->fieldsDate, "'$field'");
break;
}
}
} else {
if ($field != 'id' && $field != 'created_at' && $field != 'updated_at') {
$this->fieldsHidden .= (strlen($this->fieldsHidden) > 0 ? ', ' : '')."'$field'";
if ($field != $primaryKey && $field != 'created_at' && $field != 'updated_at') {
$this->interpolate($this->fieldsHidden, "'$field'");
}
}
}

$timestamps = ($this->getOption('timestamps', false, true)) ? 'true' : 'false';

// replace in stub
$stub = str_replace('{{docblock}}', $this->fieldsDocBlock, $stub);
$stub = str_replace('{{primaryKey}}', $primaryKey, $stub);
$stub = str_replace('{{fillable}}', $this->fieldsFillable, $stub);
$stub = str_replace('{{hidden}}', $this->fieldsHidden, $stub);
$stub = str_replace('{{casts}}', $this->fieldsCast, $stub);
$stub = str_replace('{{dates}}', $this->fieldsDate, $stub);
$stub = str_replace('{{timestamps}}', $timestamps, $stub);
$stub = str_replace('{{modelnamespace}}', $this->options['namespace'], $stub);

return $stub;
}

private function interpolate(string &$string, string $add, $delimiter = null)
{
$delimiter = $delimiter ?? $this->delimiter;
$string .= (strlen($string) > 0 ? $delimiter : '').$add;
}

public function replaceConnection($stub, $database)
{
$replacementString = '/**
* The connection name for the model.
*
* @var string
*/
protected $connection = \''.$database.'\';';
protected $connection = \''.$database.'\';'."\n\n";

if (strlen($database) <= 0) {
$stub = str_replace('{{connection}}', '', $stub);
Expand All @@ -278,41 +346,67 @@ public function getStub()
public function getOptions()
{
// debug
$this->options['debug'] = ($this->option('debug')) ? true : false;
$this->options['debug'] = $this->getOption('debug', false, true);

// connection
$this->options['connection'] = ($this->option('connection')) ? $this->option('connection') : '';
$this->options['connection'] = $this->getOption('connection', '');

// folder
$this->options['folder'] = $this->getOption('folder', '');

// namespace
$this->options['namespace'] = $this->getOption('namespace', '');

// namespace with possible folder
// if there is no folder specified and no namespace
if(! $this->option('folder') && ! $this->option('namespace')) {
if(! $this->options['folder'] && ! $this->options['namespace']) {
// assume default APP
$this->options['namespace'] = 'App';
} else {
// if we have a namespace, use it first
if($this->option('namespace')) {
$this->options['namespace'] = str_replace('/', '\\', $this->option('namespace'));
if($this->options['namespace']) {
$this->options['namespace'] = str_replace('/', '\\', $this->options['namespace']);
} else {
if($this->option('folder')) {
$folder = $this->option('folder');
if($folder = $this->options['folder']) {
$this->options['namespace'] = str_replace('/', '\\', $folder);
}
}
}

// finish setting up folder
$this->options['folder'] = ($this->option('folder')) ? base_path($this->option('folder')) : app()->path();
$this->options['folder'] = ($this->options['folder']) ? base_path($this->options['folder']) : $this->getModelPath();
// trim trailing slashes
$this->options['folder'] = rtrim($this->options['folder'], '/');

// all tables
$this->options['all'] = ($this->option('all')) ? true : false;
$this->options['all'] = $this->getOption('all', false, true);

// single or list of tables
$this->options['table'] = ($this->option('table')) ? $this->option('table') : '';
$this->options['table'] = $this->getOption('table', '');

// class name and class file name singular/plural
$this->options['singular'] = ($this->option('singular')) ? $this->option('singular') : '';
$this->options['singular'] = $this->getOption('singular', false, true);
}

/**
* returns single option with priority being user input, then user config, then default
*/
private function getOption(string $key, $default = null, bool $isBool = false)
{
if ($isBool) {
$return = ($this->option($key))
? filter_var($this->option($key), FILTER_VALIDATE_BOOLEAN)
: config("modelfromtable.{$key}", $default);
} else {
$return = $this->options[$key] = $this->option($key) ?? config("modelfromtable.{$key}", $default);
}

return $return;
}

private function getModelPath()
{
return (app()->version() > '8')? app()->path('Models') : app()->path();
}

/**
Expand All @@ -331,6 +425,8 @@ public function doComment($text, $overrideDebug = false)
public function getAllTables()
{
$tables = [];
$whitelist = config('modelfromtable.whitelist', []);
$blacklist = config('modelfromtable.blacklist', []);

if (strlen($this->options['connection']) <= 0) {
$tables = collect(DB::select(DB::raw("show full tables where Table_Type = 'BASE TABLE'")))->flatten();
Expand All @@ -340,8 +436,21 @@ public function getAllTables()

$tables = $tables->map(function ($value, $key) {
return collect($value)->flatten()[0];
})->reject(function ($value, $key) {
return $value == 'migrations';
})->reject(function ($value, $key) use ($blacklist) {
foreach($blacklist as $reject) {
if (fnmatch($reject, $value)) {
return true;
}
}
})->filter(function ($value, $key) use ($whitelist) {
if (!$whitelist) {
return true;
}
foreach($whitelist as $accept) {
if (fnmatch($accept, $value)) {
return true;
}
}
});

return $tables;
Expand All @@ -352,6 +461,7 @@ public function getAllTables()
*/
public function resetFields()
{
$this->fieldsDocBlock = '';
$this->fieldsFillable = '';
$this->fieldsHidden = '';
$this->fieldsCast = '';
Expand Down
5 changes: 4 additions & 1 deletion src/GeneratorsServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ class GeneratorsServiceProvider extends ServiceProvider

public function boot()
{
//
$this->publishes([
__DIR__.'/../config/modelfromtable.php' => config_path('modelfromtable.php'),
], 'modelfromtable');
}

public function register()
{
$this->mergeConfigFrom(__DIR__.'/../config/modelfromtable.php', 'modelfromtable');
$this->registerModelGenerator();
}

Expand Down
Loading

0 comments on commit 80c9b8b

Please sign in to comment.