This commit is contained in:
gmotov 2025-10-10 01:18:27 +03:00
parent 75a8dcaa3b
commit 3342a9ee4b
9 changed files with 499 additions and 15 deletions

View File

@ -1,5 +1,5 @@
{
"name": "drobnitsa/dictionary-yii2-generator",
"name": "drobnitsa/yii2-gii-dictionary-generator",
"description": "Генератор справочников для проектов",
"type": "yii2-extension",
"authors": [
@ -13,7 +13,7 @@
},
"autoload": {
"psr-4": {
"drobnitsa\\DictionaryYii2Generator\\": "src/"
"drobnitsa\\dictionaryGenerator\\": "src/"
}
}
}

View File

@ -1,26 +1,170 @@
<?php
namespace Drobnitsa\DictionaryYii2Generator;
namespace drobnitsa\dictionaryGenerator;
class Generator
use common\models\Addition;
use Yii;
use yii\gii\CodeFile;
use yii\helpers\Inflector;
class Generator extends yii\gii\Generator
{
public function generate($params = [])
public $modelNs = 'common\models';
public $modelName = '';
public $attributes;
public $moduleId = 'backend';
public $controllerId = '';
public $generateMigration = true;
public $migrationNs = 'console\migrations';
public $migrationName = '';
public function getName()
{
$namespace = $params['namespace'] ?? 'app\\controllers';
$basePath = $params['basePath'] ?? \Yii::getAlias('@app');
return 'Dictionary CRUD Generator';
}
// пример генерации файла
$filePath = $basePath . '/controllers/' . ucfirst($params['name']) . 'Controller.php';
public function getDescription()
{
return 'Генерирует контроллер и виды для заданной модели и полей.';
}
$content = "<?php\n\nnamespace {$namespace};\n\nclass " . ucfirst($params['name']) . "Controller extends \\yii\\web\\Controller\n{\n // ...\n}\n";
public function rules()
{
return [
[['modelNs', 'modelName', 'moduleId', 'controllerId'], 'required'],
[['migrationNs', 'modelNs', 'moduleId'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
['modelName', 'validateModelClass'],
['generateMigration', 'boolean'],
[['migrationName', 'controllerId'], 'string'],
['attributes', 'safe']
// ['attributes', 'string', 'length' => [0, 1024]],
];
}
// создаем файл
if (!file_exists(dirname($filePath))) {
mkdir(dirname($filePath), 0777, true);
public function validateModelClass($attribute, $params)
{
if (!class_exists($this->modelClass)) {
$this->addError($attribute, 'Модель не найдена.');
}
}
public function getModelClass()
{
return $this->modelNs . '\\' . $this->modelName;
}
public function getControllerNs()
{
return $this->moduleId . '\controllers';
}
public function generate()
{
$controllerName = Inflector::id2camel($this->controllerId).'Controller';
$params = [
'modelName' => $this->modelName,
'modelClass' => $this->modelClass,
'controllerNs' => $this->controllerNs,
'controllerId' => $this->controllerId,
'controllerName' => Inflector::id2camel($this->controllerId),
'attributes' => $this->attributes,
'migrationName' => $this->migrationName,
'moduleId' => $this->moduleId
];
$controllerCode = $this->render('controller.php', $params);
$viewCode = $this->render('view.php', $params);
$searchCode = $this->render('_search.php', $params);
$nsPath = str_replace('\\', '/', $this->controllerNs);
$controllerPath = \Yii::getAlias("@$nsPath/{$controllerName}.php");
$viewDir = \Yii::getAlias("@backend/views/{$this->controllerId}");
$files = [
new CodeFile($controllerPath, $controllerCode),
new CodeFile("$viewDir/index.php", $viewCode),
new CodeFile("$viewDir/_search.php", $searchCode),
];
if($this->generateMigration && $this->migrationName) {
$nsPath = str_replace('\\', '/', $this->migrationNs);
$migrationPath = \Yii::getAlias("@$nsPath/{$this->migrationName}.php");
$migrationCode = $this->render('migration.php', $params);
$files[] = new CodeFile($migrationPath, $migrationCode);
}
file_put_contents($filePath, $content);
return $files;
}
return $filePath;
/**
* Возвращает список атрибутов модели для выбора в форме
*/
public function getModelAttributes($plain = true)
{
if (!class_exists($this->modelClass)) {
return [];
}
$model = new $this->modelClass();
if(!$plain) {
$result = [];
foreach ($model->attributes() as $attribute) {
$result[$attribute] = $attribute;
}
return $result;
}
return $model->attributes();
// return $model->attributeLabels();
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return array_merge(parent::attributeLabels(), [
'modelNs' => 'Model namespace',
'moduleId' => 'Module',
]);
}
/**
* @inheritdoc
*/
public function hints()
{
return array_merge(parent::hints(), [
'modelNs' => 'This is the namespace for the source model, e.g., <code>app\models</code>',
'moduleId' => 'This is the module name for the controller and view to be generated, e.g., <code>app\controllers</code>',
]);
}
/**
* @inheritdoc
*/
public function stickyAttributes()
{
return array_merge(
parent::stickyAttributes(),
[
'modelNs',
'moduleId',
'migrationNs'
]
);
}
public function actionGetAttributes()
{
if(class_exists($this->modelClass)) {
return json_encode([
'result' => true,
'controller_id' => Inflector::pluralize(Inflector::camel2id($this->modelName)),
'attributes' => $this->getModelAttributes()
]);
}
return json_encode(['result' => false]);
}
}

13
src/assets/MainAsset.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace drobnitsa\dictionaryGenerator\assets;
use yii\web\AssetBundle;
class MainAsset extends AssetBundle
{
public $sourcePath = '@vendor/drobnitsa/yii2-gii-dictionary-generator/src/assets';
public $js = [
'main.js',
];
}

99
src/assets/main.js Normal file
View File

@ -0,0 +1,99 @@
(function() {
const checkbox = document.querySelector('form #generator-generatemigration');
function toggleFields() {
const isChecked = checkbox.checked;
const migrationFields = document.querySelectorAll('form .field-generator-migrationns, form .field-generator-migrationname');
migrationFields.forEach(function(item) {
item.style.display = isChecked ? '' : 'none';
});
}
checkbox.addEventListener('change', toggleFields);
toggleFields();
let modelNsInput = document.querySelector('#generator-modelns');
let modelInput = document.querySelector('#generator-modelname');
let controllerInput = document.querySelector('#generator-controllerid');
let attributesContainer = document.querySelector('#generator-attributes');
let autoUpdateController = true;
let debounceTimer = null;
controllerInput && controllerInput.addEventListener('input', () => {
autoUpdateController = false;
});
modelInput && modelInput.addEventListener('input', () => {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
const modelNs = modelNsInput.value.trim();
if (modelNs.length === 0) return;
const modelClassName = modelInput.value.trim();
if (modelClassName.length === 0) return;
const data = new FormData();
data.append('Generator[modelNs]', modelNs)
data.append('Generator[modelName]', modelClassName)
data.append(yii.getCsrfParam(), yii.getCsrfToken());
// AJAX-запрос на получение атрибутов
fetch('default/action?id=dictionary&name=GetAttributes', {
method: 'POST',
body: data,
})
.then(res => res.json())
.then(data => {
if(data.result) {
if (attributesContainer) {
attributesContainer.innerHTML = '';
data.attributes.forEach(attr => {
let label = document.createElement('label');
label.className = 'checkbox-label';
let checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.name = 'Generator[attributes][]';
checkbox.value = attr;
label.appendChild(checkbox);
label.appendChild(document.createTextNode(' ' + attr));
attributesContainer.appendChild(label);
attributesContainer.appendChild(document.createElement('br'));
});
}
controllerInput.value = data.controller_id;
generateMigrationName(data.controller_id);
}
});
}, 500);
});
function generateMigrationName(controllerId) {
let migrationNameInput = document.querySelector('#generator-migrationname');
// Получаем текущие дата и время
const now = new Date();
// Форматируем дату и время в виде "yymmdd_hhmmss"
const year = now.getFullYear().toString().slice(-2);
const month = ('0' + (now.getMonth() + 1)).slice(-2);
const day = ('0' + now.getDate()).slice(-2);
const hours = ('0' + now.getHours()).slice(-2);
const minutes = ('0' + now.getMinutes()).slice(-2);
const seconds = ('0' + now.getSeconds()).slice(-2);
const timestamp = `${year}${month}${day}_${hours}${minutes}${seconds}`;
// Модифицируем имя таблицы: заменяем дефисы на подчеркивания, убираем расширения
// Предположим, что tableName — это строка с дефисами, например: "furniture-packs"
const formattedName = controllerId.replace(/-/g, '_');
migrationNameInput.value = `m${timestamp}_insert_${formattedName}_permissions`;
}
})();

53
src/default/_search.php Normal file
View File

@ -0,0 +1,53 @@
<?php
/**
* This is the template for generating the controller class of a specified model.
*/
/** @var yii\web\View $this */
/** @var \drobnitsa\dictionaryGenerator\Generator $generator */
/** @var string $controllerNs controller namespace */
/** @var string $controllerName controller name */
/** @var string $modelName model name */
/** @var string $modelClass model class */
/** @var string[] $attributes attributes to render */
echo "<?php\n";
?>
use yii\helpers\Html;
use common\widgets\ActiveForm;
/* @var $this yii\web\View */
/* @var $model common\models\Addition */
/* @var $form yii\widgets\ActiveForm */
?>
<div class="form-search">
<?= "<?php " ?>
$form = ActiveForm::begin([
'action' => ['index'],
'method' => 'get',
]); ?>
<div class="row search-block">
<?php foreach ($attributes as $attribute) : ?>
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
<?= "<?= " ?> $form->field($model, '<?= $attribute ?>')->textInput(['maxlength' => true]) ?>
</div>
<?php endforeach; ?>
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
<div class="form-group">
<label for="" class="control-label">&nbsp;</label>
<div class="input-group">
<?= "<?= " ?> Html::submitButton('<i class="fa fa-trash"></i>', ['class' => 'btn btn-default btn-outline-dark', 'title' => 'Очистить', 'onclick' => "var form = jQuery(this.form); form.find('input[type=text]').val(''); form.find('select').val(0); return true;"]) ?>
</div>
</div>
</div>
</div>
<?= "<?php " ?> ActiveForm::end(); ?>
<hr/>
</div>

View File

@ -0,0 +1,24 @@
<?php
/**
* This is the template for generating the controller class of a specified model.
*/
/** @var yii\web\View $this */
/** @var \drobnitsa\dictionaryGenerator\Generator $generator */
/** @var string $controllerNs controller namespace */
/** @var string $controllerName controller name */
/** @var string $modelName model name */
/** @var string $modelClass model class */
echo "<?php\n";
?>
declare(strict_types=1);
namespace <?= $controllerNs ?>;
use <?= $modelClass ?>;
class <?= $controllerName.'Controller' ?> extends AjaxController
{
public string $modelClass = <?= $modelName ?>::class;
}

31
src/default/migration.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/**
* This is the template for generating the controller class of a specified model.
*/
/** @var yii\web\View $this */
/** @var \drobnitsa\dictionaryGenerator\Generator $generator */
/** @var string $moduleId module id */
/** @var string $controllerId controller id */
/** @var string $migrationName migration name */
echo "<?php\n";
?>
use backend\modules\rbac\migrations\BasePermissionsMigration;
class <?= $migrationName ?> extends BasePermissionsMigration
{
function configure()
{
$this->moduleId = '<?= $moduleId ?>';
$this->controllerId = '<?= $controllerId ?>';
$this->actions = $this->defaultActions;
$this->baseAction = 'index';
$this->childrenMap = [
'create' => [
'edit'
]
];
}
}

85
src/default/view.php Normal file
View File

@ -0,0 +1,85 @@
<?php
/**
* This is the template for generating the controller class of a specified model.
*/
/** @var yii\web\View $this */
/** @var \drobnitsa\dictionaryGenerator\Generator $generator */
/** @var string $controllerNs controller namespace */
/** @var string $controllerName controller name */
/** @var string $modelName model name */
/** @var string $modelClass model class */
/** @var string[] $attributes attributes to render */
echo "<?php\n";
?>
use backend\assets\EditableFieldsAsset;
use <?= $modelClass ?>;
use common\grid\GridView;
use common\widgets\Card;
use yii\helpers\Html;
use yii\helpers\Url;
use yii\widgets\Pjax;
/* @var $this yii\web\View */
/* @var $searchModel <?= $modelClass ?> */
/* @var $dataProvider yii\data\ActiveDataProvider */
EditableFieldsAsset::register($this);
$page = Yii::$app->request->getQueryParams()['page'] ?? 1;
$can_create = Yii::$app->user->can("backend_{$this->context->id}_create");
$can_update = Yii::$app->user->can("backend_{$this->context->id}_edit");
$can_delete = Yii::$app->user->can("backend_{$this->context->id}_delete");
$this->title = Yii::$app->urlManager->getLastTitle();
?>
<?= "<?= " ?> $this->render('_search', ['model' => $searchModel]); ?>
<?= "<?php " ?>Card::begin([]); ?>
<?= "<?php " ?>Pjax::begin(['id' => 'pjax']) ?>
<?= "<?= " ?> GridView::widget([
'dataProvider' => $dataProvider,
'createRowModel' => new <?= $modelName ?>(),
'formatter' => ['class' => 'yii\i18n\Formatter','nullDisplay' => ''],
'filterSelector' => '.search-block *[name]',
'tableOptions' => ['class' => 'table align-middle table-check table-bordered mb-0 editable-fields', 'data-base_url' => $this->context->id, 'id' => 'table-'.$this->context->id],
'actions' => common\widgets\ActionButtons::widget(['defaultShowTitle' => false, 'defaultAccess' => '$', 'items' => [
['name' => 'create', 'access' => $can_create, 'options' => ['class' => 'btn btn-success btn-sm btn-row-add', 'data-id' => 0], 'title' => 'Добавить', 'iconClass' => 'fa fa-plus'],
$page > 0 ?
['name' => Url::current(['page' => -1]), 'options' => ['class' => 'btn btn-primary btn-sm'], 'title' => 'Показать все', 'iconClass' => 'fa fa-expand'] :
['name' => Url::current(['page' => 1]), 'options' => ['class' => 'btn btn-primary btn-sm'], 'title' => 'Показать постранично', 'iconClass' => 'fa fa-compress'],
]]),
'columns' => [
[
'class' => 'yii\grid\SerialColumn',
'header' => '№',
],
<?php foreach($attributes as $attribute) : ?>
[
'attribute' => '<?= $attribute ?>',
'format' => 'raw',
'value' => function($model) use ($can_update) {
return $can_update ? Html::input('text', '<?= $attribute ?>', $model-><?= $attribute ?>, ['class' => 'form-control', 'required' => 1]) : $model-><?= $attribute ?>;
},
],
<?php endforeach; ?>
[
'class' => 'common\grid\ActionColumn',
'defaultShowTitle' => false,
'visible' => $can_delete,
'buttons' => [
'delete' => ['icon' => 'fa fa-trash', 'class' => 'btn btn-danger btn-sm btn-row-remove', 'title' => 'Удаление'],
],
]
],
]);
?>
<?= "<?php " ?> Pjax::end() ?>
<?= "<?php " ?> Card::end(); ?>

35
src/form.php Normal file
View File

@ -0,0 +1,35 @@
<?php
use drobnitsa\dictionaryGenerator\Generator;
use drobnitsa\dictionaryGenerator\assets\MainAsset;
use yii\helpers\Html;
use yii\helpers\ArrayHelper;
use yii\widgets\ActiveForm;
/* @var $generator Generator */
/* @var $this yii\web\View */
/* @var yii\widgets\ActiveForm $form */
MainAsset::register($this);
$modelAttributes = $generator->getModelAttributes(false);
?>
<?= $form->field($generator, 'modelNs') ?>
<?= $form->field($generator, 'modelName')->textInput(['placeholder' => 'PostRecord']) ?>
<?= $form->field($generator, 'moduleId') ?>
<?= $form->field($generator, 'controllerId')->textInput(['placeholder' => 'post-records']) ?>
<?= $form->field($generator, 'attributes')->checkboxList($modelAttributes, ['separator' => '<br>']) ?>
<?= $form->field($generator, 'generateMigration')->checkbox() ?>
<?= $form->field($generator, 'migrationNs') ?>
<?= $form->field($generator, 'migrationName')->textInput(['placeholder' => 'm250907_112925_insert_furniture_packs_permissions']) ?>