Yii forward with params

Yii’s forward doesn’t work with passing params to actions. I.e. This doesn’t work: $this->forward(“/mycontroller/myaction/1/2”);

Because of the way urlmanager works I made the decision early on to have all action parameters take on the var names $id and $id2. So in the main config:

'components'=>array(
			// 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+>/<id:\d+>/<id2:\d+>'=>'<controller>/<action>',
					'<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
					'<controller:\w+>/<action:\w+>/<cact:\w+>'=>'<controller>/<cact>',
				),
			),

So I used Controller.php in the components directory to override the forward method like so:

	 /**
	 * Overloading CController::forward
	 * added the routeArr lines so the ids are passed
	 */
	public function forward($route,$exit=true){
		//error_log("Controller::forward");
		$routeArr = explode("/", $route);
		if(isset($routeArr[3]))
			$_GET['id'] = $routeArr[3];
		if(isset($routeArr[4]))
			$_GET['id2'] = $routeArr[4];

		parent::forward($route, $exit);

	}

And that’s it.

Posted in ATG, Quizmo, Yii. Tags: , . No Comments »

Yii Javascript redirect: jsredirect

One of the platforms I have to develop for is iSites. This is a Harvard grown LMS that is complicated and annoying, but functional.

Since I have to develop tools that fit within this framework I have to work around its limitations. One of the simpler to understand limitations is the idea of not having control of the headers sent. Since by the time it gets to my tool, the page has already started loading, output has already been sent, so doing a redirect with PHP’s header function is impossible.

Yii has 2 ways to forward things through the controller. redirect and forward. Redirect uses header and forward doesn’t change the URL. So the best way to forward within isites is to use a js forward. I.e. document.location = “www.google.com”

So in the controller.php in components which extends CController I added a method jsredirect:
 https://github.com/jazahn/Quizmo/blob/ma…

	protected function jsredirect($url){
		// set the redirect in a session
		Yii::app()->session['jsredirect'] = $url;

		// forward to the jsredirect action
		$this->forward('/site/jsredirect');
	}

This just sets a session var for the redirect and forwards to site/jsredirect so in SiteController.php I have

public function actionJsredirect(){
		
		if(isset(Yii::app()->session['jsredirect'])){
			$this->render('jsredirect',array(
				'url'=>Yii::app()->session['jsredirect'],
			));
		}
		
	}

And then in the jsredirect template file we have

<script>
window.location.replace("{$url}");
</script>

Giving up on Yii Oracle Clobs

Started off yesterday with the intention of trying to implement functional Oracle Clobs in Yii similar to how I implemented it in CakePHP a couple years ago. As yesterday went on I kept busting through boundaries and was feeling great about my progress. But Then I hit the roadblock and I wasn’t able to continue. It’s not worth spending another couple days on when I have a deadline for a pilot by Fall. 4000 characters is enough for the pilot.

The result has been to alter the Yii COciSchema. Just changing the declaration of text to a varchar2(4000).

   public $columnTypes=array(
        'pk' => 'NUMBER(10) NOT NULL PRIMARY KEY',
        'string' => 'VARCHAR2(255)',
        //'text' => 'CLOB',
        'text' => 'VARCHAR2(4000)',
        'integer' => 'NUMBER(10)',
        'float' => 'NUMBER',
        'decimal' => 'NUMBER',
        'datetime' => 'TIMESTAMP',
        'timestamp' => 'TIMESTAMP',
        'time' => 'TIMESTAMP',
        'date' => 'DATE',
        'binary' => 'BLOB',
        'boolean' => 'NUMBER(1)',
		'money' => 'NUMBER(19,4)',
    );

The issue is Yii (PDO) doesn’t support LOBs well at all, so that declaration didn’t make any sense to begin with.

Yii default ORDER BY

I ran into an issue with a minor difference between Oracle and MySQL. Apparently MySQL is better about returning rows in the order they were inserted than Oracle. Now if you want to let me know it’s wrong to assume results are returned in any specific order, I know! Neither gives any guarantee on the order of results without an ORDER BY, but Oracle is semi-random.

It took me a little while to find the right way to add default items to queries. Yii uses CActiveRecords for model queries. I already had an overridden CActiveRecord class from Yii handling “getLastInsertId” with Oracle. So I knew I would be able to use that somehow.

I finally discovered scopes. It allows me to define a scope:

public function scopes()
{
    return array(
        'sort_order'=>array(
            'order'=>'SORT_ORDER ASC',
        ),
        'id_order'=>array(
            'order'=>'ID ASC',
        ),
    );
}

and then use it as such:

MyModel::model()->id_order()->FindAllByAttributes(array('SOMECOLUMN'=>1))

But still I didn’t find anything on default scopes. On a whim I did a grep -i “defaultscope” on the code and discovered it exists in the Yii framework. So I was able to piece together the following:

public function defaultScope()
{
    return array(
 	'order'=>'ID ASC'
    );
}

and bam. Add that to my QActiveRecord, and it’s golden.

Github Pull Request for Just One Commit

I have a forked Yii which I use as a submodule in a couple of my applications. I haven’t made many changes, but most would probably not be useful most people. i.e. I put a conditional in that checks the version of PHPUnit and if it’s older than 3.5 it uses a different include file. Thereby letting me get around the limitation I have in one of my development environments.

My last commit, however, directly relates to an issue Yii had identified. So I wanted to push only that last commit upstream.

First make sure you have the upstream remote.
Then fetch from upstream — this creates upstream/master.
Then we create a new branch calling it upstream — not to be confused with the local remote we just created with the same name.


cd yii
git remote add upstream git://github.com/yiisoft/yii.git
git fetch upstream
git checkout -b upstream upstream/master
git cherry-pick
git push origin upstream

Then use something that was new to me, cherry-pick and give it the hash of the specific commit. That will merge over only that commit to the new branch you made. Then just push it.

From github, you can then make a Pull Request from that specific branch and contribute. Since that’s what it’s all about.

Helpful links:
 https://help.github.com/articles/fork-a-…
 http://kurogo.org/guide/github.html
 http://stackoverflow.com/questions/52560…

Git Template Application Repository

I titled this post what I wanted, but the result wasn’t quite so succinct.

What I wanted was a git repository that contained a template for future applications. I have done so much what I’m considering good work with Quizmo, I wanted to create a skeleton framework that I could fork into applications. So all of the components that I’ve created could be contained in one place and when I update them in Quizmo, they would likewise be updated in the template repository, and future applications would be able to easily pull down those same changes from the template repo. So I could really live the dream of rapid development RoR has been trying to sell for the last decade.

My first thought was to create a repo that had a dummy application in it and just forking it. Then I should be able to pass things back via the upstream and down with merges. It seems like such a simple operation. Unfortunately, that’s not how forking works. The problem is if you want to merge upstream, you have to merge everything. You can’t just merge the things that are in the upstream, there’s no mechanism for adding or committing one revision without committing every revision prior to that one.

So the only way to have different components connected via other repositories is to have multiple submodules. Everything needs to be contained in its own directory. There’s also fake submodules*. Which just relies on a lot more setup, I haven’t encountered problems with using submodules yet, so that isn’t an issue.

Instead of having the awesome application template repo I wanted, I’ve got a bit of a mish mash which currently looks like this:

The most important thing with this discovery of using a mish mash of submodules is that I should modularize my code. That is to say I need to have all related components in the same place. That seems obvious, but when going with the flow of yii, I put things where yii wanted them, not necessarily where they would be best served. Like twitter bootstrap, which got just strewn about all over the app.

The authentication abstraction I’m using is an IdentityFactory that chooses which xIdentity component to use which is an extension of UserIdentity but each individual xIdentity calls on its respective extension. So do the xIdentities go with the appropriate extension or with the IdentityFactory/UserIdentity? Asking the question makes me think it has to be with the extension.

Interesting links:

* Update: Upon working more with submodules, and paying attention to the pitfalls (outlined here) I’ve actually become very opposed to fake submodules (see above). Fake submodules relies on the person checking out your code to check everything out, and leaves the repository with no explicit link to the submodules. You basically just have to know. Which is fine for one guy working on one project, but is horribly irresponsible for a developer working for anyone other than themselves.

Abstracting schemas for MySQL and Oracle with Yii Migrations

Yii migrations are an interesting way to keep track of your database changes.  I was originally looking for a tool that would abstract the schema SQL — with something like the Yii ActiveRecord — so I wouldn’t have to write duplicate code for Oracle and MySQL.  I mean, that’s kind of the point of going with the PDO abstraction.

So more than a few forum question/answers led me to Yii migration:

http://www.yiiframework.com/doc/guide/1.1/en/database.migration

I seriously have that page open 3 times in my tabs and I have read it top to bottom probably 20 times.  I keep going back to it over and over hoping it will have more information.  It’s just a little short on answers to all of my questions.

As I said, all I wanted was a way to abstract the schema, so the original premise of “migrations” was a little different than what I was looking for, but it being the only non-custom option, I went forward with it.  The point of migrations is to make sure the state of your database matches the revision of your code.  So it’s sort of like its own hokey versioning system.  As such, it doesn’t function as simply as I might like it.  It’s not easy to focus on one migration if it’s not the last migration.  Because the point of migrations is that you’re moving forward and editing a past migration would be like editing revision X without it effecting all of the future revisions.  So it makes sense for what it is, it’s just awkward.  For one the file structure HAS to include the timestamp.  And it has to include that timestamp at the front of the filename, so autocompleting these migration filenames is a pain in the ass.

One important thing they don’t talk about in the documentation is that the list of what’s been migrated is added automatically to your database via the “tbl_migrations” — and please remember the “s as it will insert it as lowercase, which could be annoying for Oracle users, though Yii seems to handle it “appropriately”.

The only major hurdle here is dealing with autoincrement. The way this was dealt with was I created an Autoincrement class that I plopped in the migrations directory:
protected/migrations/Autoincrement.php

This meant that I could just create the tables like so:

class m120402_194059_Quizes extends CDbMigration
{
	public function up()
	{

		$this->createTable('QUIZES', array(
			'ID' => 'pk',
			'COLLECTION_ID' => 'integer NOT NULL',
			'TITLE' => 'string NOT NULL',
			'DESCRIPTION' => 'string',
			'VISIBILITY' => "integer NOT NULL",
			'STATE' => 'string',
			'SHOW_FEEDBACK' => 'integer',
			'START_DATE' => 'datetime',
			'END_DATE' => 'datetime',
			'DATE_MODIFIED' => 'datetime',
			'DELETED' => "integer NOT NULL",
		));	

		Autoincrement::up('QUIZES', Yii::app()->db->driverName);


	}

	public function down()
	{

		Autoincrement::down('QUIZES', Yii::app()->db->driverName);

		$this->dropTable('QUIZES');

	}

Writing a Style Guide

I took a look at the style guides of all of the most popular frameworks.

Yii Style Guide
Codeigniter Style Guide
Zend Style Guide
Symfony Style Guide
CakePHP Style Guide

Yii is the simplest. They just tell you what should go in the Model, Controller and View. I actually believe that’s the best thing when thinking of a framework as a framework, since a framework shouldn’t be telling you how to write your code, and you should never be mixing your code within the framework. A framework is a tool, but it should be on the primary developer to decide on the style that’s right for them and their team. However, I do understand that most of these style guides are talking about their framework as a project, which of course it is

There are a lot of topics to consider in a style guide. A lot of things popped up as I looked at the different style guides that I hadn’t even considered.

File Names
Class Names
Method Names
Method Definition
Class and File Names Using Common Words
Database Table Names
Constants
Variable Names
Member Visibility (Private / Protected)
Indentation
Commenting
Control Structures
The PHP Tag
Comparing Return Values and Typecasting
Logical Operators
Debugging Code
PHP Errors
Whitespace
Linebreaks
Compatibility
One File per Class
Bracket and Parenthetic Spacing
Localized Text
One Statement per Line
Strings (When to use Single or Double quotes)
SQL Queries
Default Function Arguments
Associative Arrays
Unit Tests

Some of the style guides got so long I kind of got angry. Trying to enforce things like whitespace, logical operators or indentation is foolish. It’s just too much and it takes away from the things that are actually important.

So what’s actually important?

What’s important is what people are going to read. Nobody wants to read through a 30 page style guide, and they won’t. The best solution I’ve found is to have 2 style guides. The first, important style guide is to outline the very essential rules you want for your project. Try to keep it short enough to fit on one page so someone may actually read the whole thing. Then, if you choose, you can have a nitty gritty style guide that outlines how you want your variables named and if you’re an indent nazi. You can always refer back to the nitty gritty if someone is submitting code that you find repugnant.

My style guide for Quizmo
My nitty-gritty style guide for Quizmo

Yii fixtures with Foreign Keys

CDbException: CDbCommand failed to execute the SQL statement: SQLSTATE[HY000]: General error: 2292 OCIStmtExecute: ORA-02292: integrity constraint (QUIZMO_DEV.FK_ANSWERS_QUESTIONS) violated – child record found
(/web/quizmo/PDO_OCI-1.0/oci_statement.c:142). The SQL statement executed was: DELETE FROM “QUESTIONS”

Totally annoying. Now I’m not one who has a ton of experience with foreign keys as most places seem to have relations with tables but never explicitly link them as such.

So a FK just doesn’t let you add a record when the record it’s linking to doesn’t exist and won’t let you delete a record when there is some other record linking to it.

The problem is I don’t think Yii thought about these when they did fixtures. They built in a way to deal with it, but it’s a little annoying.

Let’s ignore the fact that all of my tables are in caps because I want this to work in Oracle without having to throw single quotes around everything all the time. Let’s take a look at the fixtures of a section of my tables in a project I’m working on right now.

fixtures/
USERS.php
COLLECTIONS.php
USERS_COLLECTIONS.php
QUIZZES.php
QUESTIONS.php
ANSWERS.php

First I need an init.php in the fixture directory. This is called from /yii/framework/test/CDbFixtureManager.php


	public function prepare()
	{
		$initFile=$this->basePath . DIRECTORY_SEPARATOR . $this->initScript;

		$this->checkIntegrity(false);

		if(is_file($initFile))
			require($initFile);
		else
		{
			foreach($this->getFixtures() as $tableName=>$fixturePath)
			{
				$this->resetTable($tableName);
				$this->loadFixture($tableName);
			}
		}
		$this->checkIntegrity(true);
	}

So the init file just needs to do this:

foreach($this->getFixtures() as $tableName=>$fixturePath)
			{
				$this->resetTable($tableName);
				$this->loadFixture($tableName);
			}

The issue is the resets and loads need to be done in the right order. So the way I went about this is I put in an array of the tables, using that and a reversed version of the array.

$reset_order = array(
	'USERS_COLLECTIONS',
	'ANSWERS',
	'QUESTIONS',
	'QUIZES',
	'USERS',
	'COLLECTIONS',
);

$load_order = array_reverse($reset_order);

foreach($this->getFixtures() as $tableName=>$fixturePath){
	if(!in_array($tableName, $reset_order)){
		throw new CException("Table '$tableName' is not in the reset_order.");
	}
	if(!in_array($tableName, $load_order)){
		throw new CException("Table '$tableName' is not in the load_order.");
	}
}

foreach($reset_order as $tableName){
	//echo("resetting $tableName\n");
	// this runs the TABLE.init.php if it exists
	// otherwise it just does a $this->truncateTable($tableName);
	$this->resetTable($tableName);
}
foreach($load_order as $tableName){
	//echo("loading $tableName\n");
	$this->loadFixture($tableName);
}

That’s not all though, because EVERY table needs a TABLE.init.php file that will be run when “resetTable()” is run in the above script.

fixtures/
init.php
USERS.php
USERS.init.php
COLLECTIONS.php
COLLECTIONS.init.php
USERS_COLLECTIONS.php
USERS_COLLECTIONS.init.php
QUIZZES.php
QUIZZES.init.php
QUESTIONS.php
QUESTIONS.init.php
ANSWERS.php
ANSWERS.init.php

Without these scripts, each table will just DELETE FROM MYTABLE when resetTable is called. The problem with this is if you DELETE FROM COLLECTIONS without deleting the QUIZZES first, you get an integrity constraint violation. So a table init file needs to truncate all tables that are “children” of that table in the order of smallest to largest. COLLECTIONS.init.php looks like this:

truncateTable('USERS_COLLECTIONS');
$this->truncateTable('ANSWERS');
$this->truncateTable('QUESTIONS');
$this->truncateTable('QUIZES');
$this->truncateTable('COLLECTIONS');
?>

Yii handling “getLastInsertId” with Oracle

With MySQL or SQLite, when you insert something with an auto_increment field, it will automatically deal with the ActiveRecord by putting the last inserted id into $model->id. Because Oracle needs sequences and triggers to deal with that, neither the PDO driver nor the Yii PDO code felt it necessary to deal with that. Most people are probably fine with commiting to a database and throwing in Oracle specific sequence_name.nextval.

I just added a class in the models to extend CActiveRecord. The only thing you need to do with this is have your model extend this instead of CActiveRecord and add the variable schemaName to the model:

class User extends QActiveRecord
{
	public $sequenceName = 'USERS_SEQ';	
...