Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gii enhancement idea for model-generator #494

Open
rackycz opened this issue Dec 24, 2021 · 7 comments
Open

Gii enhancement idea for model-generator #494

rackycz opened this issue Dec 24, 2021 · 7 comments

Comments

@rackycz
Copy link

rackycz commented Dec 24, 2021

Hi. Sometimes it might be handy to split model into 2 classes:

  1. class UserBase extends \yii\db\ActiveRecord - contains all code generated by Gii (placed in folder "models/base")
  2. class User extends UserBase (placed in standard folder "models" and is empty at the beginning)

UserBase contains generated code while User contains code that was produced by the programmer. The programmer can also decide to use pure PHP in model User while in UserBase can be Yii-dependent code. But it is not a rule. Code then can be more readable as classes are shorter etc.

Programmer still uses the class User as usually. No changes here.

So I am suggesting this as a new option (checkbox) in Gii.

My solution:

I extended yii\gii\generators\model\Generator and following is the result. Only method generate() was changed a little. See $this->splitModelIntoBaseClass below. (In comments are also mentioned all JS and form changes)

If you think this might be usefull for others, you can check/use my code below. But it is nothing special. Just an idea.

Good luck to yo,u Yii.

PS: Gii is amazing. When I compare it to other frameworks, you are ahead by miles as Gii is delivered directly by Yii and one has all functionalities in one bundle.

image

<?php
namespace app\gii\generators\model;

use Yii;
use yii\db\ActiveRecord;
use yii\gii\CodeFile;

class Generator extends yii\gii\generators\model\Generator
{

/*
Following should be present in file yii-basic\vendor\yiisoft\yii2-gii\src\generators\model\form.php

echo $form->field($generator, 'splitModelIntoBaseClass')->checkbox(['onclick' => "$('div#splitModelIntoBaseClass').toggle($(this).is(':checked'))"]);
$displayBaseInputs = $generator->splitModelIntoBaseClass?'block':'none';
echo '<div id="splitModelIntoBaseClass" style="color: #6c757d;background-color: rgb(240,240,240);padding:1rem;border-radius: 0.25rem;display:'.$displayBaseInputs.';">';
echo $form->field($generator, 'baseModelClass');
echo $form->field($generator, 'baseModelNamespace');
echo '</div>';

$this->registerJs(
  '$("input#generator-modelclass").change(function(){ $("input#generator-basemodelclass").val($(this).val() + "Base"); $("input#generator-queryclass").val($(this).val() + "Query"); })',
  4,
  'my-button-handler'
);
*/

  public $baseModelClass = '';
  public $baseModelNamespace = 'app\models\base';
  public $splitModelIntoBaseClass = true;

  public function getName()
  {
    return 'My Model Generator';
  }

  public function hints()
  {
    return array_merge(parent::hints(), [
      'splitModelIntoBaseClass' => 'Code of the model will be split into 2 files: <strong>models/base/TableNameBase.php</strong> and <strong>models/TableName.php</strong>. The former contains all Gii-generated code while the latter EXTENDS IT and is meant for your (possibly pure PHP) code. Now it will be empty, but you can still use it as the standard model. ',

    ]);
  }

  public function attributeLabels()
  {
    return array_merge(parent::attributeLabels(), [
      'baseModelNamespace' => 'Namespace (folder) of the base class',
    ]);
  }

  // rules were copied 1:1 from the parent class, only attribute names were changed

  public function rules()
  {
    $result = array_merge(parent::rules(), [
      [['baseModelNamespace', 'baseModelClass'], 'filter', 'filter' => 'trim'],
      [['baseModelNamespace'], 'filter', 'filter' => function ($value) { return trim($value, '\\'); }],

      [['baseModelNamespace'], 'required'],
      [['baseModelClass'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'],
      [['baseModelNamespace'], 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'],
      [['baseModelNamespace'], 'validateNamespace'],
      [['baseModelClass'], 'validateModelClass', 'skipOnEmpty' => false],
      [['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]],

      [['splitModelIntoBaseClass'], 'boolean'],
    ]);
    return $result;
  }

  public function generate()
  {
    $files = [];
    $relations = $this->generateRelations();
    $db = $this->getDbConnection();
    foreach ($this->getTableNames() as $tableName) {
      // model :
      $modelClassName = $this->generateClassName($tableName);
      $queryClassName = ($this->generateQuery) ? $this->generateQueryClassName($modelClassName) : false;
      $tableRelations = isset($relations[$tableName]) ? $relations[$tableName] : [];
      $tableSchema = $db->getTableSchema($tableName);
      $params = [
        'tableName' => $tableName,
        'className' => $modelClassName,
        'queryClassName' => $queryClassName,
        'tableSchema' => $tableSchema,
        'properties' => $this->generateProperties($tableSchema),
        'labels' => $this->generateLabels($tableSchema),
        'rules' => $this->generateRules($tableSchema),
        'relations' => $tableRelations,
        'relationsClassHints' => $this->generateRelationsClassHints($tableRelations, $this->generateQuery),
      ];

      if ($this->splitModelIntoBaseClass) {
        // New code:
        $params['className'] = $this->baseModelClass;

        $origNs = $this->ns; // $this->ns is hardcoded in model.php and I didnt want to modify it, but special value in $params would be nice.
        $this->ns = $this->baseModelNamespace; // temporary change of $this->ns

        $files[] = new CodeFile(
          Yii::getAlias('@' . str_replace('\\', '/', $this->baseModelNamespace)) . '/' . $this->baseModelClass . '.php',
          $this->render('model.php', $params)
        );

        $this->ns = $origNs; // $this->ns is hardcoded in baseModel.php and I didnt want to modify it

        $files[] = new CodeFile(
          Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $modelClassName . '.php',
          $this->render('modelEmpty.php', [
            'namespace' => $this->ns,
            'className' => $modelClassName,
            'baseClass' => $this->baseModelClass,
            'baseClassNamespace' => $this->baseModelNamespace,
          ])
        );
/* modelEmpty.php contains only this empty class definition:
<?php echo '<?php'; ?>
namespace <?= $namespace ?>;
use <?= $baseClassNamespace ?>\<?= $baseClass ?>;
class <?= $className ?> extends <?= $baseClass ?> {
}
*/
      } else {
        // Original code by Yii:
        $files[] = new CodeFile(
          Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $modelClassName . '.php',
          $this->render('model.php', $params)
        );
      }

      // query :
      if ($queryClassName) {
        $params['className'] = $queryClassName;
        $params['modelClassName'] = $modelClassName;
        $files[] = new CodeFile(
          Yii::getAlias('@' . str_replace('\\', '/', $this->queryNs)) . '/' . $queryClassName . '.php',
          $this->render('query.php', $params)
        );
      }
    }

    return $files;
  }

}
@samdark samdark transferred this issue from yiisoft/yii2 Dec 25, 2021
@samdark
Copy link
Member

samdark commented Dec 25, 2021

@yiisoft/reviewers need your opinion.

@schmunk42
Copy link
Contributor

Was one reason for me to create giiant, back then :)
https://github.com/schmunk42/yii2-giiant

@bizley
Copy link
Member

bizley commented Jan 10, 2022

Yii 2 is quite oldschool regarding the inheritance vs composition debate so adding additional layer of inheritance would not be a terrible thing but we all know that it's not a good thing, right? ;)

@schmunk42
Copy link
Contributor

Another option would be to move the generated code into Traits and just have a more or less empty class using them.

@machour
Copy link
Member

machour commented Jan 10, 2022

I remember @cebe talking about a new version that would patch the file while keeping your changes, right Carsten?

@uldisn
Copy link
Contributor

uldisn commented Jan 10, 2022

Thanks @schmunk42 for the base model idea in giiant. Base model regeneration after modifying a database tables is something I do daily.
In addition saving gii form data for reuse would be fantastic. In Giiant it is realized and useful.

@cebe
Copy link
Member

cebe commented Apr 21, 2022

I remember @cebe talking about a new version that would patch the file while keeping your changes, right Carsten?

yeah, that's an idea which would be really cool to implement, but I had no time to play with that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants