Overriding primaryKey()

I have a MySQL column ‘id’ that is the auto increment PK. However, I’d like Yii to use the username column as the id for actions. The AR guide suggested overriding primaryKey within my model, which I did like so:




public function primaryKey()

{

	return 'username';

}



However, Yii still seems to be using the original auto increment ‘id’ column.




echo $model->primaryKey(); // correctly outputs 'username'

echo $model->getPrimaryKey(); // incorrectly outputs '27'



Surely I’m missing something stupid :)

Any ideas?

The first statement outputs the primary key attribute.

The second statement outputs the primary key attribute’s value (of the AR instance).

Edit: but I now notice the value is wrong.

Can it possibly happen to be this method was omitted?




* EVERY derived AR class must override this method as follows,

 public static function model($className=__CLASS__)

 {

     return parent::model($className);

 }



/Tommy

Yes, the getPrimaryKey() is outputting the incorrect value.

The static function model is also there, as generated by Gii.




<?php


/**

 * This is the model class for table "{{users}}".

 *

 * The followings are the available columns in table '{{users}}':

 * @property integer $id

 * @property string $type

 * @property string $status

 * @property string $username

 * @property string $password

 * @property string $email

 * @property string $first_name

 * @property string $last_name

 * @property string $phone

 * @property string $phone_extension

 * @property string $company_name

 * @property string $logo_file

 * @property integer $opt_in

 * @property string $create_time

 * @property string $update_time

 */

class User extends ModelCore

{

	private $salt = "******";

	public $newPassword;

	

	/**

	 * Returns the static model of the specified AR class.

	 * @return User the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return '{{users}}';

	}

	

	public function primaryKey()

	{

		return 'username';

	}


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('url, username, email, first_name, last_name, address, city, state, zipcode', 'required'),

			array('opt_in, phone_extension, zipcode', 'numerical', 'integerOnly'=>true),

			array('email', 'email'),

			array('username', 'unique', 'message'=>'This username is already being used.'),

			array('url', 'unique', 'message'=>'This URL is already being used.'),

			array('email', 'unique', 'message'=>'This email address is already being used.'),

			array('url, username, email, first_name, last_name, company_name, city', 'length', 'max'=>100),

            array('newPassword','length','min'=>8,'max'=>16,'allowEmpty'=>false,'on'=>'insert'),

            array('newPassword','length','min'=>8,'max'=>16,'allowEmpty'=>true,'on'=>'update'),

			array('phone', 'length', 'max'=>20),

			array('phone', 'match', 'pattern'=>'/^[0-9]{3}-[0-9]{3}-[0-9]{4}$/'),

			array('address', 'length', 'max'=>255),

			array('state', 'length', 'max'=>2),

			array('zipcode', 'length', 'max'=>5),

			array('zipcode', 'length', 'min'=>5),


			// The following rule is used by search().

			// Please remove those attributes that should not be searched.

			array('id, status, username, email, first_name, last_name, phone, company_name, address, city, state, zipcode', 'safe', 'on'=>'search'),

		);

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

		'listings' => array(self::HAS_MANY, 'Listing', 'userID')

		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'ID',

			'type' => 'Type',

			'status' => 'Status',

			'url' => 'Url',

			'username' => 'Username',

			'newPassword' => 'Password',

			'password' => 'Password',

			'email' => 'Email',

			'first_name' => 'First Name',

			'last_name' => 'Last Name',

			'phone' => 'Phone',

			'phone_extension' => 'Extension',

			'company_name' => 'Company Name',

			'opt_in' => 'Opt In',

			'create_time' => 'Create Time',

			'update_time' => 'Update Time',

		);

	}


	/**

	 * Retrieves a list of models based on the current search/filter conditions.

	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.

	 */

	public function search()

	{

		// Warning: Please modify the following code to remove attributes that

		// should not be searched.


		$criteria=new CDbCriteria;


		$criteria->compare('id',$this->id);

		$criteria->compare('type',$this->type,true);

		$criteria->compare('status',$this->status,true);

		$criteria->compare('username',$this->username,true);

		$criteria->compare('email',$this->email,true);

		$criteria->compare('first_name',$this->first_name,true);

		$criteria->compare('last_name',$this->last_name,true);

		$criteria->compare('phone',$this->phone,true);

		$criteria->compare('company_name',$this->company_name,true);

		$criteria->compare('address',$this->address,true);

		$criteria->compare('city',$this->city,true);

		$criteria->compare('state',$this->state,true);

		$criteria->compare('zipcode',$this->zipcode,true);


		return new CActiveDataProvider(get_class($this), array(

			'criteria'=>$criteria,

		));

	}

	

    public function validatePassword($password)

    {

        return $this->hashPassword($password)===$this->password;

    }

 

    public function hashPassword($password)

    {

        return md5($this->salt.$password);

    }

	

	public function beforeSave()

	{

		if(parent::beforeSave())

		{

			if (!empty($this->newPassword))

			{

				$this->password = $this->hashPassword($this->newPassword);

			}

			

			return true;

		}

		else

			return false;

	}

}



Note the model extends a “global” core model, here’s the code for that:




<?php


class ModelCore extends CActiveRecord

{

	/**

	 * Returns the static model of the specified AR class.

	 * @return User the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}

	

	public function primaryKey()

	{

		return 'id'; // default for most models

	}

	

	public function getDropdownOptions($a_column)

	{

		$schema = $this->getTableSchema()->getColumn($a_column)->dbType;

	    preg_match_all("/'([^']+)'/",$schema,$matches);

		$matches = array_combine($matches[1],$matches[1]);

		return $matches;

	}

	

	public function behaviors()

	{

		return array(

			'CTimestampBehavior' => array(

				'class' => 'zii.behaviors.CTimestampBehavior',

				'createAttribute' => 'create_time',

				'updateAttribute' => 'update_time',

			)

		);

	}

}



The primaryKey() method was added to support tables with no pk defined. [s]The (re)definition actually works if the table schema doesn’t have a primary key.

[/s] Not completely true, it doesn’t change in the inherited class.

Edit:

Add this to the inherited class. It seems to do what you want.




  public function init()

  {

    $this->getMetaData()->tableSchema->primaryKey = 'username';

  }

  



/Tommy

Thanks Tommy, it appears to be outputting correctly now.

One more question, I’m trying to go from /index.php/user/view/27 to /index.php/user/view/myusername. I thought the above change would fix that, but I guess not.

Or perhaps I’m confusing myself altogether. My end goal is /index.php/myusername. I’ve searched countless threads but none of the urlManager modifications work for me.

Thanks for your help!