Yii Relational Active Record Tutorial

Yii website has an excellent written document on relational active record. I was confused at first with the example and some of the terms Yii used for their relational active record that really cause me to waste some time on this section. Therefore i decided to write a quick and dirty tutorial on Yii relational active record hoping it will post some benefits for those who are still learning Yii framework.

Yii Relational Active Record Approaches

In Yii there are two approaches mainly the lazy loading approach and the eager loading approach. Both approaches have been documented on Yii relational active record tutorial. But if you are lazy to read, this is how each approach is being use. Lazy loading approach is use when you are dealing with 1 record and eager loading approach comes in handy when there are more than 1 records you wish to access. This is made in this way to reduce the number of join which lead to inefficiency according to Yii document page

Setup Relational Active Record

In order to get relational active record to work, we have to setup the relationship between each model. Assuming we have two table, Invoice and InvoiceItem tables as shown below,

CREATE TABLE IF NOT EXISTS `invoice` (
	`invoiceId` MEDIUMINT UNSIGNED NOT NULL,
	`invoiceTotalAmount` DOUBLE NOT NULL,
	PRIMARY KEY (`invoiceId`)
) 

CREATE TABLE IF NOT EXISTS `invoice_item` (
	`invoiceItemId` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
	`invoiceItemLineId` MEDIUMINT UNSIGNED NOT NULL,
	`invoiceId` MEDIUMINT UNSIGNED NOT NULL COMMENT "CONSTRAINT FOREIGN KEY (invoiceId) REFERENCES be_invoice(invoiceId)",
	PRIMARY KEY (`invoiceItemId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Now, if we use Gii to create our models, we will get 2 models, InvoiceItem and Invoice class model. Now we will need to overwrite the method 'relations' in Invoice class model in order to retrieve Invoice and its items into the same page. In this case, using relational active record is the best option we have. In order to use relational active record, we have to modified the Invoice class model method, 'relations'. Hence, in this method we will declare the following relationship with InvoiceItem.

	/**
	 * @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(
			'invoiceItems' => array(self::HAS_MANY, 'InvoiceItem', 'invoiceId', 'together'=>true ),
		);
	}

The above means that i have a 1 to many relationship with InvoiceItem class that share the same key called 'invoiceid' and i will name my relationship 'invoiceitems'. Usually here is the most confusing part for everyone to pick up (to setup a relationship). The format for this relationship can be seen on Yii documentation too but i will show it here just for your conveniences.

'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...additional options)

In layman term, my relationship means "My invoice class model has a one to many relationship with InvoiceItem class model and we are tie together with our primary and foreign key which in this case foreign key is InvoiceItems". Hope it helps anyone stucked here to understand how a relationship can be initialized. Let's continue.

Getting The Relational Active Record

Now, we can get our counterpart data after we have setup our relational active record. In my case, I'm confused and stucked here since i am not very familiar with the example given in the document. Anyway, we can now utilized the two approaches mentioned previously to retrieve our relational active records. I will first introduce the lazy loading approach.

Lazy Loading Approach

Please bear in mind that the lazy loading approach will not automatically populate the wanted data to you. You will have to initialize it. And this is the mindset i had and got stuck with because i always though the "lazy" means i will not have to initialize it. Anyhow, assuming you have your crud setup for your invoice table. In this case, there should be a controller called 'InvoiceController.php'. In my example, i want my data for both table to appear on my create invoice page. In order to do that, i will have to go to the view folder and open up '_form.php' where the structure is being made as shown below.

<div class="form">

<?php $form=$this->beginWidget('CActiveForm', array(
	'id'=>'invoice-form',
	'enableAjaxValidation'=>false,
)); 

?>

	<p class="note">Fields with <span class="required">*</span> are required.</p>

	<?php echo $form->errorSummary($model); ?>
	
	<div class="row">
		<?php echo $form->labelEx($model,'invoiceId'); ?>
		<?php echo $form->textField($model,'invoiceId'); ?>
		<?php echo $form->error($model,'invoiceId'); ?>
	</div>
	
	<div class="row">
		<?php echo $form->labelEx($model,'invoiceTotalAmount'); ?>
		<?php echo $form->textField($model,'invoiceTotalAmount'); ?>
		<?php echo $form->error($model,'invoiceTotalAmount'); ?>
	</div>
</div>

Now, we will need to add in the lazy loading approach method.

$items=$model->invoiceItems;
foreach($items as $item){
	echo $item->invoiceItemLineId . "<br/>";
}

Take note that the invoiceItems is my relationship name that i have declared in my relations method on the model. You can also do it this way which is shown on Yii website.

// retrieve a record object
$invoice=Invoice::model()->findByPk(1);
// invoiceItems is the relationship name i have declared
$author=$invoice->invoiceItems;

Once we put this into the _forms structure file, we will have this.

<div class="form">

<?php $form=$this->beginWidget('CActiveForm', array(
	'id'=>'invoice-form',
	'enableAjaxValidation'=>false,
)); 
$items=$model->invoiceItems;
foreach($items as $item){
	echo $item->invoiceItemLineId . "<br/>";
}
?>

	<p class="note">Fields with <span class="required">*</span> are required.</p>

	<?php echo $form->errorSummary($model); ?>
	
	<div class="row">
		<?php echo $form->labelEx($model,'invoiceId'); ?>
		<?php echo $form->textField($model,'invoiceId'); ?>
		<?php echo $form->error($model,'invoiceId'); ?>
	</div>
	
	<div class="row">
		<?php echo $form->labelEx($model,'invoiceTotalAmount'); ?>
		<?php echo $form->textField($model,'invoiceTotalAmount'); ?>
		<?php echo $form->error($model,'invoiceTotalAmount'); ?>
	</div>
</div>

the above should just print out the line number of the items to show you how many items are available in this invoice.

Eager Loading Approach

Eager loading approach will be much MUCH easier. This can be easily figured just by reading what is written on Yii website. The eager loading approach required the word 'with' to join the 2 table together. A simple example which you can is shown below,

$invoices = Invoice::model()->with('invoiceItems')->findAll();

By the way, you can use this sentence anywhere as long as you need the data. What the above is saying in layman term is that "Using the Invoice class model, we fire up model to initial the db call and use the relationship called invoiceItems and show all results to me".

Summary

Hopefully this tutorial can help further explain what has already existed in the Yii documentation for anyone to get the hang of Yii relational active record. The tutorial here is pretty simple and high level. Hopefully to solve starters on Yii on issues on Yii relational active record.

WordPress 3.0 Plugin Activation Error – “Headers already sent”

Well, i have been customizing my WordPress a lot to produce something like the food directory or blogshopping tool which required a lot of hack on to WordPress to make everything work perfectly. Recently i have upgraded my WordPress to the latest version 3.0.1. Everything seems fine until one day i decided to enhance my site. Upon activting my WordPress plugin, an error message occurs stating "The plugin generated 3 characters of unexpected output during activation. If you notice “headers already sent” messages, problems with syndication feeds or other issues, try deactivating or removing this plugin.". Although the plugin successfully activated, it seems like there are some problem with the plugin that is causing this. I search high and low for it but couldn't seems to detect any header being sent explicitly without my knowledge.  To make matter worst, this caused all my timthumb (image of the fly) script to malfunction which caused ALL my images to break. Hence, none of the images generated by timthumb were generated on the website. This is disaster!!!  Why is my header being sent when there is NOTHING in my code that is sending it?!!! (panic)

I went to alert my hosting company (hostgator) about this and tried to resolved this on the server level as my test environment which is another host were functioning perfectly without causing me a single problem! However, they couldn't find any cause on their server side that may caused this problem and direct me back to the application problem and asked me to check my code. Puzzled by all the mystery that is happening on my test and live environment. I decided to look further into what could have happened. And here are some of the things that i found but doesn't happen to me.

Extra whitespace / Character

Extra whitespace or character before the tag will caused this to happen. This is a comment mistakes made by many new php developers. But in my case, this wasn't the problem.

My Situation

Soon, i found out my mistake. Apparently, my test environment server setting allows Unicode encoded file type to be read normally. However, the one on Hostgator only allows ANSCII to be read. Hence, all the Unicode encoded files were the culprits that is causing all this problems. It seems like the php setting made on the server can caused this to happen as the file type unknown to the php parser seems to bypassed the php output buffer and sent out the plaintext mime type before everything else which caused my timthumb to not work properly (since image sent in jpg mime instead of plaintext). This might be the reason why WordPress is giving you a message of "The plugin generated 3 characters of unexpected output during activation. If you notice “headers already sent” messages, problems with syndication feeds or other issues, try deactivating or removing this plugin." when you try to activate the plugin. It can also be caused by other plugins builder who are unaware that this might happen as their environment works perfectly and yours doesn't. Oh, the reason why anyone would want to change the encoding from ASCII to other form of encoding can be due to special character or other languages writing that ASCII doesn't support. Hence, changing the file encoding types allows php to display out the correct message. (WordPress is multilingual, this should happen more often than you think :)). Hope it helps 🙂

IE6 solution for position absolute with height 100% dynamically

I was working on a rounded corner solution that required to supports IE 6 and other major browsers with the number of sprites images given to me. Everything works perfectly until i test the css rule with IE 6. The look very nice layout was literally destroy by IE6. So i have to redesign it slightly so that it works nicely across all browser. Well, supporting IE6 is definitely not one of my favorite things to do for css design as there are many problems one will encounter due to the inconsistency ways of how each browser is being implemented. One of the problem that i encountered doing this was to get my left and right shadow border image to repeat itself on the y axis. The problem was it doesn't even repeat itself! 🙁

Problem with IE 6 Absolute Position with Height 100%

Soon, i found out that it was due to how IE 6 sees the current div block height. I was trying to set the div block of the left and right side shadow to fix into the content of my rounder corner solution as shown below,

This is something that i took with Firefox, let's look at IE 6 display!

Well.. this is not the actual messed up code that i initially saw but the one that i have completed with a bit of code taken off.  Notice that the side sprite images did not repeat itself. This is not due to IE 6 incompatible with css, background-repeat rule. This is purely how IE 6 behave when your div block has absolute position rule intact with it. Let's look at the code of both my CSS and HTML code.

<div id="box-container">
	<div id="box">
		<div class="tlc"></div>
		<div class="trc"></div>
		<div class="blc"></div>
		<div class="brc"></div>
		<div class="t"></div>
		<div class="b"></div>
		<div class="l"></div> <!-- Left shadow -->
		<div class="r"></div> <!-- Right shadow -->

		<div id="content">
			<h1>Viola~</h1>

			<p>
				ROUND ROUND ROUND ROUNDERRRRRRRRRR CORNERS~
			</p>
		</div>
	</div>
</div>

The above are the structure i used to construct my rounder corner solution where the tag for left and right shadow is displayed above. Now, let's look at the two shadow CSS declaration.

#box
{
	margin: 15px auto;
	text-align: left;
	width: 55em;
	word-wrap:break-word;
	overflow: hidden;
	position: relative;
}
.l, .r
{
	height: 100%;
	position: absolute;
	width: 10px;
	z-index: -1;
}

.r
{
	background: #FFF url(images/pnl-sides.png) repeat-y 9% 0%; /*set the right border image*/
	right: 0;
}

.l
{
	background: #FFF url(images/pnl-sides.png) repeat-y 90% 0%; /*set the right border image*/
	left: 0;
}

The above left and right shadow works perfectly as shown on Firefox/Chrome/Safari but fail on IE 6. This is where the problem comes, Height:100% on a position absolute div do not work on IE 6? Well, it does work since i solved this. The trick is to provide its parent a height value as the child does not know how long its height was so it will keep looking for its parent to determine the div block height. In this case, my left and right shadow div block parent was "box". Here, i set my box div block css as follow,

#box
{
	margin: 15px auto;
	text-align: left;
	width: 55em;
	word-wrap:break-word;
	overflow: hidden;
	position: relative;
	height: 100%; /* this is added for ie6 */
}

Notice the differences between the first box declaration and this. We have just added height 100%. Below shows the result of our modification.

There! I fixed it in IE 6. But wait! We have another problem. Now, all browser are rendering similar view with height 100% but i want it to fixed dynamically according to the content not the whole height of the browser. We can fix other browsers by adding the following sentence as shown below,

#box
{
	margin: 15px auto;
	text-align: left;
	width: 55em;
	word-wrap:break-word;
	overflow: hidden;
	position: relative;
	height: auto !important; /* this is added to fixed all other browser dynamic height problem */
	height: 100%; /* this is added for ie6 */
}

After we added this css rule, all other browser should work correctly. It seems like IE 6 doesn't recognized !important for height attribute and only takes the latest declared height for its css height in this case, height: 100%. Other browser which worked correct will utilized the !important rule instead. This way we can assure that all other browser render our height dynamically according to the content of our text! Wait! How about IE 6? Oh yes, we still have this trouble maker that we need to take care of. The trick to solve the problem of IE 6 being dumb by taking height 100% as the whole document height is to create another parent above our "box" container which i have already created which is called "box-container". Now, for our "box-container" css, we will place the following rules:

#box-container{
	height: 100% !important;
	height: 1px;
	min-height: 1px ;
}

This will give "box" a given height to follow and cause IE 6 to resize dynamically according to the amount of text you have given in your content block. Now, i finally have a cross browser working solution for my work 🙂

Customize Authentication On Yii Framework Using MySQL Database

Just managed to find some time to play around with Yii. Yii is powerful but there is still a long way to go if we are talking about documentation for Yii framework. In Yii framework, we can see that it is very different from CodeIgniter where documentation is really structured and well understood. Nonetheless, i still feel that Yii framework is worth to explore. I managed to get my own customized authentication on Yii by adding some secure feature such as hashing, salt, key and etc. So here i am writing this tutorial to share with you more about Yii framework using MySQL database.

Requirement

Since this is more like a follow up tutorial, there are a few requirements before you start reading this tutorial.

  1. Installed Yii with a MySQL database.
  2. Setup Gii and get the user CRUD completed.

Customize Authentication - Database

Now here comes the tricky part. We need a database that stored our hashed password which is 128 bits since i am using sha512. Our data schema should looks like this,

CREATE TABLE tbl_user (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    username VARCHAR(128) NOT NULL,
    password VARCHAR(128) NOT NULL,
    email VARCHAR(128) NOT NULL
);

Well, it looks the same as the demo one so just ignore me lol. Create this table and we are ready to do some MVC. If you are following the tutorial you would most likely get a CRUD user setup. But we only have 'admin' and 'demo' login account. We would definitely want something better.

Customize Authentication - Model

In order to validate a user, we need to create a few methods in the model folder in order to authenticate and store user password. We will need to create these functions on our User.php file on our model folder.

	/**
	 * @return boolean validate user
	 */
	public function validatePassword($password, $username){
		return $this->hashPassword($password, $username) === $this->password;
	}
	/**
	 * @return hashed value
	 */
	DEFINE('SALT_LENGTH', 10);
	public function hashPassword($phrase, $salt = null){
		$key = 'Gf;B&yXL|beJUf-K*PPiU{wf|@9K9j5?d+YW}?VAZOS%e2c -:11ii<}ZM?PO!96';
		if($salt == '')
			$salt = substr(hash('sha512', $key), 0, SALT_LENGTH);
		else
			$salt = substr($salt, 0, SALT_LENGTH);
		return hash('sha512', $salt . $key . $phrase);
	}

the two methods above is used to validate the user password during login and the other method returns a hashed password given the original plain password value. Once these two methods are pasted into the user.php file. We are done with our modal!

Customize Authentication - Controller

In controller, we need to modify the create and update handler but i will just demonstrate the create user handler. Go to your controller folder and look for UserController.php. Change the method actionCreate to the following

	/**
	 * Creates a new model.
	 * If creation is successful, the browser will be redirected to the 'view' page.
	 */
	public function actionCreate()
	{
		$model=new User;

		// Uncomment the following line if AJAX validation is needed
		// $this->performAjaxValidation($model);

		if(isset($_POST['User']))
		{
			$model->attributes=$_POST['User'];
			$model->password = $model->hashPassword($_POST['User']['password'], $_POST['User']['email']);
			if($model->save())
				$this->redirect(array('view','id'=>$model->ID));
			else
				$model->password = $_POST['User']['password'];
		}

		$this->render('create',array(
			'model'=>$model,
		));
	}

This way, we can create user with hashed password instead of plain password stored in our database.

Customize Authentication - Component

Next we need to authenticate our users. The original one just defined 'demo' and 'admin' as the only users that we are able to login. But now we have a database and a list of user and password. We should really secure our login. Here we modify our original authentication method to the following one.

	public function authenticate()
	{
		$username = $this->username;
		$user = User::model()->find('username=?', array($username));
		if($user === NULL)
			$this->errorCode=self::ERROR_USERNAME_INVALID;
		else if(!$user->validatePassword($this->password, $this->username))
			$this->errorCode=self::ERROR_PASSWORD_INVALID;
		else{
			$this->username = $user->username;
			$this->errorCode=self::ERROR_NONE;

		}
		return !$this->errorCode;
	}

this allowed us to go through the users in our database table instead of the hardcoded one by using the method we wrote previously on the modal folder.

Now, our user will be authenticate using our customized authentication process rather than using the default one!

Image Tutorial: How to setup Yii Framework on WAMP using MySQL database

I decides to move to Yii finally after comparing between different framework. At this point, although Yii documentation wasn't as good as CI, it is not something that will restrict me from entering Yii framework. Like most people i started with the tutorial given on Yii website. Here is something i tried out today through the instruction given on the cookbook section. In this article, i will extend what is present in the article in a more visual form.

Requirement

Here are some of the basic requirement for this tutorial.

  1. Window XP (Vista and Win 7 will also work)
  2. Yii Framework 1.1.3
  3. WAMP 2.0i (Apache 2.2.11, PHP 5.3.0, MySQL 5.1.36, Phpmyadmin)
  4. Basic installation of WAMP (C:\\wamp\...)

Setting up yiic on WAMP

Firstly install your WAMP with the default installation.

Once you have installed this, you should be ready to setup your computer local environment to use Yiic from Yii framework. Firstly, go to your environment variables located at Control Panel->System->Advance->Environment Variables->Path as shown below,

Once you reached the path textbox, entered the following location to tell your windows that they are the environment variables.

  1. C:\wamp\www\yii\framework
  2. C:\wamp\bin\php\php5.3.0

In this case, we are telling our window where is our yii framework yiic.bat and where is our php.exe as shown below,

since my WAMP is using php5.3.0, the folder shows the current version my WAMP is using. This might differ. Hence, change the directory path according to the php version you use. On the other hand, my yiic.bat is located at C:\wamp\www\yii\framework as shown below, hence, i pass this to the environment variable instead.

Click 'OK' for all the settings you have made and restart you PC. Once your windows has rebooted, click start->run.. and type 'cmd'. On the screen type "yiic webapp C:\\wamp\www\mywebsite" andtype 'yes". The folder should show up on your localhost.

Now you need to change your setting from sqllite3 to MySQL. This is located at C:\wamp\www\mywebsite\protected\config\main.php, open this file and replace the codes with the following.

<?php

// uncomment the following to define a path alias
// Yii::setPathOfAlias('local','path/to/local-folder');

// This is the main Web application configuration. Any writable
// CWebApplication properties can be configured here.
return array(
	'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
	'name'=>'My Web Application',

	// preloading 'log' component
	'preload'=>array('log'),

	// autoloading model and component classes
	'import'=>array(
		'application.models.*',
		'application.components.*',
	),

	// application components
	'components'=>array(
		'user'=>array(
			// enable cookie-based authentication
			'allowAutoLogin'=>true,
		),
		// uncomment the following to enable URLs in path-format
		/*
		'urlManager'=>array(
			'urlFormat'=>'path',
			'rules'=>array(
				'<controller:\w+>/<id:\d+>'=>'<controller>/view',
				'<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
				'<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
			),
		),
		*/
		'db'=>array(
			'connectionString' => 'mysql',
		),
		// uncomment the following to use a MySQL database

		'db'=>array(
			'connectionString' => 'mysql:host=localhost;dbname=yourdatabasename',
			'emulatePrepare' => true,
			'username' => 'root',
			'password' => '',
			'charset' => 'utf8',
		),

		'errorHandler'=>array(
			// use 'site/error' action to display errors
            'errorAction'=>'site/error',
        ),
		'log'=>array(
			'class'=>'CLogRouter',
			'routes'=>array(
				array(
					'class'=>'CFileLogRoute',
					'levels'=>'error, warning',
				),
				// uncomment the following to show log messages on web pages
				/*
				array(
					'class'=>'CWebLogRoute',
				),
				*/
			),
		),
	),

	// application-level parameters that can be accessed
	// using Yii::app()->params['paramName']
	'params'=>array(
		// this is used in contact page
		'adminEmail'=>'[email protected]',
	),
);

Change 'yourdatabasename' in the text above to your database name and you'r done!