Friday, December 2, 2011

Modernizr

Again, not backend, but check this out:

Modernizr

HTML5 Boilerplate - A rock-solid default template for HTML5 awesome.

Not Yii or even backend coding, but way cool.

HTML5 Boilerplate - A rock-solid default template for HTML5 awesome.

Thursday, November 17, 2011

Firefox4 on OSX 10.5 (leopard) failing to open from command line : bang_head_on_wall

I needed to launch Firefox from the command line in OS X 10.5.8, and it just wouldn't work. This blog post saved me:

Firefox4 on OSX 10.5 (leopard) failing to open from command line : bang_head_on_wall

So very easy to fix!

Tuesday, November 15, 2011

Selenium 2 from PHP code | Web Builder Zone

This article breaks down the current array of choices for PHP and Selenium better than anything I've seen so far. Nice work!

Selenium 2 from PHP code | Web Builder Zone

Thursday, November 10, 2011

Continuous Integration Pt IV - Working with PHPUnit, Selenium, and SauceLabs

Ah, yes, the saga continues!

Today, I am going to try to get our PHP Selenium tests to run on SauceLabs' OnDemand service. SauceLabs is a cross-browser testing service. You can run your tests by hand - by opening an instance of a particular browser on their server via your own Web browser - or in a more automated fashion. I want to do the latter. What this entails is writing tests to tell the SauceLabs servers with which browser we wish to test on which operating system (e.g., IE7 on Windows). You can run the same test across multiple browsers, and any failed tests will be recorded for you to see exactly what went wrong. We're planning to automate the running of these tests, kicking them off when a developer pushes their code to the staging server.

The tests can be written in a number of languages for either Selenium RC or WebDriver. Because I'm using PHPUnit (since I code primarily in PHP), I'm stuck with Selenium RC, which is quite old but is supported by extensions in PHPUnit. I've successfully run tests on my local development machine, but now I'm going to adding one more layer of complexity, i.e. the SauceLabs servers. This requires one more set of extensions be added to the PHPUnit test framework. Here goes!

Install the SauceLabs SauceOnDemand packages from Pear:


sudo pear channel-update pear.php.net
sudo pear upgrade pear
sudo pear channel-discover pear.phpunit.de
sudo pear channel-update pear.phpunit.de
sudo pear channel-discover components.ez.no
sudo pear channel-update components.ez.no
sudo pear channel-discover pear.symfony-project.com
sudo pear channel-update pear.symfony-project.com
sudo pear channel-discover saucelabs.github.com/pear
sudo pear channel-update saucelabs.github.com/pear

No problems there. I'd already done the "discovering" of pear.phpunit.de and pear.symfony-project.com, so I only needed to channel-update for those.

Next, install the SauceLabs Selenium PHPUnit extensions:

sudo pear install -a saucelabs/PHPUnit_Selenium_SauceOnDemand

Great! That worked for me with no problems.

The next steps requires a SauceLabs account, I think. It was very cryptic, at first, because, without being logged in, it just says to do the following:


sauce configure


But that didn't do anything but throw a strange error and then tell me I was ready to run saucy tests and that I feel hot and saucy (which I didn't, btw).


After creating a free account (which anyone can do) and logging in, returning to the instructions page revealed the same instructions to do "sauce configure," but that command was followed immediately by my username and a hash code. So, I ran that. No errors, and now I really am feeling hot and saucy.


I'm going to run a test of a test. (I guess I'm feeling saucy and testy.)


curl -s https://saucelabs.com/example/se1/php/private-JJHyCGMF | bash

Well, this threw up a big ugly error with stacktrace and junk. It looks like something is calling an undefined method:

undefined method PHP_CodeCoverage_Filter::getInstance()

That's no good. I'm wondering if I need to update my version of PHPUnit is old? Maybe I should just upgrade. I did this

pear upgrade

and suddenly faced all kinds of ugly. Upgrade failed. Doh! Oh, I forgot to use 'sudo':

sudo pear upgrade

Much better! Now I've got upgraded Pear stuff.

I ran the curl command from above again, and got exactly the same results. Hmmm...

Looking at my Pear channels and packages, I've noticed that I have two versions of PHPUnit installed, one in the traditional pear.phpunit.de channel, and one under saucelabs.github.com/pear. the saucelabs version is older than the other one. There is also a different version of PHPUnit_Selenium in that channel, though that's not the one that is active. I've emailed the SauceLabs people for some help on this. My concern is that it's due to conflicts in one version vs. another, and I'd like to nip that possibility in the bud.

In the meantime, I'm going to look at the problem code. The error message is:

Call to undefined method PHPUnit_Util_Test::getParallelismSettings() in /opt/local/lib/php/PHPUnit/Extensions/SeleniumTestCase.php on line 352

I won't write about that in this post, however. For the time being, this is a work in progress.

Wednesday, November 9, 2011

Continuous Integration Pt III - Working with PHPUnit and Selenium

The continuing adventures of Hollyii as I attempt to implement continuous integration at my job. I've got:


  • Ant (my build tool), with a very small, very basic, mostly useless build script just to make sure it goes
  • PHPUnit (unit testing), which is running happily on my local OS X laptop
  • A means of bootstrapping PHPUnit to integrate it into the Codeigniter application, along with very few (meaningless) tests
  • Selenium Server installed, which hasn't been put to the test at all, except to ascertain through ps auxwww that it's genuinely running

So, what's next? Well, I think the next thing is to set up the Selenium extension for PHPUnit. This, I'm happy to report, is very easy. Assuming you're using Pear and have installed PHPUnit through Pear, this is all you do:

sudo pear install phpunit/PHPUnit_Selenium

I need to 'sudo' due to my permissions, but you might not need to.

Everything went well for me, and it's installed. I can verify using

pear list -a

Yep. It's there. Good.

I'd like to run a basic test with Selenium. I'm going to be referring to the PHPUnit documentation, found here, and a Zend Dev Zone article, found here.

Before I can do that, it turns out that I need one more Pear package, Testing_Selenium. I've installed it thusly:

sudo pear install pear/Testing_Selenium-beta

Note the "-beta" on the end. This was necessary due to the fact that my preferred state for Pear packages is "stable," but this package default state is "alpha." Including the "-beta" resulted in my installing v0.4.3 (beta) not 0.4.4 (alpha).

I've copied and pasted a test right out of the PHPUnit manual. It looks like this:

<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class WebTest extends PHPUnit_Extensions_SeleniumTestCase
{
    protected $captureScreenshotOnFailure = TRUE;
    protected $screenshotPath = '/var/www/localhost/htdocs/screenshots';
    protected $screenshotUrl = 'http://localhost/screenshots';

    protected function setUp()
    {
        $this->setBrowser('*firefox');
        $this->setBrowserUrl('http://www.example.com/');
    }

    public function testTitle()
    {
        $this->open('http://www.example.com/');
        $this->assertTitle('Example WWW Page');
    }
}
?>

This, alas, did not work. It should have failed, because the HTML title property for www.example.com is not really "Example WWW Page." But my test skipped the test altogether. It comes back as "OK, but incomplete or skipped tests!" Not what I wanted.

I did a little searching in the PHPUnit documentation, and I've learned that skipped tests are often due to the environment not supporting the test you're trying to run. You can even mark things as skipped, if you determine through your code within the test that you don't have access to something. They're example was:

<?php
class DatabaseTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        if (!extension_loaded('mysqli')) {
            $this->markTestSkipped(
              'The MySQLi extension is not available.'
            );
        }
    }

    public function testConnection()
    {
        // ...
    }
}
?>

I'm not doing this in my code (that is, checking for loaded extensions, marking things as skipped), but the fact that I am getting a "skipped" message suggests that my Selenium set-up isn't quite right. So, now I need to figure out how to make sure Selenium is set up correctly.

After some sleuthing, I have discovered that my version of Selenium is perhaps too new for PHPUnit's extension. When I originally started this quest for continuous integration, I intended to use Selenium RC but found that it had be deprecated in favor of the new Selenium 2 (a.k.a. Selenium WebDriver), so I downloaded that one. (Who wants a deprecated application?) But that was not what I needed, so I am now backtracking out of it.

The first step was to download the older model from the seleniumhq.org site. The page the link directs you to has all of the available downloads, including all the versions of Selenium RC. I downloaded the newest version of that program. This gave me a zipped archive containing a slew of files. All I needed was the selenium-server.jar file. I moved it to the directory in which I like to keep jar archives.

Moving the file was all that I really needed to do to "install" it; jar files are executable, and all you really need to do to run them is invoke java and pass it the location of the jar archive. But I'm on a Mac, and I want to take advantage of the launchctl program to launch the file for me. I created a plist file (almost identical to the one in my previous post), stored it where the plist files go (usually ~/Library/LaunchAgents), and issued two commands to 1) load the plist file and 2) to start the executable:

launchctl load ~/Library/LaunchAgents/org.seleniumhq.selenium.plist
launchctl start org.seleniumhq.selenium

But it didn't work. It failed silently! I know this because I ran

ps auxwww | grep selenium

All I saw was my old version. Doh! I need to stop the old one and start the new. They're both listening on the same port, so the new one couldn't start. It would have been nice to have some feedback, but oh well.

launchctl unload ~/Library/LaunchAgents/org.nhabit.Selenium.plist

That stopped the old one. I ran the same previous two commands as before:

launchctl load ~/Library/LaunchAgents/org.seleniumhq.selenium.plist
launchctl start org.seleniumhq.selenium

And now I can try to run my test again.

Well, it still skipped my test, but I know that what I've done was not a waste of time (at least, I think it wasn't), because I really needed to install Selenium RC anyway. Now, it's just a matter of making sure I've got it set up properly.

Alright. I poked around some more, using mindfulness based stress reduction to keep from having an actual tantrum on the hard, tile floor. Here is a tip: use the '--verbose' option when running phpunit. Let me say it again: use '--verbose' to help debug your phpunit testing! For example:

phpunit --verbose mytest.php

I had been using it to debug other tests, and it didn't give me anything. But this time, it let me know that it was attempting to connect to Selenium on port 4444 and couldn't. Very useful! Upon inspection, I realized I was running Selenium on port 4443. A-ha! Shut the server down, changed the port number, restarted it, and presto! It works. Amazing. I can now die happy.

Continuous Integration Pt II - Unit Testing and Codeigniter

I'm still plugging away at getting our shop set up for continuous integration. We use Codeigniter for our application, and this presents a huge can of worms with regards to unit testing. First of all, Codeigniter does include some minimal unit testing support within the app, but, by all accounts, it's pretty darn small. The holy grail for unit testing in PHP, IMHO, is PHPUnit. It's pretty much the standard, and it's well supported. So, it makes sense to use that, since our tests will continue to be relevant, whether we change versions of Codeigniter, whether developers come and go, etc. It's a nice constant to shoot for.

The big problem is that PHPUnit doesn't just naturally hook into Codeigniter. Things get even more complicated when you start looking for solutions for bootstrapping it, as it greatly varies depending on the version of Codeigniter you're using. Ellis Labs (the developers of Codeigniter) is working on integrating PHPUnit into the framework at some point in the future, which will be version 2.?. In the meantime, another developer has started with their code and completed it to make it work, and it works with version 2.0.3. Well, we're on version 2.0.2. So, that's a bummer. The earlier attempts to integrate PHPUnit with Codeigniter are usually for 1.7.x and below.

In the end, I managed to get another developer's much simpler bootstrapping code to work. You can find it here. That's a Codeigniter forum post. Look for CarloGI's post about his technique. He includes a download of example files. Download the files, put the where they belong, and make sure you modify the _getDBObject function of the bootstrap.php file to match your mysqli settings for your app. You'll want to include the --stderr argument when you run phpunit so that you don't get the output buffers error. One other thing I had to do was to comment out an echo statement in myControllerTest.php. Otherwise, it will cause the output buffer error, as well.

Sorry these instructions are so cryptic. I wanted to get them out there (mostly for myself), but they might help someone else. If I have time, I will add more detail.

Thursday, November 3, 2011

Format My Source Code for Blogging

This is very useful for blogging source code.

Format My Source Code for Blogging

I'll go through and fix some previous posts soon.

Notes on Apache Ant

I'm an Apache Ant newbie. I've known for years that it exists, but I've never used it. I just installed it, today, and now I'm trying to understand what it does and how to use it. Here are some notes:


  • An Ant build contains one project
  • That project has at least one target
  • Each target contains tasks


The build file defines these things and is written in XML and is called build.xml. It has to define at least one target. The target defines one or more tasks to perform when it is run. Here is my first build file:

<?xml version="1.0" encoding="UTF-8"?>  
<project name="helloworld" default="init" basedir=".">  
    <description>  
    Build file for the Hello World application.  
    </description>  
    <target name="init" description="Initialize the build.">
        <touch file="testinit" />
    </target>
</project>

It includes a description (of the project) and one target. The target is simply a set of tasks you want to run. It can be called whatever you want, really. The target name attribute is what you will call from the command line when you want to run those tasks. In this example, my target has the name attribute "init". In that target, the only task I've included is the "touch" task. This will execute the *nix "touch" command. The "file" attribute within the "touch" task specifies which file to touch. (See the Ant manual for more available tasks. They are legion.)

Now, to run this, I go to the directory containing the build.xml file and issue the following command:

ant init

Notice that I didn't say "ant build.xml" or "ant build." This is because I'm telling ant to run a specific target. Also, if I change the name of the file from build.xml to moo.xml, it will break. Ant expects the build file to be called build.xml.

Wasn't that fun?

Continuous Integration Part I

I'm setting up a suite of continuous integration tools for my current job. We are a PHP shop using Mercurial for version control. My catalogue of tools is as follows (so far):

  • Ant (automated build tool)
  • PHPUnit (unit testing)
  • Selenium (functional testing)


I haven't decided on a CI server, yet. I'm looking at Hudson, as it has an extension to support Mercurial. Eventually, I'm hoping we'll use a service, like SauceLabs' Sauce OnDemand to perform our functional tests, since I'd like to take advantage of their cross-browser tests. They use Selenium RC, so we'll want to get started using that, ourselves. As of version 3, PHPUnit has support for writing tests for Selenium. I don't know, yet, whether those would work with SauceLabs, nor do I know whether we'll use those or write our scripts the traditional way, as per the documentation on the Selenium site. Another thing to note is that Selenium RC (Selenium 1) is officially deprecated in favor of Selenium Webdriver (Selenium 2), but they're going to support Selenium RC, doing bug fixes and the like, as it has more features than Webdriver (at least for now).

To get Ant to work with Mercurial, I'm going to need ANT4HG.

I still need to ascertain what all, exactly, will go into my build process. I want it to represent a complete rebuild of our entire Web app from scratch, complete with a set of clean test data and the application code, as well as all tests. I'm suspicious, however, that there will be more to it than doing a clean check out (in hg terms, a clone) of our code base and grabbing a db schema from somewhere, but we'll see.

Tasks done so far:

  1. I've installed PHPUnit using Pear (which I had already installed through Macports when I installed php-5). 
  2. I have installed Ant (an Apache project) using Macports.
  3. Installed ANT4HG as follows:
To install ANT4HG, I downloaded the binary from the SourceForge downloads. I extracted the zip file and ended up with a jar file. I then moved the jar file to the ant lib directory. On my system, that is located here: /opt/local/share/java/apache-ant/lib. It should now be available for Ant to use.


OK, now for Selenium (deep breath). I need the Selenium standalone server. Eventually, I won't need this (I hope), since we'll rely on SauceLabs for this service. But for the purposes of setting up a test of our chosen tools - and also because we're not going to subscribe to a service, yet - I need to have it up and running locally. I'm following instructions I found here, with the exception that I put my selenium jar file in /opt/local/share/java/selenium instead of /usr/lib/selenium. (Either way, I needed to create the selenium directory.)

I then created a launch file so that Selenium would be launched on system startup. The file is copied from the instructions I used on Dan Straw's site. Since my "selenium" directory is in a different location than his, I had to modify my launch file to reflect the proper location. I also had to change the name of the jar file, as he mentions in his write-up. I loaded the plist file and started the service. Using "ps auxwww | grep -i selenium" I learned that it's up and running. Good.

Everything is installed, now, so I just need to get my feet wet and play with it. I'll save that for another post. In the meantime, I'll be looking this.

Monday, October 17, 2011

Searching Relations in Yii's CGridview

[NOTE: I'm typing this pretty frantically, just to get it out there and then move on to my next task! So, it might be a bit confusing. You should familiarize yourself with Yii's CGridView widget before trying to understand what the heck I'm rambling on about below.]

In the default grid view widget usage in admin.php (when you use gii to generate it), all of the fields displayed are directly tied to the model you're viewing. Many times, you don't want to see the value of a foreign key but rather the actual string that would be associated with that particular foreign element. For example, if I have a grid view to display a series of Company models, I don't want the "state" column to display "2" if it really could display "California." That's easy enough to fix by providing some additional information in the CGridView widget definition. Instead of including the state column as "stateId", include an array that specifies the column in the Company table ("stateId"), a header to display at the top of the grid column ("State"), and the value that should actually be shown ("$data->state->name" which represents the "name" column from the related model, "state," for the current data element - $data - of the many Company models returned):


<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'company-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'selectableRows'=>2,
'columns'=>array(
'name',
'street',
'street2',
'city',
array(
'name'=>'stateId',
'header'=>'State',
'value'=>'$data->state->name',
),
'zipCode',
array(
'class'=>'CButtonColumn',
),
),
)); ?>

The problem, however, is that you lose the search field at the top of the column that is used for filtering the result set shown in the grid! To get that to show up again, you'll need to do a couple of other things. Both steps involve the model itself. In my case, I'm dealing with my Company model. Here goes.

First, you need to change the way the comparison is handled in the search() function when it comes to the model's state information. By default, you probably would have something like this:

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

But this doesn't get you what you want, because we're not really comparing the value of a foreign key with an integer that has been entered. (I mean, who's gonna enter an integer when they're searching for Georgia?) So, make it look like this:

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

This tells the comparison to compare the name of the state with the value returned from the incoming 'stateId' field (which is now going to be a textual representation of the state's name).

Notice that I'm using the "dot" notation, as in "state.name". This is the syntax for querying a table with the alias "state" for the value of it's "name" column, of course. The reason I can do this, here, is that I've already made sure that

  1. I have a relation in Company with the State table that is referenced with the alias "state" (part of the Company model's relations() function that returns an array of relations).
  2. I have included "with" in my search() function's criteria to let Yii know to join the State table when it selects the Company models:  $criteria->with=array('user','image','video','state','approvedBy0','deletedBy0');
  3. I have included "together" in the criteria and set it to true: $criteria->together=true;
These are important for defining for Yii how to retrieve the related states information in a way that makes it available to search().

The other piece of the puzzle involves the rules() function of the Company model. You have to declare that the "name" column of the State table is safe on search. Most likely, you already have a rule for searching that looks something like this:

array('name, street, street2, city', 'safe', 'on'=>'search')

This means that the columns, "name," "street," and "street2" are safe to include for the search scenario. But we also want the "name" column from our State table to be safe. The way we add this is to take advantage of our relation (which has provided us with the alias, "state," for the State table). Because states are retrieved actively - since "with" includes the state alias and "together" is set to true - we can refer to it's member variables. To include the State tables "name" column in the search, then, just add it to the array:

array('name, street, street2, city, state.name', 'safe', 'on'=>'search')

That's all there is to it! You should now have:


  • A column in your grid that displays the state's actual name, rather than the foreign key for the state
  • A header at the top of the column that reads "State" instead of "stateId" (how gauche)
  • A text field for searching on states that compares what you've typed in with the actual state name values from the available models.

I've seen examples of how to make the search field a pull down menu rather than a text field (which makes sense for states), but I'm not going to post on that, at the moment. I would point out, however, that you can also now sort the state names by clicking the "State" header. Sweet!


Friday, October 14, 2011

Updating a Listview in Yii with Ajax

This was an unbelievable exasperating feature to add. I have no idea why this didn't work in the myriad ways I tried to make it work, but I've got it working, now, so I wanted to post.

I looked at every example of updating a CListview that I could find on Google. None of them were doing what I wanted to do (and what I'd assumed was a very common task). In a nutshell, I wanted to have a listview on my view page and then update the results when someone clicked on a link. All of the examples I saw involved submitting a form. I saw one that was using a link, but the details provided in how they solved it were so miniscule as to be almost humorous (had I not been so frustrated).

I won't discuss this solution in great detail. I'll simply include the information below.

The View
At the top of the index.php view, I have this:

<?php

Yii::app()->clientScript->registerScript('ajaxUpdate',
"
$('.ajax_link').click(function(){
$.fn.yiiListView.update('companyList')
});
return false;
", CClientScript::POS_READY);
?>

Inside the view itself is the listview:


<?php $this->widget('zii.widgets.CListView', array(
'dataProvider'=>$dataProvider,
'itemView'=>'_view',
'id'=>'companyList',
)); ?>

The links are generated in the view, too, of course. They look like this:


foreach($allLetters as $letter) {
if(in_array($letter, $dbInitials)) {
/*
echo CHtml::ajaxLink(strtoupper($letter), CHtml::normalizeUrl(array('index', 'initial'=>$letter)),
array('success'=>"$.fn.yiiListView.update('companyList')")
);
*/
echo CHtml::link(strtoupper($letter), CHtml::normalizeUrl(array('index','initial'=>$letter)), array(
'class'=>'ajax_link',
));
} else {
echo strtoupper($letter);
}
// Add some whitespace.
echo '&nbsp;&nbsp;';

}

In fact, that's pretty much all there is in my index.php page. A Javascript portion that is registered as a client script, a list view with the id set to companyList so I can refer to it later, and a series of basic html links with a class of ajax_link so that the click event is triggered in the Javascript.

On the server side, the controller action for index looks like this:

public function actionIndex($initial='')
{
if($initial) {
$criteria = new CDbCriteria;
$criteria->compare('name', $initial . '%', true, 'AND', false);
} else {
// Retrieve all companies to list
$criteria = new CDbCriteria;
$criteria->with=array('video','image','state');
$criteria->together = TRUE;
}
// Only admin users can see ALL records. Others can only see approved records.
if(!Yii::app()->user->checkAccess('admin')) {
$criteria->addCondition('t.isApproved=:isApproved');
$criteria->params[':isApproved']=1;
}
$dataProvider=new CActiveDataProvider('Company', array('criteria'=>$criteria));

$this->render('index',array(
'dataProvider'=>$dataProvider,
'dbInitials'=>$this->dbInitials,
'allLetters'=>$this->allLetters,
));
}

If no parameters are submitted, it just renders all of the results. If an initial letter is submitted, it filters the results. Either way, the index view is rendered.

The main problems I encountered were that:
  1. The AJAX request would return the results I wanted, but the div wouldn't update.
  2. Once I finally got the div to update, I was getting an error from the yiiListView.update() method. Adding the "return false" in the Javascript code fixed that.
Done.

Thursday, October 13, 2011

Yii: Selecting First Letter of a Column in Each Row

At some time or other, I'm sure you've browsed names of companies or people and were presented a list of initials to filter your results by letter or other character. If the search results don't contain any companies or people with a particular letter, it is nice when that letter is inactive, that is when it doesn't link to anything. Otherwise, you just get an annoying "no results" response. Shouldn't the application have known in advance that there were no people in the database whose name started with Z? Now, you've wasted your valuable time clicking it for nothing!

I'm working on building just such a list of letters and characters, and it's surprising to me, but I've never actually gone through this exercise of creating the list with anchor tags only around those letters that correspond to values in my db. I'm going to blog my steps in short posts. This will cover only retrieving the initials. Subsequent posts will cover the steps to output the results.

I'm pulling from a table called "Company." I want just the first letter of each company name. In this case, the name is stored in - can you guess? - the "name" column. So, my basic SQL looks like this:

"SELECT DISTINCT substring(name,1,1) AS initial FROM Company"

This should return the first letter (a substring of one character, beginning with the first character) of the name column from the table Company. 'DISTINCT' ensures I only get one of each.

But I'm using Yii, so I need a way to execute this query the Yii way. (Or one of the Yii ways, anyhow.) I decided to use CDbCommand with a series of available setters. Here's what that looks like:


$dbCommand = Yii::app()->db->createCommand();
$dbCommand->setSelect(array('substring(name,1,1) as initial'));
$dbCommand->setFrom('Company');
$dbCommand->setDistinct(true);

That's pretty straightforward, if you have taken a look at the CDbCommand info in the Yii documentation. In a nutshell, I've instantiated the command object. Next, I use setSelect() to provide my select information. In this case, I'm using an array. You could include it as a string (e.g., $dbCommand->setSelect('myname as name'). I'd come across a post that suggested that there were issues with using the string, however, and  so I decided to just go with an array.

The call to setFrom() is probably self-explanatory; it just provides the "from" values for the query. I used a string, because I had only one table. More than one value in from would require you to use an array instead.

The use of setDistinct() is so that I can keep my results, well, distinct. It takes a boolean (true or false) as it's value. I want it to return distinct results (only one of each), so I set it to true.

Oh, you might have noticed that I used Yii::app()->db->createCommand() instead of new CDbCommand. This is a convenient way of taking advantage of the 'db' component I've already set up for my app. If I were to instantiate a new CDbCommand object with the "new" keyword, I'd have to supply the connection information. By using the createCommand() function, I can avoid all that and just use the connection info set up in my app. (If you were connecting to a different database, you'd need to do this the long way with "new.")

OK, now how do I get the results? I've built up the command itself. Now, I just need to actually execute it. I went with one of the convenience functions provided by CDbCommand, "queryColumn()." It returns one column of results, which is exactly what I need, and stores it in an array, like so:

$dbInitials = $dbCommand->queryColumn(); // First letter of the company names from the db

If I had wanted only the first row of information, I could have used queryRow(). The queryScalar() function returns just the first column from the first row. There are a couple of other query functions, and there's also just plain execute(). Check them out in the documentation to see what works best for your situation.

So, that's it for this installment. I've now got an array of letters that I can loop through and create links for filtering. Of course, I want to include the other letters that didn't end up in my $dbInitials array, but I'll save that for the next post.


Feel free to post suggestions if you have better ideas of how to do this. I'd love to hear how this could be made more efficient or otherwise more intelligent. Thanks!

Tuesday, September 20, 2011

Useless SVN Error Message: Network connection closed unexpectedly « Ben J. Christensen

This is VERY helpful if you're trying to use svn+ssh with an alternate port number and you get the vague message about the network connection closing unexpectedly:

Useless SVN Error Message: Network connection closed unexpectedly « Ben J. Christensen

Before you go there, however, if you have shell access on the remote host from which you're trying to check the project out, be sure to test locally:

svn list svn+ssh://username@localhost/path/to/project/repository

If you don't have a port number specified in ~/.ssh/config, you should first try:

svn list svn+ssh://username@localhost:1234/path/to/project/repository ('1234' in place of your port number)

If that doesn't work, open up ~/.ssh/config (you may need to create it) and enter this:

Host yourhost.com
User username
Port 1234

In the case of testing on localhost, I used this config file:

Host localhost
User myUsername
Port 1234

Literally use the host, "localhost."

Once I knew that worked, I could try remotely, at which point I got the seemingly meaningless error. So, I then created the config file on my local machine.

Good luck!

Sunday, September 11, 2011

Dana Luther on Yii's AuthManager Using the Database

Dana Luther is "da bomb" as they say. Her guides are well-written and super helpful. Her blog is one of my go-to choices when I'm stymied or confused by something in Yii.

Here, for example, is a great article on setting up Yii's built-in AuthManager to use your application's database:

Adventures in Programming: Yii Authentication via database

Monday, August 29, 2011

Sanitizing user data: How and where to do it | Diovo

Sanitizing user data: How and where to do it | Diovo:

'via Blog this'

Output escaping in PHP • Matt Robinson

Output escaping in PHP • Matt Robinson:

'via Blog this'

Help Central » Blog Archive » How to create custom validation rule - Discretelogix

Creating your own validation rule for a model. It's basically a one-off for that particular model, since the rule is both used and defined within the model class definition.

Help Central » Blog Archive » How to create custom validation rule - Discretelogix:

'via Blog this'

Example of Including 'criteria' with a Unique Validation Rule


public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('entity_category_id, entity_id', 'required'),
array('preferred','boolean'),

                        // unique with condition
array('preferred','unique',
'criteria'=>array(
'condition'=>'entity_id=:entity_id',
'params'=>array(':entity_id'=>$this->entity_id),
),
),

// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, entity_category_id, entity_id', 'safe', 'on'=>'search'),
);
}

Thursday, August 25, 2011

Setting the returnUrl for a User in Yii

When you have logged in a user in Yii, the default is for them to be redirected to the main home page of the site. But sometimes, you want them to go elsewhere. Here's a forum question (in which the op answers it for himself) that is very helpful in figuring out how to do this (minimally documented) function:

returnUrl question? - Yii Framework Forum:

'via Blog this'

In my case, I needed the user to return to an admin module's home page. Here's what I did:

In SiteController, in the actionLogin function, add a line of code after the user has successfully logged in, like this: Yii::app()->user->setReturnUrl('/admin');

Including the slash on the front of "admin" tells the app to start at the top level (the root of the actual site) when determining the path to take rather than a relative path. For my app, '/admin' means the admin module.

Here is what the entire actionLogin function in SiteController.php looks like:

public function actionLogin()
{
$model=new LoginForm;

// if it is ajax validation request
if(isset($_POST['ajax']) && $_POST['ajax']==='login-form')
{
echo CActiveForm::validate($model);
Yii::app()->end();
}

// collect user input data
if(isset($_POST['LoginForm']))
{
$model->attributes=$_POST['LoginForm'];
// validate user input and redirect to the previous page if valid
if($model->validate() && $model->login())
Yii::app()->user->setReturnUrl('/admin');
$this->redirect(Yii::app()->user->returnUrl);
}
// display the login form
$this->render('login',array('model'=>$model));
}

Monday, August 22, 2011

Finding Out Your Current Subversion Repository URL

Have you been working on this project so long that you've forgotten where your repository is? Do this:

svn info | grep URL

Nice.

Switching a Working Copy in SVN

Use "svn switch" for this. It's not very painful, and it keeps your local changes. Basically, it's "svn update" on steroids: not only does it update to the latest modifications in the repository (again, keeping your local changes), it also relocates where your working copy is "looking to" to know what has been changed. Think of your local working copy as keeping its eye on the master list of files and changes. Now, that gaze has shifted elsewhere, perhaps to a branch. This is fantastic, for example, if you want your current local modifications to be checked into a branch instead of the head. Just use svn switch, and then you'll be interacting with a different batch of project files.

The command itself is simple:

svn switch new/repository/location

The new repository location could be a file:/// location, if you're doing this on the same server on which the repository resides, or an https:// location, if it's remote. Be sure to include the full path, including any directories, like /trunk or /braches/new-branch.

Subversion Users: Re: create trunk for an existing rep

Like a lot of people (I think/maybe/probably/noobs), I was in a hurry when I created the svn repository for my current project and didn't bother setting up trunk, branches, and tags subdirs. Now, I regret it, because I am suddenly interested in branching.

This is all very scary, because we're in the middle of testing an app that is supposed to go to the client at the end of the week. If I break this, I screw up our beta testing for everyone. So, I was a bit nervous.

I found the following forum posting, though, and it was very helpful:

Subversion Users: Re: create trunk for an existing rep

In a nutshell, I did everything recommended on the forum:


  1. Set up a sandbox somewhere to check out the files. I'm on Linux (and this assumes you are, too), so I just logged onto a server to which I had access and went straight home. (I was already in home upon logging in via ssh, but you can just do this: cd ~)
  2. Check out the current head: svn checkout https://path/to/project (you're on your own for this, since I don't know where your repos lives)
  3. Alternatively, you could check it out to a specific directory: https://path/to/project somedir (specifying 'somedir' as the new directory to which to check the files)
  4. Go into the directory (either 'somedir' or the default, if you didn't provide the 'somedir' directory): cd somedir
  5. Now, make your new trunk directory: svn mkdir trunk (actually type 'trunk' if that's what you want to call your trunk)
  6. Use a little ol' shell script to loop through the existing files, doing svn mv on each one: for f in *; do svn mv $f trunk; done
  7. Now, all of your files are actually svn deleted (scary) from the original location and svn added to the new place (trunk)
  8. You can safely create branches and tags dirs, now. Doing it before would have resulted in them being moved to trunk, I think: svn mkdir branches tags
  9. Now, commit your changes: svn commit -m "moved files into trunk; created branches and tags dirs"

Not too bad. It seems to have worked without a hitch. I used svn switch on my working repository so that I could continue working on the relocated trunk. (I'm using Subclipse, so I just did it in Eclipse.) Now all I have to do is go onto the test server and do an svn switch to get it pointing at the new repos there, and I should be done. Cross your fingers, and good luck!

What Mother never told you about SVN Branching and Merging « Design By Gravity

Another useful article about branching and merging back into the trunk with svn. I need to look at it more closely, but the digrams are very helpful!

What Mother never told you about SVN Branching and Merging « Design By Gravity

svn branching best practices (in practice) « snax

This article on branching in svn in well-written. I'm sure everyone has their differences with the techniques, even if only minor ones, but it's a great start:

svn branching best practices (in practice) « snax

Wednesday, August 17, 2011

Easy RSS feed reading with SimplePie // Corvid Works

Really good article to help get you started using SimplePie to parse RSS feeds with PHP:

Easy RSS feed reading with SimplePie // Corvid Works

Formatting Yii's CGridView Can be a Bit Tricky

First, an example:


<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'news-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'type',
'title',
array(
'name'=>'content',
'type'=>'ntext',
),
'release_date',
array(
'name'=>'external_link',
'type'=>'url',
),
array(
'type'=>'image',
'value'=>'$data->image->getImgSrc()',
),
array(
'class'=>'CButtonColumn',
),
),
)); ?>

This is part of an admin section for a site, so I've included Yii's CButtonColumn.

This is similar to my previous post on CDetailView. I'll try not to repeat myself.

Primary points of interest:

  • Individual models are referred to as $data. This is "automagically" done by Yii. It's the variable CGridView provides. You must use it if you want to access that model for anything fancy.
  • Most of the things I'm trying to display, here, are attributes belonging to the main model. I don't need to use $data to get the content, for example. All I have to do is include 'content' in the output. Because I wanted to format content using the ntext formatter, I had to use an array to tell CGridView to include 'content' and to format as 'ntext.' (See above. You'll get it.)
  • I have a method for getting the image source of a related Image model. I used eager loading to give myself access to $data->image, so now I can call the Image method, getImgSrc(). Important: the whole expression to be evaluated is surrounded by quotes! That's necessary. Even though I'm using single quotes, Yii will evaluate it rather than render it as a literal. Trust me.


That's it!

Formatting CDetailView

CFormatter is used when you're doing some custom formatting to a CDetailView (among other places). Here's the link to the Class Reference on the Yii site:

CFormatter | Class Reference | Yii Framework

Here's an example of using some of the formatting options:

widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
'id',
'type',
'title',
array(
'name'=>'content',
'type'=>'ntext',
),
array(
'name'=>'release_date',
'type'=>'text',
'value'=>date_format(new DateTime($model->release_date), 'm-d-Y'),
),
array(
'name'=>'external_link',
'type'=>'url',
),
array(
'label'=>'Image',
'type'=>'image',
'value'=>$model->image->getImgSrc(),
),
),
)); ?>

In this example, I've included a few fields - id, type, and title - in the straightforward, default, manner. These will be formatted as text, by default. But I wanted to include my carriage returns for the content, so instead of simply specifying my content attribute the same way I did 'type' or 'title' I have created an array of options. The first element specifies the name of one of this model's attributes, 'content.' I want the value of 'content' to be formatted as 'ntext.' This will convert line breaks to '
' tags for me. The attribute 'external_link' is formatted as a 'url.'

The model's image is a little trickier. The model I'm displaying with this CDetailView has a relation, 'image,' that joins records out of the images table with details about a particular image. But this main model, itself, only has a foreign key - image_id - to relate to the images table. It's not very interesting to see image_id, to tell you the truth, so I like to display the actual image using the HTML image tag. Doing this within CDetailView requires that I get the details for the Image model that is related to this particular model I'm displaying.

How do we do this? The first step is not to use the 'name' attribute as I have elsewhere; 'name' tells CDetailView to expect to display the attribute for this main model (which would render the image_id integer), not information from a relation (that is, data from the join part of the query). Instead, I use 'label.' This just tells CDetailView to use whatever value I provide for 'label' as the label when displaying the rest of the information. The type has been set to 'image,' which lets CDetailView know to format this using the tag.

Now, here's the crux of it. So far, we have a label and a tag, but we do not have a value to include. That value is important, because it ends up being used as the 'src' attribute within our image tag (). I happen to have a helper method for my Image model that gets the image source so that I can use it in image tags. It just creates a string of the relative path in the app for whatever image is being handled. Now, all I have to do for the value is get the related image for our main model and call the Image method, getImgSrc() on it. (You don't know this, because I didn't mention it, but I have direct access to the image relation because I used 'with' in my query when I loaded this particular model. No great details on this, here, but suffice to say that you can do this when you load your model: instead of simply doing ModelName::model()->findByPk($id), do ModelName::model()->with('somerelation')->findByPk($id). Google "eager loading Yii." Anyway, this is why I can do: $model->image->imageMethod().)

One other thing. Did you notice I used 'text' for my type for the date? There is an actual 'date' type, but I could not get it to work to my liking. The common workaround I've seen is simply to use standard PHP date formatting and just output the result as text. It's a real pain, IMO. If I figure it out, I'll post it.

Really Basic CJuiDatePicker Example


Here's a CJuiDatePicker example with the basic parameters and a couple of the optional JavaScript options. In particular, I like using the dateFormat option to format my date the way I want to save it in the db.


<?php
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
    'model'=>$model,
   'attribute'=>'release_date',
   // additional javascript options for the date picker plugin
   'options'=>array(
       'showAnim'=>'fold',
'dateFormat'=>'yy-mm-dd',
   ),
   'htmlOptions'=>array(
       'style'=>'height:20px;',
   ),
));
?>

Tuesday, August 16, 2011

PHP Tutorials Examples Filtering Data with PHP

A terrific tutorial on PHP's wild and wooly filter_var family. If you need to filter input (and you do), check it out.

PHP Tutorials Examples Filtering Data with PHP

Also, if you decide you want to include multiple flags for one of the filters, here's a guide to help:

How to add multiple options/flags to PHP filter_var()

Monday, August 15, 2011

Accessing View Files from Anywhere in the Yii Application

By default, view files are accessible using a "double slash" notation, like so:

'//directoryName/fileName'

where 'directoryName' refers to the ID of the view (and, incidentally, its corresponding controller) and 'fileName' is the actual file you wish to use within that directory. For example, if you have a controller called StoreController, it will most likely have a views directory called 'store' in which you might have index.php, update.php or whatever. To include the update.php page from another controller or another view, you can do this:

$this->render('//store/update');

Easy, right?

Tuesday, August 9, 2011

Learn Yii

Learn Yii

This is a great how-to site for Yii and other Web stuff.

What Are Websites Made Of? (Infographic)

What Are Websites Made Of? (Infographic)

Just a bit of fun. Enjoy! (Well, it's also informative.)

Fun with Google Calendar API (Sort of...)

I've been working with Google's Calendar API a bit, as of late, and I wanted to share a few hiccups I ran into in the hopes of avoiding them again, myself, and helping someone else. Some of this is certainly my own impatience with reading the (large amounts of "strangely" organized) documentation. I have a hard time imagining anyone sitting and reading this stuff from start to finish, so I end up Googling for help (how ironic). Here are some highlights:


  • When requesting a calendar feed, if you include "futureevents=true" in the URL, any start-max value you have in the string will be overridden. Sort of makes sense, but it seems arbitrary. In fact, if I go through the trouble of including a start-max, I would think that it should override futureevents (which, IMHO, is much easier to include accidentally on purpose, and has a fifty-fifty chance of being set to true). But whatever.
  • Requesting the "basic" version of a calendar will result in a very different response format. This sort of makes sense (since "basic" vs. "full" by definition defines the format). In particular, the start/end times are included in the "detail" part of the returned information, NOT in a "when" array, as they are with full. I thought this was strange since, regardless of how extensive the details are, I would expect the date/time information to have remained the same, whether I'm getting a lot of other details or not. (After all - isn't date and time what it's all about, anyway?)
  • This one didn't really trip me up, but I'll throw it in for good measure: setting singleevents=true in the query string returns individual event listings for each event, even if that event is recurring. In other words, you will get a unique event entry, for example, for every meeting on Tuesday that you can parse out individually. Otherwise, you get one mention of the Tuesday meeting with a bunch of dates/times attached to the one event. This is intuitive, really, but it makes a difference in dealing with a recurring event. Note that, in either case, the "when" data comes back as an array, so you still need to loop through it, even if it's an individual event with only one start and end date.
Here's a quick example of a feed request url:

https://www.google.com/calendar/feeds/atlanticstationatl%40gmail.com/public/full?alt=jsonc&start-min=2011-08-09T00:00:00&start-max=2011-08-30T00:00:00&orderby=starttime&sortorder=ascending&futureevents=false&singleevents=true

In it, I'm requesting a full calendar feed (so I get my "when" values) in the json-c format, with a start and end time as listed (note the standardized format it needs), ordered by the start time (ascending), with no future events (so that I don't override my end times), with recurring events treated as individual entries.

Good luck!

Monday, August 8, 2011

Yii CActiveDataProvider and a Limit Condition

Just discovered that, if you're trying to limit the number of results in a query using "limit" in your CActiveDataProvider configuration, you need to explicitly include 'pagination'=>false for it to work. For example:


$fCriteria = new CDbCriteria;
$fCriteria->with = array('textualContent','entity');
$fCriteria->limit = 2;



$fashionFindsDataProvider = new CActiveDataProvider('FashionFinds',
array(
'criteria'=>$fCriteria,
'pagination'=>false,
)
);

If you don't, limit does not take effect. Note that I'm using a CDbCriteria object, but you could also do this:


$fashionFindsDataProvider = new CActiveDataProvider('FashionFinds', 
array(
'criteria'=>array('limit'=>2), // Leaving out the other junk from above for easy reading
'pagination'=>false,
)
);



Abstract Data Types in Yii's Database Query Builder

I've been making use of Yii's database migration feature a lot, lately. I'm refactoring some things in my app, so I frequently have had to add columns. You can specify the explicit column type, or you can use the abstract types provided by Yii. I tend to use the abstract types, since it makes my life easier. (If you want greater control, you'll need to state them explicitly.) I won't go into detail about CDbMigration in this article. I'll save it for a later time. But I wanted to include the link to the abstract data types explanation in the Yii Class Reference, because I use it a lot, right now, and I keep having to remember where it is! So, here you go.

Abstract Data Types Used in Yii's Query Builder

P.S. If you want some help getting started with Yii's database migration feature, here's a great introduction by Dana Luther on her blog, Adventures in Programming:

Yii 1.1.6 and the beauty of yiic migrate

Friday, August 5, 2011

Avoiding Duplicate Records When Using Yii's With/Together Combo

Came across a situation in which a query with relations returns duplicate records. First of all, for those of you just getting started, you can retrieve a model's relations with the 'with' method. For example, say you have a model, Person, and Person has a relation, CellPhone. If you want to get Person's CellPhone, you can use with() to retrieve the related record(s) when you query for Person. For example, assuming you've created a relation for the Person model called 'cellPhone':

$model = Person::model()->with('cellPhone')->findAll()

This allows you to do things like this:

echo $model->first_name; // prints the first name
echo $model->cellPhone->os_type // prints information about the cell phone, like 'Android'

The issue, here, however, is that Yii will actually do two queries, one to get the Person and one to get the CellPhone. To get everything in one query (more efficient), use "together" like so:

$model=Person::model()->with(array('cellPhone'=>array('together'=>true)))->findAll()

The syntax is a little different, since I'm including a parameter, here, for the query, namely together. (You could do this with a CDbCriteria, but we'll save that for another time.) The main point is that this will execute the select query once to get the Person and any related CellPhone records.

Now that we've covered that, the gotcha I wanted to mention, here, is that, if your Person has many CellPhones, you're likely to end up with duplicate records when you perform the together version of the query. The solution I found was to use a 'group by' clause in my query, grouping by the primary key for Person. You could do something like this;

$model=Person::model()->with(array('cellPhone'=>array('together'=>true)))->findAll(array('group'=>'t.id'))

where the 't' in 't.id' refers to the main table being queried (e.g., the table for Person). For all queries, the main table will use the alias 't' and you can use 't' in your queries for disambiguation.

I hope this gives you some idea of the solution. There is probably a better one, but this one works for me, and it makes sense. You'll especially want to watch this if you're going to be paging through your records, because the duplicates will throw off the total count returned from the query, but Yii will only show the distinct rows! So, you end up with something like "Showing 2 through 12 of 38," which is weird enough, but even worse, you don't actually get to page through 38 records, because many of them are duplicates!

By the way, I don't actually use the syntax above to pass in the query criteria. I'll cover CDbCriteria in a later post.

Monday, August 1, 2011

Storing a NULL Value in a MySQL Date/Time Field

I had a table with a column of type 'time.' It's set up so that the values can be null, and the default is, well, NULL. The create code looks like this:


CREATE TABLE IF NOT EXISTS `tbl_event` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `start_date` date DEFAULT NULL,
  `end_date` date DEFAULT NULL,
  `start_time` time DEFAULT NULL,
  `end_time` time DEFAULT NULL,
  `title` varchar(255) DEFAULT NULL,
  `subheading` varchar(255) DEFAULT NULL,
  `external_link` varchar(255) DEFAULT NULL,
  `link_text` varchar(255) DEFAULT NULL,
  `description` text,
  `is_featured` tinyint(1) DEFAULT NULL,
  `image_id` int(11) DEFAULT NULL,
  `location` varchar(255) DEFAULT NULL,
  `event_type_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=17 ;

Pretty straighforward, right? Wrong! It turns out that MySQL is pretty persnickety about what you put in a time column. If it considers it "illegal," it will default to its version of an "empty" field which, in this case, is "00:00:00" for zero hours, zero minutes, and zero seconds. Unfortunately, this is not really nothing, it's midnight!

So, why wasn't it just defaulting to NULL, like I'd asked it to? Although midnight as a translation for an illegal insert is arguably a pretty poor choice, the problem wasn't entirely MySQL's fault. When I tried to directly insert NULL without going through Yii (okay - I cheated and checked the "null" box in PHPMyAdmin just to see if it would take the value), it worked. In other words, you can use a NULL value in there if you ask it nicely. Knowing that, I tried a variety of different ways in my Yii code, like setting the attribute to 'NULL' in single quotes ('NULL'), null with no quotes (null), and plain old empty string (''). None of these things worked. MySQL considered all of them to be illegal entries.

What, you may ask, is the right way to ask MySQL to insert NULL in the time field, particularly if you're depending on Yii to do the asking for you? The answer is to use Yii's CDbExpression. When you have a time-related field, and you want a NULL value to be inserted, do it like this:

$model->myTimeColumn = new CDbExpression('NULL')

It's that simple. True, it takes a little extra code to make sure that's what you want, actually do the setting, etc., But it's much better than inadvertently setting it to midnight!