@@ -0,0 +1,924 @@
< ? php
declare ( strict_types = 1 );
namespace app\admin\library\crud ;
use Throwable ;
use ba\Filesystem ;
use ba\TableManager ;
use app\common\library\Menu ;
use app\admin\model\AdminRule ;
use app\admin\model\CrudLog ;
use ba\Exception as BaException ;
use Phinx\Db\Adapter\MysqlAdapter ;
use Phinx\Db\Adapter\AdapterInterface ;
use support\think\Db ;
/**
* CRUD 代码生成器 Helper( Webman 迁移版)
*/
class Helper
{
protected static array $reservedKeywords = [
'abstract' , 'and' , 'array' , 'as' , 'break' , 'callable' , 'case' , 'catch' , 'class' , 'clone' ,
'const' , 'continue' , 'declare' , 'default' , 'die' , 'do' , 'echo' , 'else' , 'elseif' , 'empty' ,
'enddeclare' , 'endfor' , 'endforeach' , 'endif' , 'endswitch' , 'endwhile' , 'eval' , 'exit' , 'extends' ,
'final' , 'for' , 'foreach' , 'function' , 'global' , 'goto' , 'if' , 'implements' , 'include' , 'include_once' ,
'instanceof' , 'insteadof' , 'interface' , 'isset' , 'list' , 'namespace' , 'new' , 'or' , 'print' , 'private' ,
'protected' , 'public' , 'require' , 'require_once' , 'return' , 'static' , 'switch' , 'throw' , 'trait' , 'try' ,
'unset' , 'use' , 'var' , 'while' , 'xor' , 'yield' , 'match' , 'readonly' , 'fn' ,
];
protected static array $parseNamePresets = [
'controller' => [
'user' => [ 'user' , 'user' ],
'admin' => [ 'auth' , 'admin' ],
'admin_group' => [ 'auth' , 'group' ],
'attachment' => [ 'routine' , 'attachment' ],
'admin_rule' => [ 'auth' , 'rule' ],
],
'model' => [],
'validate' => [],
];
public static array $menuChildren = [
[ 'type' => 'button' , 'title' => '查看' , 'name' => '/index' , 'status' => 1 ],
[ 'type' => 'button' , 'title' => '添加' , 'name' => '/add' , 'status' => 1 ],
[ 'type' => 'button' , 'title' => '编辑' , 'name' => '/edit' , 'status' => 1 ],
[ 'type' => 'button' , 'title' => '删除' , 'name' => '/del' , 'status' => 1 ],
[ 'type' => 'button' , 'title' => '快速排序' , 'name' => '/sortable' , 'status' => 1 ],
];
protected static array $inputTypeRule = [
[ 'type' => [ 'tinyint' , 'int' , 'enum' ], 'suffix' => [ 'switch' , 'toggle' ], 'value' => 'switch' ],
[ 'column_type' => [ 'tinyint(1)' , 'char(1)' , 'tinyint(1) unsigned' ], 'suffix' => [ 'switch' , 'toggle' ], 'value' => 'switch' ],
[ 'type' => [ 'longtext' , 'text' , 'mediumtext' , 'smalltext' , 'tinytext' , 'bigtext' ], 'suffix' => [ 'content' , 'editor' ], 'value' => 'editor' ],
[ 'type' => [ 'varchar' ], 'suffix' => [ 'textarea' , 'multiline' , 'rows' ], 'value' => 'textarea' ],
[ 'suffix' => [ 'array' ], 'value' => 'array' ],
[ 'type' => [ 'int' ], 'suffix' => [ 'time' , 'datetime' ], 'value' => 'timestamp' ],
[ 'type' => [ 'datetime' , 'timestamp' ], 'value' => 'datetime' ],
[ 'type' => [ 'date' ], 'value' => 'date' ],
[ 'type' => [ 'year' ], 'value' => 'year' ],
[ 'type' => [ 'time' ], 'value' => 'time' ],
[ 'suffix' => [ 'select' , 'list' , 'data' ], 'value' => 'select' ],
[ 'suffix' => [ 'selects' , 'multi' , 'lists' ], 'value' => 'selects' ],
[ 'suffix' => [ '_id' ], 'value' => 'remoteSelect' ],
[ 'suffix' => [ '_ids' ], 'value' => 'remoteSelects' ],
[ 'suffix' => [ 'city' ], 'value' => 'city' ],
[ 'suffix' => [ 'image' , 'avatar' ], 'value' => 'image' ],
[ 'suffix' => [ 'images' , 'avatars' ], 'value' => 'images' ],
[ 'suffix' => [ 'file' ], 'value' => 'file' ],
[ 'suffix' => [ 'files' ], 'value' => 'files' ],
[ 'suffix' => [ 'icon' ], 'value' => 'icon' ],
[ 'column_type' => [ 'tinyint(1)' , 'char(1)' , 'tinyint(1) unsigned' ], 'suffix' => [ 'status' , 'state' , 'type' ], 'value' => 'radio' ],
[ 'suffix' => [ 'number' , 'int' , 'num' ], 'value' => 'number' ],
[ 'type' => [ 'bigint' , 'int' , 'mediumint' , 'smallint' , 'tinyint' , 'decimal' , 'double' , 'float' ], 'value' => 'number' ],
[ 'type' => [ 'longtext' , 'text' , 'mediumtext' , 'smalltext' , 'tinytext' , 'bigtext' ], 'value' => 'textarea' ],
[ 'type' => [ 'enum' ], 'value' => 'radio' ],
[ 'type' => [ 'set' ], 'value' => 'checkbox' ],
[ 'suffix' => [ 'color' ], 'value' => 'color' ],
];
protected static array $parseWebDirPresets = [
'lang' => [],
'views' => [
'user' => [ 'user' , 'user' ],
'admin' => [ 'auth' , 'admin' ],
'admin_group' => [ 'auth' , 'group' ],
'attachment' => [ 'routine' , 'attachment' ],
'admin_rule' => [ 'auth' , 'rule' ],
],
];
protected static string $createTimeField = 'create_time' ;
protected static string $updateTimeField = 'update_time' ;
protected static array $attrType = [
'controller' => [
'preExcludeFields' => 'array|string' ,
'quickSearchField' => 'string|array' ,
'withJoinTable' => 'array' ,
'defaultSortField' => 'string|array' ,
'weighField' => 'string' ,
],
];
public static function getDictData ( array & $dict , array $field , string $lang , string $translationPrefix = '' ) : array
{
if ( ! $field [ 'comment' ]) return [];
$comment = str_replace ([ ', ' , ': ' ], [ ',' , ':' ], $field [ 'comment' ]);
if ( stripos ( $comment , ':' ) !== false && stripos ( $comment , ',' ) && stripos ( $comment , '=' ) !== false ) {
[ $fieldTitle , $item ] = explode ( ':' , $comment );
$dict [ $translationPrefix . $field [ 'name' ]] = $lang == 'en' ? $field [ 'name' ] : $fieldTitle ;
foreach ( explode ( ',' , $item ) as $v ) {
$valArr = explode ( '=' , $v );
if ( count ( $valArr ) == 2 ) {
[ $key , $value ] = $valArr ;
$dict [ $translationPrefix . $field [ 'name' ] . ' ' . $key ] = $lang == 'en' ? $field [ 'name' ] . ' ' . $key : $value ;
}
}
} else {
$dict [ $translationPrefix . $field [ 'name' ]] = $lang == 'en' ? $field [ 'name' ] : $comment ;
}
return $dict ;
}
public static function recordCrudStatus ( array $data ) : int
{
if ( isset ( $data [ 'id' ])) {
CrudLog :: where ( 'id' , $data [ 'id' ]) -> update ([ 'status' => $data [ 'status' ]]);
return $data [ 'id' ];
}
$connection = $data [ 'table' ][ 'databaseConnection' ] ? ? config ( 'thinkorm.default' , config ( 'database.default' , 'mysql' ));
$log = CrudLog :: create ([
'table_name' => $data [ 'table' ][ 'name' ],
'comment' => $data [ 'table' ][ 'comment' ],
'table' => $data [ 'table' ],
'fields' => $data [ 'fields' ],
'connection' => $connection ,
'status' => $data [ 'status' ],
]);
return $log -> id ;
}
public static function getPhinxFieldType ( string $type , array $field ) : array
{
if ( $type == 'tinyint' ) {
if (
( isset ( $field [ 'dataType' ]) && $field [ 'dataType' ] == 'tinyint(1)' ) ||
( $field [ 'default' ] == '1' && ( $field [ 'defaultType' ] ? ? '' ) == 'INPUT' )
) {
$type = 'boolean' ;
}
}
$phinxFieldTypeMap = [
'tinyint' => [ 'type' => AdapterInterface :: PHINX_TYPE_INTEGER , 'limit' => MysqlAdapter :: INT_TINY ],
'smallint' => [ 'type' => AdapterInterface :: PHINX_TYPE_INTEGER , 'limit' => MysqlAdapter :: INT_SMALL ],
'mediumint' => [ 'type' => AdapterInterface :: PHINX_TYPE_INTEGER , 'limit' => MysqlAdapter :: INT_MEDIUM ],
'int' => [ 'type' => AdapterInterface :: PHINX_TYPE_INTEGER , 'limit' => null ],
'bigint' => [ 'type' => AdapterInterface :: PHINX_TYPE_BIG_INTEGER , 'limit' => null ],
'boolean' => [ 'type' => AdapterInterface :: PHINX_TYPE_BOOLEAN , 'limit' => null ],
'varchar' => [ 'type' => AdapterInterface :: PHINX_TYPE_STRING , 'limit' => null ],
'tinytext' => [ 'type' => AdapterInterface :: PHINX_TYPE_TEXT , 'limit' => MysqlAdapter :: TEXT_TINY ],
'mediumtext' => [ 'type' => AdapterInterface :: PHINX_TYPE_TEXT , 'limit' => MysqlAdapter :: TEXT_MEDIUM ],
'longtext' => [ 'type' => AdapterInterface :: PHINX_TYPE_TEXT , 'limit' => MysqlAdapter :: TEXT_LONG ],
'tinyblob' => [ 'type' => AdapterInterface :: PHINX_TYPE_BLOB , 'limit' => MysqlAdapter :: BLOB_TINY ],
'mediumblob' => [ 'type' => AdapterInterface :: PHINX_TYPE_BLOB , 'limit' => MysqlAdapter :: BLOB_MEDIUM ],
'longblob' => [ 'type' => AdapterInterface :: PHINX_TYPE_BLOB , 'limit' => MysqlAdapter :: BLOB_LONG ],
];
return array_key_exists ( $type , $phinxFieldTypeMap ) ? $phinxFieldTypeMap [ $type ] : [ 'type' => $type , 'limit' => null ];
}
public static function analyseFieldLimit ( string $type , array $field ) : array
{
$fieldType = [ 'decimal' => [ 'decimal' , 'double' , 'float' ], 'values' => [ 'enum' , 'set' ]];
$dataTypeLimit = self :: dataTypeLimit ( $field [ 'dataType' ] ? ? '' );
if ( in_array ( $type , $fieldType [ 'decimal' ])) {
if ( $dataTypeLimit ) {
return [ 'precision' => $dataTypeLimit [ 0 ], 'scale' => $dataTypeLimit [ 1 ] ? ? 0 ];
}
$scale = isset ( $field [ 'precision' ]) ? intval ( $field [ 'precision' ]) : 0 ;
return [ 'precision' => $field [ 'length' ] ? ? 10 , 'scale' => $scale ];
} elseif ( in_array ( $type , $fieldType [ 'values' ])) {
foreach ( $dataTypeLimit as & $item ) {
$item = str_replace ([ '"' , " ' " ], '' , $item );
}
return [ 'values' => $dataTypeLimit ];
} elseif ( $dataTypeLimit && $dataTypeLimit [ 0 ]) {
return [ 'limit' => $dataTypeLimit [ 0 ]];
} elseif ( isset ( $field [ 'length' ])) {
return [ 'limit' => $field [ 'length' ]];
}
return [];
}
public static function dataTypeLimit ( string $dataType ) : array
{
preg_match ( " / \ ((.*?) \ )/ " , $dataType , $matches );
if ( isset ( $matches [ 1 ]) && $matches [ 1 ]) {
return explode ( ',' , trim ( $matches [ 1 ], ',' ));
}
return [];
}
public static function analyseFieldDefault ( array $field ) : mixed
{
return match ( $field [ 'defaultType' ] ? ? 'NONE' ) {
'EMPTY STRING' => '' ,
'NULL' => null ,
default => $field [ 'default' ] ? ? null ,
};
}
public static function searchArray ( $fields , callable $myFunction ) : array | bool
{
foreach ( $fields as $key => $field ) {
if ( call_user_func ( $myFunction , $field , $key )) {
return $field ;
}
}
return false ;
}
public static function getPhinxFieldData ( array $field ) : array
{
$conciseType = self :: analyseFieldType ( $field );
$phinxTypeData = self :: getPhinxFieldType ( $conciseType , $field );
$phinxColumnOptions = self :: analyseFieldLimit ( $conciseType , $field );
if ( $phinxTypeData [ 'limit' ] !== null ) {
$phinxColumnOptions [ 'limit' ] = $phinxTypeData [ 'limit' ];
}
$noDefaultValueFields = [
'text' , 'blob' , 'geometry' , 'geometrycollection' , 'json' , 'linestring' , 'longblob' , 'longtext' , 'mediumblob' ,
'mediumtext' , 'multilinestring' , 'multipoint' , 'multipolygon' , 'point' , 'polygon' , 'tinyblob' ,
];
if (( $field [ 'defaultType' ] ? ? '' ) != 'NONE' && ! in_array ( $conciseType , $noDefaultValueFields )) {
$phinxColumnOptions [ 'default' ] = self :: analyseFieldDefault ( $field );
}
$phinxColumnOptions [ 'null' ] = ( bool )( $field [ 'null' ] ? ? false );
$phinxColumnOptions [ 'comment' ] = $field [ 'comment' ] ? ? '' ;
$phinxColumnOptions [ 'signed' ] = ! ( $field [ 'unsigned' ] ? ? false );
$phinxColumnOptions [ 'identity' ] = $field [ 'autoIncrement' ] ? ? false ;
return [ 'type' => $phinxTypeData [ 'type' ], 'options' => $phinxColumnOptions ];
}
public static function updateFieldOrder ( string $tableName , array $fields , array $designChange , ? string $connection = null ) : void
{
if ( $designChange ) {
$table = TableManager :: phinxTable ( $tableName , [], false , $connection );
foreach ( $designChange as $item ) {
if ( ! $item [ 'sync' ]) continue ;
if ( ! empty ( $item [ 'after' ])) {
$fieldName = in_array ( $item [ 'type' ], [ 'add-field' , 'change-field-name' ]) ? $item [ 'newName' ] : $item [ 'oldName' ];
$field = self :: searchArray ( $fields , fn ( $f ) => $f [ 'name' ] == $fieldName );
if ( ! $field ) continue ;
$phinxFieldData = self :: getPhinxFieldData ( $field );
$phinxFieldData [ 'options' ][ 'after' ] = $item [ 'after' ] == 'FIRST FIELD' ? MysqlAdapter :: FIRST : $item [ 'after' ];
$table -> changeColumn ( $fieldName , $phinxFieldData [ 'type' ], $phinxFieldData [ 'options' ]);
}
}
$table -> update ();
}
}
public static function handleTableDesign ( array $table , array $fields ) : array
{
$name = TableManager :: tableName ( $table [ 'name' ], true , $table [ 'databaseConnection' ] ? ? null );
$comment = $table [ 'comment' ] ? ? '' ;
$designChange = $table [ 'designChange' ] ? ? [];
$adapter = TableManager :: phinxAdapter ( false , $table [ 'databaseConnection' ] ? ? null );
$pk = self :: searchArray ( $fields , fn ( $item ) => $item [ 'primaryKey' ] ? ? false );
$pk = $pk ? $pk [ 'name' ] : '' ;
if ( $adapter -> hasTable ( $name )) {
if ( $designChange ) {
$tableManager = TableManager :: phinxTable ( $name , [], false , $table [ 'databaseConnection' ] ? ? null );
$tableManager -> changeComment ( $comment ) -> update ();
$priorityOpt = false ;
foreach ( $designChange as $item ) {
if ( ! $item [ 'sync' ]) continue ;
if ( in_array ( $item [ 'type' ], [ 'change-field-name' , 'del-field' ]) && ! $tableManager -> hasColumn ( $item [ 'oldName' ])) {
throw new BaException ( __ ( $item [ 'type' ] . ' fail not exist' , [ $item [ 'oldName' ]]));
}
if ( $item [ 'type' ] == 'change-field-name' ) {
$priorityOpt = true ;
$tableManager -> renameColumn ( $item [ 'oldName' ], $item [ 'newName' ]);
} elseif ( $item [ 'type' ] == 'del-field' ) {
$priorityOpt = true ;
$tableManager -> removeColumn ( $item [ 'oldName' ]);
}
}
if ( $priorityOpt ) {
$tableManager -> update ();
}
foreach ( $designChange as $item ) {
if ( ! $item [ 'sync' ]) continue ;
if ( $item [ 'type' ] == 'change-field-attr' ) {
if ( ! $tableManager -> hasColumn ( $item [ 'oldName' ])) {
throw new BaException ( __ ( $item [ 'type' ] . ' fail not exist' , [ $item [ 'oldName' ]]));
}
$field = self :: searchArray ( $fields , fn ( $f ) => $f [ 'name' ] == $item [ 'oldName' ]);
if ( $field ) {
$phinxFieldData = self :: getPhinxFieldData ( $field );
$tableManager -> changeColumn ( $item [ 'oldName' ], $phinxFieldData [ 'type' ], $phinxFieldData [ 'options' ]);
}
} elseif ( $item [ 'type' ] == 'add-field' ) {
if ( $tableManager -> hasColumn ( $item [ 'newName' ])) {
throw new BaException ( __ ( $item [ 'type' ] . ' fail exist' , [ $item [ 'newName' ]]));
}
$field = self :: searchArray ( $fields , fn ( $f ) => $f [ 'name' ] == $item [ 'newName' ]);
if ( $field ) {
$phinxFieldData = self :: getPhinxFieldData ( $field );
$tableManager -> addColumn ( $item [ 'newName' ], $phinxFieldData [ 'type' ], $phinxFieldData [ 'options' ]);
}
}
}
$tableManager -> update ();
self :: updateFieldOrder ( $name , $fields , $designChange , $table [ 'databaseConnection' ] ? ? null );
}
} else {
$tableManager = TableManager :: phinxTable ( $name , [
'id' => false , 'comment' => $comment , 'row_format' => 'DYNAMIC' ,
'primary_key' => $pk , 'collation' => 'utf8mb4_unicode_ci' ,
], false , $table [ 'databaseConnection' ] ? ? null );
foreach ( $fields as $field ) {
$phinxFieldData = self :: getPhinxFieldData ( $field );
$tableManager -> addColumn ( $field [ 'name' ], $phinxFieldData [ 'type' ], $phinxFieldData [ 'options' ]);
}
$tableManager -> create ();
}
return [ $pk ];
}
public static function parseNameData ( $app , $table , $type , $value = '' ) : array
{
$pathArr = [];
if ( $value ) {
$value = str_replace ( '.php' , '' , $value );
$value = str_replace ([ '.' , '/' , '\\' , '_' ], '/' , $value );
$pathArrTemp = explode ( '/' , $value );
$redundantDir = [ 'app' => 0 , $app => 1 , $type => 2 ];
foreach ( $pathArrTemp as $key => $item ) {
if ( ! array_key_exists ( $item , $redundantDir ) || $key !== $redundantDir [ $item ]) {
$pathArr [] = $item ;
}
}
} elseif ( isset ( self :: $parseNamePresets [ $type ]) && array_key_exists ( $table , self :: $parseNamePresets [ $type ])) {
$pathArr = self :: $parseNamePresets [ $type ][ $table ];
} else {
$table = str_replace ([ '.' , '/' , '\\' , '_' ], '/' , $table );
$pathArr = explode ( '/' , $table );
}
$originalLastName = array_pop ( $pathArr );
$pathArr = array_map ( 'strtolower' , $pathArr );
$lastName = ucfirst ( $originalLastName );
if ( in_array ( strtolower ( $originalLastName ), self :: $reservedKeywords )) {
throw new \Exception ( 'Unable to use internal variable:' . $lastName );
}
$appDir = root_path () . 'app' . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR ;
$namespace = " app \\ $app\\ $type " . ( $pathArr ? '\\' . implode ( '\\' , $pathArr ) : '' );
$parseFile = $appDir . $type . DIRECTORY_SEPARATOR . ( $pathArr ? implode ( DIRECTORY_SEPARATOR , $pathArr ) . DIRECTORY_SEPARATOR : '' ) . $lastName . '.php' ;
$rootFileName = $namespace . " / $lastName " . '.php' ;
return [
'lastName' => $lastName ,
'originalLastName' => $originalLastName ,
'path' => $pathArr ,
'namespace' => $namespace ,
'parseFile' => Filesystem :: fsFit ( $parseFile ),
'rootFileName' => Filesystem :: fsFit ( $rootFileName ),
];
}
public static function parseWebDirNameData ( $table , $type , $value = '' ) : array
{
$pathArr = [];
if ( $value ) {
$value = str_replace ([ '.' , '/' , '\\' , '_' ], '/' , $value );
$pathArrTemp = explode ( '/' , $value );
$redundantDir = [ 'web' => 0 , 'src' => 1 , 'views' => 2 , 'lang' => 2 , 'backend' => 3 , 'pages' => 3 , 'en' => 4 , 'zh-cn' => 4 ];
foreach ( $pathArrTemp as $key => $item ) {
if ( ! array_key_exists ( $item , $redundantDir ) || $key !== $redundantDir [ $item ]) {
$pathArr [] = $item ;
}
}
} elseif ( isset ( self :: $parseWebDirPresets [ $type ]) && array_key_exists ( $table , self :: $parseWebDirPresets [ $type ])) {
$pathArr = self :: $parseWebDirPresets [ $type ][ $table ];
} else {
$table = str_replace ([ '.' , '/' , '\\' , '_' ], '/' , $table );
$pathArr = explode ( '/' , $table );
}
$originalLastName = array_pop ( $pathArr );
$pathArr = array_map ( 'strtolower' , $pathArr );
$lastName = lcfirst ( $originalLastName );
$webDir [ 'path' ] = $pathArr ;
$webDir [ 'lastName' ] = $lastName ;
$webDir [ 'originalLastName' ] = $originalLastName ;
if ( $type == 'views' ) {
$webDir [ 'views' ] = " web/src/views/backend " . ( $pathArr ? '/' . implode ( '/' , $pathArr ) : '' ) . " / $lastName " ;
} elseif ( $type == 'lang' ) {
$webDir [ 'lang' ] = array_merge ( $pathArr , [ $lastName ]);
foreach ([ 'en' , 'zh-cn' ] as $item ) {
$webDir [ $item ] = " web/src/lang/backend/ $item " . ( $pathArr ? '/' . implode ( '/' , $pathArr ) : '' ) . " / $lastName " ;
}
}
foreach ( $webDir as & $item ) {
if ( is_string ( $item )) $item = Filesystem :: fsFit ( $item );
}
return $webDir ;
}
public static function getMenuName ( array $webDir ) : string
{
return ( $webDir [ 'path' ] ? implode ( '/' , $webDir [ 'path' ]) . '/' : '' ) . $webDir [ 'originalLastName' ];
}
public static function getStubFilePath ( string $name ) : string
{
return root_path () . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'crud' . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . Filesystem :: fsFit ( $name ) . '.stub' ;
}
public static function arrayToString ( array | string $value ) : string
{
if ( ! is_array ( $value )) return $value ;
foreach ( $value as & $item ) {
$item = self :: arrayToString ( $item );
}
return implode ( PHP_EOL , $value );
}
public static function assembleStub ( string $name , array $data , bool $escape = false ) : string
{
foreach ( $data as & $datum ) {
$datum = self :: arrayToString ( $datum );
}
$search = $replace = [];
foreach ( $data as $k => $v ) {
$search [] = " { % $k %} " ;
$replace [] = $v ;
}
$stubPath = self :: getStubFilePath ( $name );
$stubContent = file_get_contents ( $stubPath );
$content = str_replace ( $search , $replace , $stubContent );
return $escape ? self :: escape ( $content ) : $content ;
}
public static function escape ( array | string $value ) : string
{
if ( is_array ( $value )) {
$value = json_encode ( $value , JSON_UNESCAPED_UNICODE );
}
return htmlspecialchars (( string ) $value , ENT_QUOTES , 'UTF-8' , false );
}
public static function tab ( int $num = 1 ) : string
{
return str_pad ( '' , 4 * $num );
}
public static function parseTableColumns ( string $table , bool $analyseField = false , ? string $connection = null ) : array
{
$connection = TableManager :: getConnection ( $connection );
$connectionConfig = TableManager :: getConnectionConfig ( $connection );
$sql = 'SELECT * FROM `information_schema`.`columns` '
. 'WHERE TABLE_SCHEMA = ? AND table_name = ? '
. 'ORDER BY ORDINAL_POSITION' ;
$tableColumn = Db :: connect ( $connection ) -> query ( $sql , [ $connectionConfig [ 'database' ], TableManager :: tableName ( $table , true , $connection )]);
$columns = [];
foreach ( $tableColumn as $item ) {
$isNullAble = $item [ 'IS_NULLABLE' ] == 'YES' ;
if ( str_contains ( $item [ 'COLUMN_TYPE' ], '(' )) {
$dataType = substr_replace ( $item [ 'COLUMN_TYPE' ], '' , stripos ( $item [ 'COLUMN_TYPE' ], ')' ) + 1 );
} else {
$dataType = str_replace ( ' unsigned' , '' , $item [ 'COLUMN_TYPE' ]);
}
$default = '' ;
if ( $isNullAble && $item [ 'COLUMN_DEFAULT' ] === null ) {
$defaultType = 'NULL' ;
} elseif ( $item [ 'COLUMN_DEFAULT' ] == '' && in_array ( $item [ 'DATA_TYPE' ], [ 'varchar' , 'char' ])) {
$defaultType = 'EMPTY STRING' ;
} elseif ( ! $isNullAble && $item [ 'COLUMN_DEFAULT' ] === null ) {
$defaultType = 'NONE' ;
} else {
$defaultType = 'INPUT' ;
$default = $item [ 'COLUMN_DEFAULT' ];
}
$column = [
'name' => $item [ 'COLUMN_NAME' ],
'type' => $item [ 'DATA_TYPE' ],
'dataType' => $dataType ,
'default' => $default ,
'defaultType' => $defaultType ,
'null' => $isNullAble ,
'primaryKey' => $item [ 'COLUMN_KEY' ] == 'PRI' ,
'unsigned' => ( bool ) stripos ( $item [ 'COLUMN_TYPE' ], 'unsigned' ),
'autoIncrement' => stripos ( $item [ 'EXTRA' ], 'auto_increment' ) !== false ,
'comment' => $item [ 'COLUMN_COMMENT' ],
'designType' => self :: getTableColumnsDataType ( $item ),
'table' => [],
'form' => [],
];
if ( $analyseField ) {
self :: analyseField ( $column );
} else {
self :: handleTableColumn ( $column );
}
$columns [ $item [ 'COLUMN_NAME' ]] = $column ;
}
return $columns ;
}
public static function handleTableColumn ( & $column ) : void
{
}
public static function analyseFieldType ( array $field ) : string
{
$dataType = ( isset ( $field [ 'dataType' ]) && $field [ 'dataType' ]) ? $field [ 'dataType' ] : $field [ 'type' ];
if ( stripos ( $dataType , '(' ) !== false ) {
$typeName = explode ( '(' , $dataType );
return trim ( $typeName [ 0 ]);
}
return trim ( $dataType );
}
public static function analyseFieldDataType ( array $field ) : string
{
if ( ! empty ( $field [ 'dataType' ])) return $field [ 'dataType' ];
$conciseType = self :: analyseFieldType ( $field );
$limit = self :: analyseFieldLimit ( $conciseType , $field );
if ( isset ( $limit [ 'precision' ])) {
return " $conciseType ( { $limit [ 'precision' ] } , { $limit [ 'scale' ] } ) " ;
}
if ( isset ( $limit [ 'values' ])) {
return " $conciseType ( " . implode ( ',' , $limit [ 'values' ]) . " ) " ;
}
return " $conciseType ( { $limit [ 'limit' ] } ) " ;
}
public static function analyseField ( & $field ) : void
{
$field [ 'type' ] = self :: analyseFieldType ( $field );
$field [ 'originalDesignType' ] = $field [ 'designType' ];
$designTypeComparison = [ 'pk' => 'string' , 'weigh' => 'number' , 'timestamp' => 'datetime' , 'float' => 'number' ];
if ( array_key_exists ( $field [ 'designType' ], $designTypeComparison )) {
$field [ 'designType' ] = $designTypeComparison [ $field [ 'designType' ]];
}
$supportMultipleComparison = [ 'select' , 'image' , 'file' , 'remoteSelect' ];
if ( in_array ( $field [ 'designType' ], $supportMultipleComparison )) {
$multiKey = $field [ 'designType' ] == 'remoteSelect' ? 'select-multi' : $field [ 'designType' ] . '-multi' ;
if ( isset ( $field [ 'form' ][ $multiKey ]) && $field [ 'form' ][ $multiKey ]) {
$field [ 'designType' ] = $field [ 'designType' ] . 's' ;
}
}
}
public static function getTableColumnsDataType ( $column ) : string
{
if ( stripos ( $column [ 'COLUMN_NAME' ], 'id' ) !== false && stripos ( $column [ 'EXTRA' ], 'auto_increment' ) !== false ) {
return 'pk' ;
}
if ( $column [ 'COLUMN_NAME' ] == 'weigh' ) return 'weigh' ;
if ( in_array ( $column [ 'COLUMN_NAME' ], [ 'createtime' , 'updatetime' , 'create_time' , 'update_time' ])) return 'timestamp' ;
foreach ( self :: $inputTypeRule as $item ) {
$typeBool = ! isset ( $item [ 'type' ]) || ! $item [ 'type' ] || in_array ( $column [ 'DATA_TYPE' ], $item [ 'type' ]);
$suffixBool = ! isset ( $item [ 'suffix' ]) || ! $item [ 'suffix' ] || self :: isMatchSuffix ( $column [ 'COLUMN_NAME' ], $item [ 'suffix' ]);
$columnTypeBool = ! isset ( $item [ 'column_type' ]) || ! $item [ 'column_type' ] || in_array ( $column [ 'COLUMN_TYPE' ], $item [ 'column_type' ]);
if ( $typeBool && $suffixBool && $columnTypeBool ) {
return $item [ 'value' ];
}
}
return 'string' ;
}
protected static function isMatchSuffix ( string $field , string | array $suffixArr ) : bool
{
$suffixArr = is_array ( $suffixArr ) ? $suffixArr : explode ( ',' , $suffixArr );
foreach ( $suffixArr as $v ) {
if ( preg_match ( " / $v $ /i " , $field )) return true ;
}
return false ;
}
public static function createMenu ( $webViewsDir , $tableComment ) : void
{
$menuName = self :: getMenuName ( $webViewsDir );
if ( AdminRule :: where ( 'name' , $menuName ) -> value ( 'id' )) {
return ;
}
$menuChildren = self :: $menuChildren ;
foreach ( $menuChildren as & $item ) {
$item [ 'name' ] = $menuName . $item [ 'name' ];
}
$componentPath = str_replace ([ '\\' , 'web/src' ], [ '/' , '/src' ], $webViewsDir [ 'views' ] . '/' . 'index.vue' );
$menus = [
'type' => 'menu' ,
'title' => $tableComment ? : $webViewsDir [ 'originalLastName' ],
'name' => $menuName ,
'path' => $menuName ,
'menu_type' => 'tab' ,
'keepalive' => 1 ,
'component' => $componentPath ,
'children' => $menuChildren ,
];
$paths = array_reverse ( $webViewsDir [ 'path' ]);
foreach ( $paths as $path ) {
$menus = [
'type' => 'menu_dir' ,
'title' => $path ,
'name' => $path ,
'path' => $path ,
'children' => [ $menus ],
];
}
Menu :: create ([ $menus ], 0 , 'ignore' );
}
public static function writeWebLangFile ( $langData , $webLangDir ) : void
{
foreach ( $langData as $lang => $langDatum ) {
$langTsContent = '' ;
foreach ( $langDatum as $key => $item ) {
$quote = self :: getQuote ( $item );
$keyStr = self :: formatObjectKey ( $key );
$langTsContent .= self :: tab () . $keyStr . " : $quote $item $quote , \n " ;
}
$langTsContent = " export default { \n " . $langTsContent . " } \n " ;
self :: writeFile ( root_path () . $webLangDir [ $lang ] . '.ts' , $langTsContent );
}
}
public static function writeFile ( $path , $content ) : bool | int
{
$path = Filesystem :: fsFit ( $path );
if ( ! is_dir ( dirname ( $path ))) {
mkdir ( dirname ( $path ), 0755 , true );
}
return file_put_contents ( $path , $content );
}
public static function buildModelAppend ( $append ) : string
{
if ( ! $append ) return '' ;
return " \n " . self :: tab () . " // 追加属性 \n " . self :: tab () . " protected \$ append = " . self :: buildFormatSimpleArray ( $append ) . " ; \n " ;
}
public static function buildModelFieldType ( array $fieldType ) : string
{
if ( ! $fieldType ) return '' ;
$maxStrLang = 0 ;
foreach ( $fieldType as $key => $item ) {
$maxStrLang = max ( strlen ( $key ), $maxStrLang );
}
$str = '' ;
foreach ( $fieldType as $key => $item ) {
$str .= self :: tab ( 2 ) . " ' $key ' " . str_pad ( '=>' , ( $maxStrLang - strlen ( $key ) + 3 ), ' ' , STR_PAD_LEFT ) . " ' $item ', \n " ;
}
return " \n " . self :: tab () . " // 字段类型转换 \n " . self :: tab () . " protected \$ type = [ \n " . rtrim ( $str , " \n " ) . " \n " . self :: tab () . " ]; \n " ;
}
public static function writeModelFile ( string $tablePk , array $fieldsMap , array $modelData , array $modelFile ) : void
{
if ( $modelData [ 'connection' ] && $modelData [ 'connection' ] != config ( 'thinkorm.default' , config ( 'database.default' , 'mysql' ))) {
$modelData [ 'connection' ] = " \n " . self :: tab () . " // 数据库连接配置标识 \n " . self :: tab () . 'protected $connection = ' . " ' { $modelData [ 'connection' ] } '; \n " ;
} else {
$modelData [ 'connection' ] = '' ;
}
$modelData [ 'pk' ] = $tablePk == 'id' ? '' : " \n " . self :: tab () . " // 表主键 \n " . self :: tab () . 'protected $pk = ' . " ' $tablePk '; \n " ;
$modelData [ 'autoWriteTimestamp' ] = array_key_exists ( self :: $createTimeField , $fieldsMap ) || array_key_exists ( self :: $updateTimeField , $fieldsMap ) ? 'true' : 'false' ;
if ( $modelData [ 'autoWriteTimestamp' ] == 'true' ) {
$modelData [ 'createTime' ] = array_key_exists ( self :: $createTimeField , $fieldsMap ) ? '' : " \n " . self :: tab () . " protected \$ createTime = false; " ;
$modelData [ 'updateTime' ] = array_key_exists ( self :: $updateTimeField , $fieldsMap ) ? '' : " \n " . self :: tab () . " protected \$ updateTime = false; " ;
}
$modelMethodList = isset ( $modelData [ 'relationMethodList' ]) ? array_merge ( $modelData [ 'methods' ], $modelData [ 'relationMethodList' ]) : $modelData [ 'methods' ];
$modelData [ 'methods' ] = $modelMethodList ? " \n " . implode ( " \n " , $modelMethodList ) : '' ;
$modelData [ 'append' ] = self :: buildModelAppend ( $modelData [ 'append' ] ? ? []);
$modelData [ 'fieldType' ] = self :: buildModelFieldType ( $modelData [ 'fieldType' ] ? ? []);
if ( isset ( $modelData [ 'beforeInsertMixins' ][ 'snowflake' ])) {
$modelData [ 'beforeInsert' ] = self :: assembleStub ( 'mixins/model/beforeInsert' , [
'setSnowFlakeIdCode' => $modelData [ 'beforeInsertMixins' ][ 'snowflake' ]
]);
}
if (( $modelData [ 'afterInsert' ] ? ? '' ) && ( $modelData [ 'beforeInsert' ] ? ? '' )) {
$modelData [ 'afterInsert' ] = " \n " . $modelData [ 'afterInsert' ];
}
$modelFileContent = self :: assembleStub ( 'mixins/model/model' , $modelData );
self :: writeFile ( $modelFile [ 'parseFile' ], $modelFileContent );
}
public static function writeControllerFile ( array $controllerData , array $controllerFile ) : void
{
if ( isset ( $controllerData [ 'relationVisibleFieldList' ]) && $controllerData [ 'relationVisibleFieldList' ]) {
$relationVisibleFields = '->visible([' ;
foreach ( $controllerData [ 'relationVisibleFieldList' ] as $cKey => $controllerDatum ) {
$relationVisibleFields .= " ' $cKey ' => [' " . implode ( " ', ' " , $controllerDatum ) . " '], " ;
}
$relationVisibleFields = rtrim ( $relationVisibleFields , ', ' ) . '])' ;
$controllerData [ 'methods' ][ 'index' ] = self :: assembleStub ( 'mixins/controller/index' , [
'relationVisibleFields' => $relationVisibleFields
]);
$controllerData [ 'use' ][ 'Throwable' ] = " \n use Throwable; " ;
unset ( $controllerData [ 'relationVisibleFieldList' ]);
}
$controllerAttr = '' ;
foreach ( $controllerData [ 'attr' ] ? ? [] as $key => $item ) {
$attrType = self :: $attrType [ 'controller' ][ $key ] ? ? '' ;
if ( is_array ( $item )) {
$controllerAttr .= " \n " . self :: tab () . " protected $attrType \$ $key = [' " . implode ( " ', ' " , $item ) . " ']; \n " ;
} elseif ( $item ) {
$controllerAttr .= " \n " . self :: tab () . " protected $attrType \$ $key = ' $item '; \n " ;
}
}
$controllerData [ 'attr' ] = $controllerAttr ;
$controllerData [ 'initialize' ] = self :: assembleStub ( 'mixins/controller/initialize' , [
'modelNamespace' => $controllerData [ 'modelNamespace' ],
'modelName' => $controllerData [ 'modelName' ],
'filterRule' => $controllerData [ 'filterRule' ] ? ? '' ,
]);
$contentFileContent = self :: assembleStub ( 'mixins/controller/controller' , $controllerData );
self :: writeFile ( $controllerFile [ 'parseFile' ], $contentFileContent );
}
public static function writeFormFile ( $formVueData , $webViewsDir , $fields , $webTranslate ) : void
{
$fieldHtml = " \n " ;
$formVueData [ 'bigDialog' ] = $formVueData [ 'bigDialog' ] ? " \n " . self :: tab ( 2 ) . 'width="70%"' : '' ;
foreach ( $formVueData [ 'formFields' ] ? ? [] as $field ) {
$fieldHtml .= self :: tab ( 5 ) . " <FormItem " ;
foreach ( $field as $key => $attr ) {
if ( is_array ( $attr )) {
$fieldHtml .= ' ' . $key . '="' . self :: getJsonFromArray ( $attr ) . '"' ;
} else {
$fieldHtml .= ' ' . $key . '="' . $attr . '"' ;
}
}
$fieldHtml .= " /> \n " ;
}
$formVueData [ 'formFields' ] = rtrim ( $fieldHtml , " \n " );
foreach ( $fields as $field ) {
if ( isset ( $field [ 'form' ][ 'validator' ])) {
foreach ( $field [ 'form' ][ 'validator' ] as $item ) {
$message = isset ( $field [ 'form' ][ 'validatorMsg' ]) && $field [ 'form' ][ 'validatorMsg' ] ? " , message: ' { $field [ 'form' ][ 'validatorMsg' ] } ' " : '' ;
$formVueData [ 'formValidatorRules' ][ $field [ 'name' ]][] = " buildValidatorData( { name: ' $item ', title: t(' $webTranslate { $field [ 'name' ] } ') $message }) " ;
}
}
}
if ( $formVueData [ 'formValidatorRules' ] ? ? []) {
$formVueData [ 'imports' ][] = " import { buildValidatorData } from '/@/utils/validate' " ;
}
$formVueData [ 'importExpand' ] = self :: buildImportExpand ( $formVueData [ 'imports' ] ? ? []);
$formVueData [ 'formItemRules' ] = self :: buildFormValidatorRules ( $formVueData [ 'formValidatorRules' ] ? ? []);
$formVueContent = self :: assembleStub ( 'html/form' , $formVueData );
self :: writeFile ( root_path () . $webViewsDir [ 'views' ] . '/' . 'popupForm.vue' , $formVueContent );
}
public static function buildImportExpand ( array $imports ) : string
{
$importExpand = '' ;
foreach ( $imports as $import ) {
$importExpand .= " \n $import " ;
}
return $importExpand ;
}
public static function buildFormValidatorRules ( array $formValidatorRules ) : string
{
$rulesHtml = " " ;
foreach ( $formValidatorRules as $key => $formItemRule ) {
$rulesArrHtml = '' ;
foreach ( $formItemRule as $item ) {
$rulesArrHtml .= $item . ', ' ;
}
$rulesHtml .= self :: tab () . $key . ': [' . rtrim ( $rulesArrHtml , ', ' ) . " ], \n " ;
}
return $rulesHtml ? " \n " . $rulesHtml : '' ;
}
public static function writeIndexFile ( $indexVueData , $webViewsDir , $controllerFile ) : void
{
$indexVueData [ 'optButtons' ] = self :: buildSimpleArray ( $indexVueData [ 'optButtons' ] ? ? []);
$indexVueData [ 'defaultItems' ] = self :: getJsonFromArray ( $indexVueData [ 'defaultItems' ] ? ? []);
$indexVueData [ 'tableColumn' ] = self :: buildTableColumn ( $indexVueData [ 'tableColumn' ] ? ? []);
$indexVueData [ 'dblClickNotEditColumn' ] = self :: buildSimpleArray ( $indexVueData [ 'dblClickNotEditColumn' ] ? ? [ 'undefined' ]);
$controllerFile [ 'path' ][] = $controllerFile [ 'originalLastName' ];
$indexVueData [ 'controllerUrl' ] = '\'/admin/' . ( $controllerFile [ 'path' ] ? implode ( '.' , $controllerFile [ 'path' ]) : '' ) . '/\'' ;
$indexVueData [ 'componentName' ] = ( $webViewsDir [ 'path' ] ? implode ( '/' , $webViewsDir [ 'path' ]) . '/' : '' ) . $webViewsDir [ 'originalLastName' ];
$indexVueContent = self :: assembleStub ( 'html/index' , $indexVueData );
self :: writeFile ( root_path () . $webViewsDir [ 'views' ] . '/' . 'index.vue' , $indexVueContent );
}
public static function buildTableColumn ( $tableColumnList ) : string
{
$columnJson = '' ;
$emptyUnset = [ 'comSearchInputAttr' , 'replaceValue' , 'custom' ];
foreach ( $tableColumnList as $column ) {
foreach ( $emptyUnset as $unsetKey ) {
if ( empty ( $column [ $unsetKey ])) unset ( $column [ $unsetKey ]);
}
$columnJson .= self :: tab ( 3 ) . '{' ;
foreach ( $column as $key => $item ) {
$columnJson .= self :: buildTableColumnKey ( $key , $item );
}
$columnJson = rtrim ( $columnJson , ',' ) . " }, \n " ;
}
return rtrim ( $columnJson , " \n " );
}
public static function buildTableColumnKey ( $key , $item ) : string
{
$key = self :: formatObjectKey ( $key );
if ( is_array ( $item )) {
$itemJson = ' ' . $key . ': {' ;
foreach ( $item as $ik => $iItem ) {
$itemJson .= self :: buildTableColumnKey ( $ik , $iItem );
}
$itemJson = rtrim ( $itemJson , ',' ) . ' },' ;
} elseif ( $item === 'false' || $item === 'true' ) {
$itemJson = ' ' . $key . ': ' . $item . ',' ;
} elseif ( in_array ( $key , [ 'label' , 'width' , 'buttons' ], true ) || str_starts_with (( string ) $item , " t(' " ) || str_starts_with (( string ) $item , 't("' )) {
$itemJson = ' ' . $key . ': ' . $item . ',' ;
} else {
$itemJson = ' ' . $key . ': \'' . $item . '\',' ;
}
return $itemJson ;
}
public static function formatObjectKey ( string $keyName ) : string
{
if ( preg_match ( " /^[a-zA-Z_][a-zA-Z0-9_]+ $ / " , $keyName )) {
return $keyName ;
}
$quote = self :: getQuote ( $keyName );
return " $quote $keyName $quote " ;
}
public static function getQuote ( string $value ) : string
{
return stripos ( $value , " ' " ) === false ? " ' " : '"' ;
}
public static function buildFormatSimpleArray ( $arr , int $tab = 2 ) : string
{
if ( ! $arr ) return '[]' ;
$str = '[' . PHP_EOL ;
foreach ( $arr as $item ) {
if ( $item == 'undefined' || $item == 'false' || is_numeric ( $item )) {
$str .= self :: tab ( $tab ) . $item . ',' . PHP_EOL ;
} else {
$quote = self :: getQuote (( string ) $item );
$str .= self :: tab ( $tab ) . " $quote $item $quote , " . PHP_EOL ;
}
}
return $str . self :: tab ( $tab - 1 ) . ']' ;
}
public static function buildSimpleArray ( $arr ) : string
{
if ( ! $arr ) return '[]' ;
$str = '' ;
foreach ( $arr as $item ) {
if ( $item == 'undefined' || $item == 'false' || is_numeric ( $item )) {
$str .= $item . ', ' ;
} else {
$quote = self :: getQuote (( string ) $item );
$str .= " $quote $item $quote , " ;
}
}
return '[' . rtrim ( $str , " , " ) . ']' ;
}
public static function buildDefaultOrder ( string $field , string $type ) : string
{
if ( $field && $type ) {
$defaultOrderStub = self :: getJsonFromArray ([ 'prop' => $field , 'order' => $type ]);
if ( $defaultOrderStub ) {
return " \n " . self :: tab ( 2 ) . " defaultOrder: " . $defaultOrderStub . ',' ;
}
}
return '' ;
}
public static function getJsonFromArray ( $arr )
{
if ( is_array ( $arr )) {
$jsonStr = '' ;
foreach ( $arr as $key => $item ) {
$keyStr = ' ' . self :: formatObjectKey ( $key ) . ': ' ;
if ( is_array ( $item )) {
$jsonStr .= $keyStr . self :: getJsonFromArray ( $item ) . ',' ;
} elseif ( $item === 'false' || $item === 'true' ) {
$jsonStr .= $keyStr . ( $item === 'false' ? 'false' : 'true' ) . ',' ;
} elseif ( $item === null ) {
$jsonStr .= $keyStr . 'null,' ;
} elseif ( str_starts_with (( string ) $item , " t(' " ) || str_starts_with (( string ) $item , 't("' ) || $item == '[]' || in_array ( gettype ( $item ), [ 'integer' , 'double' ])) {
$jsonStr .= $keyStr . $item . ',' ;
} elseif ( isset ( $item [ 0 ]) && $item [ 0 ] == '[' && str_ends_with (( string ) $item , ']' )) {
$jsonStr .= $keyStr . $item . ',' ;
} else {
$quote = self :: getQuote (( string ) $item );
$jsonStr .= $keyStr . " $quote $item $quote , " ;
}
}
return $jsonStr ? '{' . rtrim ( $jsonStr , ',' ) . ' }' : '{}' ;
}
return $arr ;
}
}