src/base/BaseCrudController.php
<?php
namespace luya\admin\base;
use luya\console\Command;
use Yii;
use yii\base\NotSupportedException;
use yii\db\Schema;
use yii\helpers\Inflector;
/**
* Base Crud Controller
*
* As we can not ensure to access the gii model generate class we have to copy the base of the class, check the see section.
*
* @see https://github.com/yiisoft/yii2-gii/blob/master/src/generators/model/Generator.php
* @author Basil Suter <basil@nadar.io>
* @since 1.0.0
*/
abstract class BaseCrudController extends Command
{
/**
* @var boolean Whether to use schem name or not
*/
public $useSchemaName = true;
/**
* @var array A list of class names.
*/
protected $classNames;
/**
* @var array A list of table names.
*/
protected $tableNames;
/**
* @var string The name of the table.
*/
public $tableName;
/**
* @var boolean Whether to generate labels from comments or not.
*/
public $generateLabelsFromComments = false;
/**
* Get the sql tables from the current database connection
*
* @return array An array with all sql tables.
*/
public function getSqlTablesArray()
{
$names = Yii::$app->db->schema->tableNames;
return array_combine($names, $names);
}
/**
* Generates validation rules for the specified table.
* @param \yii\db\TableSchema $table the table schema
* @return array the generated validation rules
*/
public function generateRules($table)
{
$types = [];
$lengths = [];
foreach ($table->columns as $column) {
if ($column->autoIncrement) {
continue;
}
if (!$column->allowNull && $column->defaultValue === null) {
$types['required'][] = $column->name;
}
switch ($column->type) {
case Schema::TYPE_SMALLINT:
case Schema::TYPE_INTEGER:
case Schema::TYPE_BIGINT:
case Schema::TYPE_TINYINT:
$types['integer'][] = $column->name;
break;
case Schema::TYPE_BOOLEAN:
$types['boolean'][] = $column->name;
break;
case Schema::TYPE_FLOAT:
case Schema::TYPE_DOUBLE:
case Schema::TYPE_DECIMAL:
case Schema::TYPE_MONEY:
$types['number'][] = $column->name;
break;
case Schema::TYPE_DATE:
case Schema::TYPE_TIME:
case Schema::TYPE_DATETIME:
case Schema::TYPE_TIMESTAMP:
case Schema::TYPE_JSON:
$types['safe'][] = $column->name;
break;
default: // strings
if ($column->size > 0) {
$lengths[$column->size][] = $column->name;
} else {
$types['string'][] = $column->name;
}
}
}
$rules = [];
$driverName = $this->getDbDriverName();
foreach ($types as $type => $columns) {
if ($driverName === 'pgsql' && $type === 'integer') {
$rules[] = "[['" . implode("', '", $columns) . "'], 'default', 'value' => null]";
}
$rules[] = "[['" . implode("', '", $columns) . "'], '$type']";
}
foreach ($lengths as $length => $columns) {
$rules[] = "[['" . implode("', '", $columns) . "'], 'string', 'max' => $length]";
}
$db = $this->getDbConnection();
// Unique indexes rules
try {
$uniqueIndexes = array_merge($db->getSchema()->findUniqueIndexes($table), [$table->primaryKey]);
$uniqueIndexes = array_unique($uniqueIndexes, SORT_REGULAR);
foreach ($uniqueIndexes as $uniqueColumns) {
// Avoid validating auto incremental columns
if (!$this->isColumnAutoIncremental($table, $uniqueColumns)) {
$attributesCount = is_countable($uniqueColumns) ? count($uniqueColumns) : 0;
if ($attributesCount === 1) {
$rules[] = "[['" . $uniqueColumns[0] . "'], 'unique']";
} elseif ($attributesCount > 1) {
$columnsList = implode("', '", $uniqueColumns);
$rules[] = "[['$columnsList'], 'unique', 'targetAttribute' => ['$columnsList']]";
}
}
}
} catch (NotSupportedException $e) {
// doesn't support unique indexes information...do nothing
}
// Exist rules for foreign keys
foreach ($table->foreignKeys as $refs) {
$refTable = $refs[0];
$refTableSchema = $db->getTableSchema($refTable);
if ($refTableSchema === null) {
// Foreign key could point to non-existing table: https://github.com/yiisoft/yii2-gii/issues/34
continue;
}
$refClassName = $this->generateClassName($refTable);
unset($refs[0]);
$attributes = implode("', '", array_keys($refs));
$targetAttributes = [];
foreach ($refs as $key => $value) {
$targetAttributes[] = "'$key' => '$value'";
}
$targetAttributes = implode(', ', $targetAttributes);
$rules[] = "[['$attributes'], 'exist', 'skipOnError' => true, 'targetClass' => $refClassName::className(), 'targetAttribute' => [$targetAttributes]]";
}
return $rules;
}
/**
* Generates a class name from the specified table name.
* @param string $tableName the table name (which may contain schema prefix)
* @param boolean $useSchemaName should schema name be included in the class name, if present
* @return string the generated class name
*/
protected function generateClassName($tableName, $useSchemaName = null)
{
if (isset($this->classNames[$tableName])) {
return $this->classNames[$tableName];
}
$schemaName = '';
$fullTableName = $tableName;
if (($pos = strrpos($tableName, '.')) !== false) {
if (($useSchemaName === null && $this->useSchemaName) || $useSchemaName) {
$schemaName = substr($tableName, 0, $pos) . '_';
}
$tableName = substr($tableName, $pos + 1);
}
$db = $this->getDbConnection();
$patterns = [];
$patterns[] = "/^{$db->tablePrefix}(.*?)$/";
$patterns[] = "/^(.*?){$db->tablePrefix}$/";
if (str_contains($this->tableName, '*')) {
$pattern = $this->tableName;
if (($pos = strrpos($pattern, '.')) !== false) {
$pattern = substr($pattern, $pos + 1);
}
$patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/';
}
$className = $tableName;
foreach ($patterns as $pattern) {
if (preg_match($pattern, $tableName, $matches)) {
$className = $matches[1];
break;
}
}
return $this->classNames[$fullTableName] = Inflector::id2camel($schemaName.$className, '_');
}
/**
* Checks if any of the specified columns is auto incremental.
* @param \yii\db\TableSchema $table the table schema
* @param array $columns columns to check for autoIncrement property
* @return boolean whether any of the specified columns is auto incremental.
*/
protected function isColumnAutoIncremental($table, $columns)
{
foreach ($columns as $column) {
if (isset($table->columns[$column]) && $table->columns[$column]->autoIncrement) {
return true;
}
}
return false;
}
/**
* Generates the attribute labels for the specified table.
* @param \yii\db\TableSchema $table the table schema
* @return array the generated attribute labels (name => label)
*/
public function generateLabels($table)
{
$labels = [];
foreach ($table->columns as $column) {
if ($this->generateLabelsFromComments && !empty($column->comment)) {
$labels[$column->name] = $column->comment;
} elseif (!strcasecmp($column->name, 'id')) {
$labels[$column->name] = 'ID';
} else {
$label = Inflector::camel2words($column->name);
if (!empty($label) && str_ends_with($label, ' id')) {
$label = substr($label, 0, -3) . ' ID';
}
$labels[$column->name] = $label;
}
}
return $labels;
}
/**
* @return string|null driver name of db connection.
* In case db is not instance of \yii\db\Connection null will be returned.
* @since 2.0.6
*/
protected function getDbDriverName()
{
/** @var Connection $db */
$db = $this->getDbConnection();
return $db instanceof \yii\db\Connection ? $db->driverName : null;
}
/**
* @return \yii\db\Connection the DB connection as specified by [[db]].
*/
protected function getDbConnection()
{
return Yii::$app->get('db', false);
}
}