Safely Change Database Columns in Yii Framework

Matt McCormick

2012/09/19

Note: This post refers to Yii Framework version 1.1.11

Let’s say you have an existing web application that uses Yii framework. For some reason, you would like to go about changing some column names in your database tables. Recently I went through this process because some tables in our application were using title and others were using name. I wanted to standardize them all to name.

Changing column names can be a pain because, if you are making use of CActiveRecord, you probably have many in places in the code that refer to the attribute directly such as:

$model->title;

Finding all the references and updating them could be quite time consuming depending on the size of your application.

Luckily, because of the way Yii accesses attributes, it is quite painless to change column names in a way that ensures any existing references do not break.

Let’s take a look at how you could go about changing the column title to name by first taking a look at what is happening when you call $model->title.

Yii makes use of the PHP magic methods to access attributes directly.

In the CActiveRecord class we have the __get() method:

public function __get($name)
{
	if(isset($this->_attributes[$name]))
		return $this->_attributes[$name];
	else if(isset($this->getMetaData()->columns[$name]))
		return null;
	else if(isset($this->_related[$name]))
		return $this->_related[$name];
	else if(isset($this->getMetaData()->relations[$name]))
		return $this->getRelated($name);
	else
		return parent::__get($name);
}

When you try to access a property of the object, Yii will first check to see if it is an attribute. If is not an attribute, and not a relation, the call will be passed up the chain to CComponent.

public function __get($name)
{
	$getter='get'.$name;
	if(method_exists($this,$getter))
		return $this->$getter();
	else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
	{
		// duplicating getEventHandlers() here for performance
		$name=strtolower($name);
		if(!isset($this->_e[$name]))
			$this->_e[$name]=new CList;
		return $this->_e[$name];
	}
	else if(isset($this->_m[$name]))
		return $this->_m[$name];
	else if(is_array($this->_m))
	{
		foreach($this->_m as $object)
		{
			if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
				return $object->$name;
		}
	}
	throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
		array('{class}'=>get_class($this), '{property}'=>$name)));
}

We only need to concern ourselves with the first few lines of this method. It says if a method exists that starts with ‘get’, in our case this would be getTitle() then it will return the return value from that method. Set works similarly as Yii implements __set() as well. I won’t post the code for it but you can have a look if you are interested.

Knowing this, it is easy to rename our columns without having to touch any existing code.

In your model class we just need to add a couple of very simple methods:

public function getTitle()
{
	return $this->name;
}

public function setTitle($name)
{
	$this->name = $name;
}

That’s it! Our existing code will continue to function as normal. Any call to $model->title will be handled by the getTitle() method which now will return the name attribute.

It would be good idea to write a few unit tests to make sure it is working properly in your application.

Alternatively, if you have columns in many tables that are being renamed in the same manner, you can refactor the above code into a Behaviour to avoid code duplication.

I should point out that this only works for code where you are directly referencing the attribute as a property. If you refer to the column name elsewhere, such as in the rules() or relations() methods or directly in SQL you will need to change those manually. So it is still a good idea to do a search through your code but implementing the above methods should help substantially to decrease the time it takes to rename columns.