Yii2 Gii CRUD generation with fields spaces in names

313 views Asked by At

I have a PostgreSQL database with spaces in both table names and field names. I have no control over this database structure. The problem isn't the table names, but the field names with the scaffold CRUD code. Here's an example:

CREATE TABLE "public"."MILESTONES 4 FORM TABLE" (
"Estimate ID" varchar(250) NOT NULL DEFAULT 'None'::character varying COLLATE "default",
"MS 4 Name" varchar(100) NOT NULL COLLATE "default",
"MS 4 Begin" varchar(50) COLLATE "default",
"MS 4 End" varchar(50) COLLATE "default",
"MS 4 Weight" float4 DEFAULT 0.0,
"MS 4 % Oct" float4 DEFAULT 0.0,
"MS 4 % Nov" float4 DEFAULT 0.0,
....the other months here... deleted for brevity
"MS 4 % Sep" float4 DEFAULT 0.0,
"Change Control Choice" varchar(50) COLLATE "default",
"CV% MS Oct Cum" varchar(50) COLLATE "default",
"SV% MS Oct Cum" varchar(50) COLLATE "default",
"CV% MS NOV Cum" varchar(50) COLLATE "default",
"SV% MS NOV Cum" varchar(50) COLLATE "default",
....the other months here... deleted for brevity
"CV% MS Sep Cum" varchar(50) COLLATE "default",
"SV% MS Sep Cum" varchar(50) COLLATE "default"
);

Gii generated the following for the model (I had to comment out the @property declarations because PHPStorm marked them as an error):

<?php

namespace app\models;

use Yii;

/**
 * This is the model class for table "MILESTONES 4 FORM TABLE".
 *
//* @property string $Estimate ID
//* @property string $MS 4 Name
//* @property string $MS 4 Begin
//* @property string $MS 4 End
//* @property double $MS 4 Weight
//* @property double $MS 4 % Oct
//* @property double $MS 4 % Nov
...deleted for brevity
//* @property double $MS 4 % Sep
//* @property string $Change Control Choice
//* @property string $CV% MS Oct Cum
//* @property string $SV% MS Oct Cum
...deleted for brevity...
//* @property string $CV% MS Sep Cum
//* @property string $SV% MS Sep Cum
 */
class Milestone4AR extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'MILESTONES 4 FORM TABLE';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['Estimate ID', 'MS 4 Name'], 'required'],
            [['MS 4 Weight', 'MS 4 % Oct', 'MS 4 % Nov', 'MS 4 % Dec', 'MS 4 % Jan', 'MS 4 % Feb', 'MS 4 % Mar', 'MS 4 % Apr', 'MS 4 % May', 'MS 4 % Jun', 'MS 4 % Jul', 'MS 4 % Aug', 'MS 4 % Sep'], 'number'],
            [['Estimate ID'], 'string', 'max' => 250],
            [['MS 4 Name'], 'string', 'max' => 100],
            [['MS 4 Begin', 'MS 4 End', 'Change Control Choice', 'CV% MS Oct Cum', 'SV% MS Oct Cum', 'CV% MS NOV Cum', 'SV% MS NOV Cum', 'CV% MS Dec Cum', 'SV% MS Dec Cum', 'CV% MS Jan Cum', 'SV% MS Jan Cum', 'CV% MS Feb Cum', 'SV% MS Feb Cum', 'CV% MS Mar Cum', 'SV% MS Mar Cum', 'CV% MS Apr Cum', 'SV% MS Apr Cum', 'CV% MS May Cum', 'SV% MS May Cum', 'CV% MS Jun Cum', 'SV% MS Jun Cum', 'CV% MS Jul Cum', 'SV% MS Jul Cum', 'CV% MS Aug Cum', 'SV% MS Aug Cum', 'CV% MS Sep Cum', 'SV% MS Sep Cum'], 'string', 'max' => 50],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'Estimate ID' => 'Estimate  ID',
            'MS 4 Name' => 'Ms 4  Name',
            'MS 4 Begin' => 'Ms 4  Begin',
            'MS 4 End' => 'Ms 4  End',
            'MS 4 Weight' => 'Ms 4  Weight',
            'MS 4 % Oct' => 'Ms 4 %  Oct',
            'MS 4 % Nov' => 'Ms 4 %  Nov',
...deleted for brevity...
            'MS 4 % Aug' => 'Ms 4 %  Aug',
            'MS 4 % Sep' => 'Ms 4 %  Sep',
            'Change Control Choice' => 'Change  Control  Choice',
            'CV% MS Oct Cum' => 'Cv%  Ms  Oct  Cum',
            'SV% MS Oct Cum' => 'Sv%  Ms  Oct  Cum',
...deleted for brevity...
            'CV% MS Sep Cum' => 'Cv%  Ms  Sep  Cum',
            'SV% MS Sep Cum' => 'Sv%  Ms  Sep  Cum',
        ];
    }
}

I then used Gii to generate the CRUD code from the model. You can see an example of the problem with the $query->andFilterWhere section. For example, ... andFilterWhere(['like', 'MS 4 Name', $this->MS 4 Name]) will not parse.

What have I done wrong, and how can I fix it?

This isn't production code (yet) so I can easily blow away what I've already done, but I can't alter the original database table structure (boy, do I wish I could! I really dislike working on legacy code). I have over 100 tables using this same naming convention, so need a fix that can be applied over all of them.

<?php

namespace app\models;

use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\Milestone4AR;

/**
 * Milestone4Search represents the model behind the search form about `app\models\Milestone4AR`.
 */
class Milestone4Search extends Milestone4AR
{
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['Estimate ID', 'MS 4 Name', 'MS 4 Begin', 'MS 4 End', 'Change Control Choice', 'CV% MS Oct Cum', 'SV% MS Oct Cum', 'CV% MS NOV Cum', 'SV% MS NOV Cum', 'CV% MS Dec Cum', 'SV% MS Dec Cum', 'CV% MS Jan Cum', 'SV% MS Jan Cum', 'CV% MS Feb Cum', 'SV% MS Feb Cum', 'CV% MS Mar Cum', 'SV% MS Mar Cum', 'CV% MS Apr Cum', 'SV% MS Apr Cum', 'CV% MS May Cum', 'SV% MS May Cum', 'CV% MS Jun Cum', 'SV% MS Jun Cum', 'CV% MS Jul Cum', 'SV% MS Jul Cum', 'CV% MS Aug Cum', 'SV% MS Aug Cum', 'CV% MS Sep Cum', 'SV% MS Sep Cum'], 'safe'],
            [['MS 4 Weight', 'MS 4 % Oct', 'MS 4 % Nov', 'MS 4 % Dec', 'MS 4 % Jan', 'MS 4 % Feb', 'MS 4 % Mar', 'MS 4 % Apr', 'MS 4 % May', 'MS 4 % Jun', 'MS 4 % Jul', 'MS 4 % Aug', 'MS 4 % Sep'], 'number'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = Milestone4AR::find();

        // add conditions that should always apply here

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $this->load($params);

        if (!$this->validate()) {
            // uncomment the following line if you do not want to return any records when validation fails
            // $query->where('0=1');
            return $dataProvider;
        }

        // grid filtering conditions
        $query->andFilterWhere([
            'MS 4 Weight' => $this->'MS 4 Weight',
            'MS 4 % Oct' => $this->'MS 4 % Oct',
            'MS 4 % Nov' => $this->'MS 4 % Nov',
 ...deleted for brevity...
          'MS 4 % Jul' => $this->'MS 4 % Jul',
            'MS 4 % Aug' => $this->'MS 4 % Aug',
            'MS 4 % Sep' => $this->'MS 4 % Sep',
        ]);

        $query->andFilterWhere(['like', 'Estimate ID', $this->Estimate ID])
            ->andFilterWhere(['like', 'MS 4 Name', $this->MS 4 Name])
            ->andFilterWhere(['like', 'MS 4 Begin', $this->MS 4 Begin])
            ->andFilterWhere(['like', 'MS 4 End', $this->MS 4 End])
            ->andFilterWhere(['like', 'Change Control Choice', $this->Change Control Choice])
            ->andFilterWhere(['like', 'CV% MS Oct Cum', $this->CV% MS Oct Cum])
            ->andFilterWhere(['like', 'SV% MS Oct Cum', $this->SV% MS Oct Cum])
            ->andFilterWhere(['like', 'CV% MS NOV Cum', $this->CV% MS NOV Cum])
            ->andFilterWhere(['like', 'SV% MS NOV Cum', $this->SV% MS NOV Cum])
            ->andFilterWhere(['like', 'CV% MS Dec Cum', $this->CV% MS Dec Cum])
 ...deleted for brevity...
            ->andFilterWhere(['like', 'CV% MS Aug Cum', $this->CV% MS Aug Cum])
            ->andFilterWhere(['like', 'SV% MS Aug Cum', $this->SV% MS Aug Cum])
            ->andFilterWhere(['like', 'CV% MS Sep Cum', $this->CV% MS Sep Cum])
            ->andFilterWhere(['like', 'SV% MS Sep Cum', $this->SV% MS Sep Cum]);

        return $dataProvider;
    }
}
1

There are 1 answers

1
Bizley On

In your case you can not use shorthand properties because it's conflicting PHP attribute naming rules.

There are solutions you can use though.

You can call attribute like this:

$model->getAttribute('MS 4 Name')

or you can prepare aliased attributes in model like:

public $ms4name;

And then fill them on afterFind() like:

public function afterFind()
{
    $this->ms4name = $this->getAttribute('MS 4 Name');
    parent::afterFind();
}

So from this point you can use

$model->ms4name