diff --git a/README.md b/README.md index 46d3b06..fe3889d 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,9 @@ This command comes with a bunch of different options, please see below for each * --table= * This parameter if filled in will generate a model for the given table * You can also pass in a list of tables using comma separated values -* --all=[_true|false (default)_] - * If this flag is present, then the table command will be ignored - * This will generate a model for **all** tables found in your database - * You can optionally specify a whitelist/blacklist in `config/modelfromtable.php` - * _Note: by default, this command will only ignore the `migrations` table_ + * When omitted, **all** tables will generate a model + * In this scenario you can optionally specify a whitelist/blacklist in `config/modelfromtable.php` + * `migrations` table will be blacklisted by default * --connection= * By default, if omitted, the default connection found in `config/database.php` will be used * To specify a connection, first ensure that it exists in your `config/database.php` @@ -86,16 +84,10 @@ php artisan generate:modelfromtable --table=users php artisan generate:modelfromtable --table=users,posts ``` -### Generating all tables +### Changing to another connection found in `database.php` ``` -php artisan generate:modelfromtable --all=true -``` - -### Changing to another connection found in `database.php` and generating models for all tables - -``` -php artisan generate:modelfromtable --connection=spark --all=true +php artisan generate:modelfromtable --connection=spark ``` ### Changing the folder where to /app/Models @@ -105,7 +97,7 @@ php artisan generate:modelfromtable --table=user --folder=app\Models ``` ## Configuration file for saving defaults, dynamic lambdas -A [config file](https://github.com/laracademy/generators/blob/master/config/modelfromtable.php) should be in your project's config folder (if not, you can easily create it). Through this, you can set defaults you commonly use to cut down on the input your command line call requires. For example: `'all' => true`. Then when you run `php artisan generate:modelfromtable` all tables will be generated by default. Some fields, like `namespace`, accept a static value or, more powerfully, a lambda to generate dynamic values. Additional fields not available to the CLI are available in the config. See below. +A [config file](https://github.com/laracademy/generators/blob/master/config/modelfromtable.php) should be in your project's config folder (if not, you can easily create it). Through this, you can set defaults you commonly use to cut down on the input your command line call requires. Some fields, like `namespace`, accept a static value or, more powerfully, a lambda to generate dynamic values. Additional fields not available to the CLI are available in the config. See below. ### Primary Key, using lamba (config only) Some apps do not use the Laravel default of `id`, so say your table name prefixes the primary key... diff --git a/config/modelfromtable.php b/config/modelfromtable.php index 509464a..13447c0 100644 --- a/config/modelfromtable.php +++ b/config/modelfromtable.php @@ -28,8 +28,6 @@ 'debug' => false, - 'all' => false, - 'singular' => false, 'overwrite' => false, diff --git a/src/Commands/ModelFromTableCommand.php b/src/Commands/ModelFromTableCommand.php index c26e181..a961302 100644 --- a/src/Commands/ModelFromTableCommand.php +++ b/src/Commands/ModelFromTableCommand.php @@ -21,7 +21,7 @@ class ModelFromTableCommand extends Command {--filename= : generated by table name, but you can customize this further} {--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 - DEPRECATED} {--overwrite= : overwrite model(s) if exists} {--timestamps= : whether to timestamp or not}'; @@ -32,18 +32,10 @@ class ModelFromTableCommand extends Command */ protected $description = 'Generate models for the given tables based on their columns'; - public $fieldsDocBlock; - public $fieldsFillable; - public $fieldsHidden; - public $fieldsCast; - public $fieldsDate; - public $columns; - - public $debug; - public $options; + private $db; + private $options; private $delimiter; - - public $databaseConnection; + private $stubConnection; /** * Create a new command instance. @@ -61,7 +53,6 @@ public function __construct() 'folder' => $this->getModelPath(), 'filename' => '', 'debug' => false, - 'all' => false, 'singular' => false, 'overwrite' => false ]; @@ -77,20 +68,17 @@ public function __construct() public function handle() { $this->doComment('Starting Model Generate Command', true); - $this->getOptions(); + + $this->hydrateOptions(); + + $this->db = DB::connection($this->options['connection']); + $this->stubConnection = $this->getConnectionStub(); $tables = []; $path = $this->options['folder']; $overwrite = $this->getOption('overwrite', false); $modelStub = file_get_contents($this->getStub()); - // can we run? - if (strlen($this->options['table']) <= 0 && $this->options['all'] == false) { - $this->error('No --table specified or --all'); - - return; - } - // figure out if we need to create a folder or not // NOTE: lambas will need to handle this themselves if (!is_callable($path) && $path != $this->getModelPath()) { @@ -99,215 +87,120 @@ public function handle() } } - // figure out if it is all tables - $tables = ($this->options['all']) ? $this->getAllTables() : explode(',', $this->options['table']); + $tables = $this->getTables(); // cycle through each table foreach ($tables as $table) { // grab a fresh copy of our stub $stub = $modelStub; - $filename = $this->options['filename']; - if (is_callable($filename)) { - $filename = $filename($table); - } elseif (!$filename) { - // generate the file name for the model based on the table name - $filename = Str::studly($table); - } - - if ($this->options['singular']) { - $filename = Str::singular($filename); - } - - if (is_callable($path)) { - $path = $path($table); - } - - $fullPath = "$path/$filename.php"; - - if (!$overwrite and file_exists($fullPath)) { - $this->doComment("Skipping file: $filename.php"); + // if (!$overwrite and file_exists($fullPath)) { + if (!$overwrite and file_exists($table['file']['path'])) { + $this->doComment("Skipping file: {$table['file']['name']}"); continue; } - $this->doComment("Generating file: $filename.php"); - - // gather information on it - $model = [ - 'table' => $table, - 'path' => $path, - 'fillable' => $this->getSchema($table), - 'guardable' => [], - 'hidden' => [], - 'casts' => [], - ]; - - // fix these up - $columns = $this->describeTable($table); - - // use a collection - $this->columns = collect(); - - foreach ($columns as $col) { - $this->columns->push([ - 'field' => $col->Field, - 'type' => $col->Type, - ]); - } - - // reset fields - $this->resetFields(); - - // replace the class name - $stub = $this->replaceClassName($stub, $filename); - - // replace the fillable - $stub = $this->replaceModuleInformation($stub, $model); + $this->doComment("Generating file: {$table['file']['name']}"); - // figure out the connection - $stub = $this->replaceConnection($stub, $this->options['connection']); + $stub = $this->hydrateStub($stub, $table); // writing stub out - $this->doComment('Writing model: '.$fullPath, true); - file_put_contents($fullPath, $stub); + $this->doComment("Writing model: {$table['file']['path']}", true); + file_put_contents($table['file']['path'], $stub); } $this->info('Complete'); } - public function getSchema($tableName) - { - $this->doComment('Retrieving table definition for: '.$tableName); - - if (strlen($this->options['connection']) <= 0) { - return Schema::getColumnListing($tableName); - } else { - return Schema::connection($this->options['connection'])->getColumnListing($tableName); - } - } - public function describeTable($tableName) { $this->doComment('Retrieving column information for : '.$tableName); - if (strlen($this->options['connection']) <= 0) { - return DB::select(DB::raw("describe `{$tableName}`")); - } else { - return DB::connection($this->options['connection'])->select(DB::raw("describe `{$tableName}`")); - } - } - - /** - * replaces the class name in the stub. - * - * @param string $stub stub content - * @param string $filename the name of the file to set as the class - * - * @return string stub content - */ - public function replaceClassName($stub, $filename) - { - return str_replace('{{class}}', $filename, $stub); + return $this->db->select($this->db->raw("describe `{$tableName}`")); } /** - * replaces the module information. + * hydrates the stub with table data * - * @param string $stub stub content - * @param array $modelInformation array (key => value) + * @param string $stub stub content + * @param array $table array (key => value) * * @return string stub content */ - public function replaceModuleInformation($stub, $modelInformation) + public function hydrateStub($stub, $table) { // replace table - $stub = str_replace('{{table}}', $modelInformation['table'], $stub); + $stub = str_replace('{{table}}', $table['name'], $stub); $primaryKey = config('modelfromtable.primaryKey', 'id'); - $namespace = $this->options['namespace']; // allow config to apply a lamba to obtain non-ordinary primary key name if (is_callable($primaryKey)) { - $primaryKey = $primaryKey($modelInformation['table']); + $primaryKey = $primaryKey($table['name']); } - // allow config to apply a lambda to obtain non-ordinary namespace - if (is_callable($namespace)) { - $namespace = $namespace($modelInformation['path']); - } - - // replace fillable - $this->fieldsDocBlock = ''; - $this->fieldsHidden = ''; - $this->fieldsFillable = ''; - $this->fieldsCast = ''; + // reset stub fields + $stubDocBlock = $stubFillable = $stubHidden = $stubCast = $stubDate = ''; $types = []; - foreach ($modelInformation['fillable'] as $field) { - // fillable and hidden - if ($field != $primaryKey) { - $this->interpolate($this->fieldsFillable, "'$field'"); - - $fieldsFiltered = $this->columns->where('field', $field); - if ($fieldsFiltered) { - // check 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] : ''; - - switch ($columnType) { - case 'int': - case 'tinyint': - case 'boolean': - case 'bool': - $castType = ($columnLength == 1) ? 'boolean' : 'int'; - $types[$castType][] = $field; - - $this->interpolate($this->fieldsCast, "'$field' => '$castType'"); - break; - case 'varchar': - case 'text': - case 'tinytext': - case 'mediumtext': - case 'longtext': - $types['string'][] = $field; - - $this->interpolate($this->fieldsCast, "'$field' => 'string'"); - break; - case 'float': - case 'double': - $types['float'][] = $field; - - $this->interpolate($this->fieldsCast, "'$field' => '$columnType'"); - break; - case 'timestamp': - $types['int'][] = $field; - - $this->interpolate($this->fieldsCast, "'$field' => '$columnType'"); - $this->interpolate($this->fieldsDate, "'$field'"); - break; - case 'datetime': - $types['DateTime'][] = $field; - - $this->interpolate($this->fieldsCast, "'$field' => '$columnType'"); - $this->interpolate($this->fieldsDate, "'$field'"); - break; - case 'date': - $types['Date'][] = $field; - - $this->interpolate($this->fieldsCast, "'$field' => '$columnType'"); - $this->interpolate($this->fieldsDate, "'$field'"); - break; - } - } - } else { - if ($field != $primaryKey && $field != 'created_at' && $field != 'updated_at') { - $this->interpolate($this->fieldsHidden, "'$field'"); - } + foreach ($table['columns'] as $column) { + // fillable + if ($column['field'] != $primaryKey) { + $this->interpolate($stubFillable, "'{$column['field']}'"); + } + + // cast/date + $type = strtolower($column['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] : ''; + + switch ($columnType) { + case 'int': + case 'tinyint': + case 'boolean': + case 'bool': + $castType = ($columnLength == 1) ? 'boolean' : 'int'; + $types[$castType][] = $column['field']; + + $this->interpolate($stubCast, "'{$column['field']}' => '$castType'"); + break; + case 'varchar': + case 'text': + case 'tinytext': + case 'mediumtext': + case 'longtext': + $types['string'][] = $column['field']; + + $this->interpolate($stubCast, "'{$column['field']}' => 'string'"); + break; + case 'float': + case 'double': + $types['float'][] = $column['field']; + + $this->interpolate($stubCast, "'{$column['field']}' => '$columnType'"); + break; + case 'timestamp': + $types['int'][] = $column['field']; + + $this->interpolate($stubCast, "'{$column['field']}' => '$columnType'"); + $this->interpolate($stubDate, "'{$column['field']}'"); + break; + case 'datetime': + $types['DateTime'][] = $column['field']; + + $this->interpolate($stubCast, "'{$column['field']}' => '$columnType'"); + $this->interpolate($stubDate, "'{$column['field']}'"); + break; + case 'date': + $types['Date'][] = $column['field']; + + $this->interpolate($stubCast, "'{$column['field']}' => '$columnType'"); + $this->interpolate($stubDate, "'{$column['field']}'"); + break; } } @@ -317,21 +210,23 @@ public function replaceModuleInformation($stub, $modelInformation) foreach ($types as $type => $fields) { foreach($fields as $field) { - $this->interpolate($this->fieldsDocBlock, $generateDocLine($type, $field, $padding), ""); + $this->interpolate($stubDocBlock, $generateDocLine($type, $field, $padding), ""); } } $timestamps = ($this->getOption('timestamps', false, true)) ? 'true' : 'false'; // replace in stub - $stub = str_replace('{{docblock}}', $this->fieldsDocBlock, $stub); + $stub = str_replace('{{connection}}', $this->stubConnection, $stub); + $stub = str_replace('{{class}}', $table['file']['class'], $stub); + $stub = str_replace('{{docblock}}', $stubDocBlock, $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('{{fillable}}', $stubFillable, $stub); + $stub = str_replace('{{hidden}}', $stubHidden, $stub); + $stub = str_replace('{{casts}}', $stubCast, $stub); + $stub = str_replace('{{dates}}', $stubDate, $stub); $stub = str_replace('{{timestamps}}', $timestamps, $stub); - $stub = str_replace('{{modelnamespace}}', str_replace('/', '\\', $namespace), $stub); + $stub = str_replace('{{namespace}}', str_replace('/', '\\', $table['file']['namespace']), $stub); return $stub; } @@ -342,22 +237,16 @@ private function interpolate(string &$string, string $add, $delimiter = null) $string .= (strlen($string) > 0 ? $delimiter : '').$add; } - public function replaceConnection($stub, $database) + public function getConnectionStub() { - $replacementString = '/** + return ($database = $this->options['connection']) + ? "/** * The connection name for the model. * * @var string */ - protected $connection = \''.$database.'\';'."\n\n"; - - if (strlen($database) <= 0) { - $stub = str_replace('{{connection}}', '', $stub); - } else { - $stub = str_replace('{{connection}}', $replacementString, $stub); - } - - return $stub; + protected \$connection = '{$database}';\n\n " + : ''; } /** @@ -371,23 +260,14 @@ public function getStub() } /** - * returns all the options that the user specified. + * fills all the options that the user specified, overwriting defaults if necessary */ - public function getOptions() + public function hydrateOptions() { - // debug $this->options['debug'] = $this->getOption('debug', false, true); - - // connection $this->options['connection'] = $this->getOption('connection', ''); - - // folder $this->options['folder'] = $this->getOption('folder', ''); - - // filename $this->options['filename'] = $this->getOption('filename', ''); - - // namespace $this->options['namespace'] = $this->getOption('namespace', ''); // if there is no folder specified and no namespace, set default namespaace @@ -409,10 +289,6 @@ public function getOptions() // trim trailing slashes $this->options['folder'] = rtrim($this->options['folder'], '/'); } - - - // all tables - $this->options['all'] = $this->getOption('all', false, true); // single or list of tables $this->options['table'] = $this->getOption('table', ''); @@ -455,49 +331,100 @@ public function doComment($text, $overrideDebug = false) /** * will return an array of all table names. */ - public function getAllTables() + public function getTables() { - $tables = []; - $whitelist = config('modelfromtable.whitelist', []); - $blacklist = config('modelfromtable.blacklist', []); + $tables = collect(); - if (strlen($this->options['connection']) <= 0) { - $tables = collect(DB::select(DB::raw("show full tables where Table_Type = 'BASE TABLE'")))->flatten(); + if ($this->options['table']) { + $tableNames = explode(',', $this->options['table']); } else { - $tables = collect(DB::connection($this->options['connection'])->select(DB::raw("show full tables where Table_Type = 'BASE TABLE'")))->flatten(); - } - - $tables = $tables->map(function ($value, $key) { - return collect($value)->flatten()[0]; - })->reject(function ($value, $key) use ($blacklist) { - foreach($blacklist as $reject) { - if (fnmatch($reject, $value)) { - return true; + // get all tables by default + $whitelist = config('modelfromtable.whitelist', []); + $blacklist = config('modelfromtable.blacklist', []); + + $tableNames = collect($this->db->select($this->db->raw("show full tables where Table_Type = 'BASE TABLE'")))->flatten(); + + $tableNames = $tableNames->map(function ($value) { + return collect($value)->flatten()[0]; + })->reject(function ($value) 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)) { + })->filter(function ($value) use ($whitelist) { + if (!$whitelist) { return true; } - } - }); + foreach($whitelist as $accept) { + if (fnmatch($accept, $value)) { + return true; + } + } + }); + } + + // get all columns + foreach($tableNames as $tableName) { + $tables->push([ + 'name' => $tableName, + 'columns' => $this->getColumns($tableName), + 'file' => $this->getPath($tableName) + ]); + } return $tables; } - /** - * reset all variables to be filled again when using multiple - */ - public function resetFields() + private function getColumns($table) + { + // fix these up + $columns = $this->describeTable($table); + + // use a collection + $return = collect(); + + foreach ($columns as $col) { + $return->push([ + 'field' => $col->Field, + 'type' => $col->Type, + ]); + } + + return $return; + } + + private function getPath($tableName) { - $this->fieldsDocBlock = ''; - $this->fieldsFillable = ''; - $this->fieldsHidden = ''; - $this->fieldsCast = ''; - $this->fieldsDate = ''; + $path = $this->options['folder']; + $filename = $this->options['filename']; + $namespace = $this->options['namespace']; + + if (is_callable($filename)) { + $filename = $filename($tableName); + } elseif (!$filename) { + // generate the file name for the model based on the table name + $filename = Str::studly($tableName); + } + + if ($this->options['singular']) { + $filename = Str::singular($filename); + } + + if (is_callable($path)) { + $path = $path($tableName); + } + + // allow config to apply a lambda to obtain non-ordinary namespace + if (is_callable($namespace)) { + $namespace = $namespace($path); + } + + return [ + 'class' => $filename, + 'name' => "{$filename}.php", + 'path' => "{$path}/{$filename}.php", + 'namespace' => $namespace + ]; } } diff --git a/src/stubs/model.stub b/src/stubs/model.stub index b6b1d08..e91fe03 100644 --- a/src/stubs/model.stub +++ b/src/stubs/model.stub @@ -1,6 +1,6 @@