I want objects, not arrays!

A few times I’ve heard people complaining about the fact that CakePHP’s find methods return data as arrays and not as objects. “It’s not OO!”, “I want access to Model methods!”, etc. etc. if you’ve followed cake for a while, you’ve probably heard the same.

It’s a bit of a debate as to why you’d want to have objects vs arrays, but I can tell you that most arguments I’ve read call for some breaking of MVC and often arise due to not really understanding the way CakePHP does certain things. Personally, I have nothing against working with arrays especially if my ultimate goal is to just display some data in the view. You can do a quick search at the google group to get views from both sides of the story, so I won’t repeat them here, but please keep in mind that it’s always a good idea to figure out how to use a framework (any framework) to accomplish what’s needed, rather than how to make it work the way you prefer.

So can you return objects from find methods, instead of arrays? Actually it’s very simple, here’s a little gem of code that was posted at the google group, by gwoo:

	function afterFind($results, $primary = false) {
	    if($primary == true && !is_object($results)) {
	        return Set::map($results);
	    }
	}

Just to clarify, this is supposed to be added to your app model, so all of the results returned by your find methods, will be automatically “objectified”.

Obviously you’d probably want to expand on that a little, but even with this simple addition you can easily get access to the much desired objects. Just remember to keep in mind that if you do use this approach, it should not be an excuse to break MVC and other conventions.

Incrementing a field in CakePHP

Let’s say you have an application, where users can place votes for their favorite products and you’d like to increment the current number of votes by one.

It’s very easy by using updateAll:

$this->Product->updateAll(array('Product.vote'=>'Product.vote+1'), array('Product.id'=>40));

You may want to restrict Product model by using unbindModel, containable, etc.

Also, you don’t have to pass in the second argument to updateAll, if you wish to update all records in your table.

Remember $this!

Do you need to get some information about your current state of the cake app?… Maybe find out the name of the current layout? Or see what helpers are currently loaded? Need session time?… etc., etc.

$this contains a lot of usefull information.

Just try it in your beforeRender() in app controller:

pr($this);

Good stuff…

Build a string from a resultset array

Sometimes it’s very handy to be able to quickly build a string from CakePHP’s resultset array. For example you want to build a string of ID’s to be used with the “IN” condition in your DB (such as Profile.id IN(1,2,3,4)… ).

Let’s say we did a find(‘all’… and got an array similar to this one:

Array
(
    [0] => Array
        (
            [Account] => Array
                (
                    [id] => 23
                )

        )

    [1] => Array
        (
            [Account] => Array
                (
                    [id] => 24
                )

        )

    [2] => Array
        (
            [Account] => Array
                (
                    [id] => 25
                )

        )

)

Our goal is to extract the ID’s from the above array into 23,24,25.

Here we go (assuming that $acctIds is our resultset array from above):

$stringOfIds = implode(',', Set::extract($acctIds, '{n}.Account.id'));

Cake’s Set class is full of magic and here we are using the extract method to get just the values we are interested in, then we implode the resulting array with a comma.

Note the {n} above. It simply represents a numeric key and therefore allows us to easily follow the path to the desired value in the array.

 

I need the first record from my table…

Let’s say you need to know when was the very first user record created.
Add a method like this to your User model (assuming you have a ‘created’ field):

function getFirstDay() {
     return $this->find('first', array(
		            'fields' => array('created'),
			   'order' => 'User.created ASC'));
}

Try in the controller: pr($this->User->getFirstDay());

Similar method can be used to get information about any other arbitrary field.

Array from XML in CakePHP

Update (9/9/2008): Since the writing of this post, there is now a nicer way to handle this, which David Persson kindly pointed out:

$Xml = new Xml($rawXML);
$Xml->toArray();

For historical reference and if you don’t have the latest and greatest CakePHP core, here’s the “old method”…

On IRC gwoo posted a neat trick to convert an XML string into an array.
I thought that it definitely deserves to be shared with everyone…

Behold:
$myArray = Set::reverse(new Xml(… your XML here…));

Pretty cool.

P.S. Be sure to include: uses(‘Xml’);

amCharts with CakePHP

There is a very slick flash charting tool out there called amCharts (www.amcharts.com). If you are in need of some reporting or charting UI for your application, I strongly recommend it.

This is a quick guide on how to get it working with CakePHP.

For this example, I’ll show you how to get the amLine setup, but the rest (amPie, amColumn, etc.) are pretty similar.

1. First create a directory in your webroot called ‘amcharts’.
2. Next, download the zip with Line and Area chart.
3. There should be two folders in the archive (amline and examples).
4. Extract the following files into your ‘amcharts’ directory:
– amline.swf
– amline_settings.xml
5. The rest you really don’t need to worry about for right now.
6. Also, extract the ‘plugins’ and ‘fonts’ folders right into your ‘amcharts’ directory.

So basically you should have something like this:

amcharts
|- fonts (directory with fonts)
|- plugins (directory with value_indicator.swf)
|
|- amline.swf
|- amline_settings.xml

Now create an empty file called amcharts_key.txt and place it in your ‘amcharts’ directory (it will save you some headache in the future). This file is required to hold the key if you purchsase a license for your charts (otherwise you’ll see a tiny text along the lines of ‘chart by amcharts.com’), without this file present I ran into all sorts of silly problems, the cause of which wasn’t very obvious.

Lastly, extract the swfobject.js file into your ‘js’ directory (in your webroot, where you keep all your JavaScript includes).

Let’s say that you already have a model and a controller setup (if not, you shoud create some for testing).
Let’s create a view and call it chart.ctp. You will need to include the swfobject.js, so at the top of view do:

 <?php $javascript->link('swfobject', false); ?> 

Now, in the view you will need to include the amCharts code, here’s a sample:

<!-- amline script-->
	  
	        <div id="flashcontent">
	                <strong>You need to upgrade your Flash Player</strong>
	        </div>
	
	        <script type="text/javascript">
	                // <!&#91;CDATA&#91;                
	                var so = new SWFObject("/amcharts/amline.swf", "amline", "450", "250", "8", "#ffffff");
	                so.addVariable("path", "/amcharts/");
	                so.addVariable("settings_file", escape("/amcharts/amline_settings.xml?<?php echo microtime();?>"));
	                so.addVariable("chart_data", "<chart><series><?php echo $seriesXML; ?></series><graphs><graph gid='0' title=''><?php echo $valuesXML; ?></graph></graphs></chart>");
	                so.addVariable("preloader_color", "#FFFFFF");
	                so.write("flashcontent");
	                // &#93;&#93;>
	        </script>
	<!-- end of amline script -->

Couple of things to note here… You’ll notice the echo microtime() after the amline_settings.xml, this little trick will ensure that whenever you update the settings file, amCharts will use a fresh copy (rather than the one you have in cache, leaving your wondering why none of your changes are taking effect).

Then you’ve got $seriesXML and $valuesXML variables. This is actual data to be displayed on the chart. Of course, these variables will have to come from your controller as XML strings.

So, what kind of data are we going to display on this chart? Well, for the sake of example let’s say we will display the date on X-axis (our series) and the number of log-ins into your application on the y-axis (our values).

You did your ‘find’ in the controller and now you have an array that looks something like this:

[0] => Array
(
[thedate] => Apr 4, 2008
[count] => 375
)

[1] => Array
(
[thedate] => Apr 5, 2008
[count] => 412
)

[2] => Array
(
[thedate] => Apr 6, 2008
[count] => 407
)

[3] => Array
(
[thedate] => Apr 7, 2008
[count] => 415
)

… and so on…

You will now need to step through this array and convert it to simple XML strings, then pass the resulting strings to the view as $seriesXML and $valuesXML. (Remember those variables in the view you’ve setup earlier?)

Setup this function in the controller:

 
function __buildXMLstring ($data, $type) { 
 $counter = 0; 
 $xmlString = ''; 

 foreach($data as $key => $value) { 
   $xmlString .= '<value xid=\''.$counter.'\'>'.$value[$type].'</value>'; 
   $counter++; 
 } 

 return $xmlString; 
} 

So you’ve already used the ‘find’ method and got the data (in the format as the array above) stored in the $myData array variable.

$this->set(‘seriesXML’,$this->__buildXMLstring($myData, ‘thedate’));
$this->set(‘valuesXML’,$this->__buildXMLstring($myData, ‘count’));

Note, that as a second parameter you are passing the array key, so that correct values will be used for both axis of your graph.

This will generate the appropriate XML for your data array that should now display nicely in your chart.

Note, that if you are displaying huge amounts of data, it might be best to write it to a file instead of passing it to the view. Anything passed to the view will be rendered by the browser and if you’ve got thousands of rows of data it could cause some serious problems with performance.

There are a lot great features in amCharts, but you’ll have to dig around the documentation to get the full benefits. Good news is that it’s relatively easy to follow once you get the basics down.

Good luck!

CakePHP and custom SQL

If you’ve been around CakePHP for some time, you’ve probably heard that writing custom SQL is pretty much frowned upon. At the core, CakePHP provides some clever ways to write queries by employing the find() and save() methods, yet in some cases it is just impossible to use find() orsave() to get Cake to build the query you need.

So what happens then and what is the big deal about custom SQL?

Well, first remember that cake’s goal is to make your life easier. Therefore find()/save() methods do a lot more than just build the queries for you and therefore save you some typing. Remember the following benefits of using CakePHP’s find()/save() methods:

  • beforeFind()/afterFind() and beforeSave()/afterSave() methods
  • CakePHP will make your data safe for insertion and generally will sanitize your SQL
  • Model recursivness

Having CakePHP take care of all of the above, is not only life-saving at times (such as safe SQL) and convenient (getting associated model data), but it also allows you to write clean, robust and easily manageable code and it promotes good coding practice. You can forget about all that when writing custom SQL by using Model->query().

Well, if you’ve spent at least a few hours banging your head on the wall and simply cannot find a way to build your query in a cake-like manner, then be mindful of what you are doing with the SQL and always remember to keep your data safe.

And to wrap it up, don’t forget that as a rule of thumb… all custom SQL should be in the model.

 

Redirecting to the home page

Sometimes you’d like to create a link that will redirect your traffic to the homepage. For example, you have some partner web site that will drive traffic to a URL like: http://www.yoursite.com/partnerName. This way you’ll be able to track this link in the web site logs.

Just add the following line to your routes.php file:

 Router::connect('/partnerName', array('controller' => 'pages', 'action' => 'display', 'home'));