Wednesday, August 11, 2010

Quick guide on setting up a CakePHP project (updated for 1.3)

This blog describes installing for CakePHP 1.3. It is an updated version of my previous guide.

1. Create database

First we create a database called cake13. Rename it as needed, but be sure to change it in the examples below.
user@host:~$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3890
Server version: 5.1.37-1ubuntu5.4 (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> create database cake13;
Query OK, 1 row affected (0.00 sec)

mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER
-> ON cake13.*
-> TO 'cake13user'@'localhost' IDENTIFIED BY 's3cr3t';
Query OK, 0 rows affected (0.00 sec)

mysql> exit
Bye

2. Download CakePHP

Next we download the latest CakePHP from http://github.com/cakephp/cakephp/archives/1.3. The version used in this example is 1.3.3 and the file cakephp-cakephp-1.3.3-43-gd1028a7.tar.gz is placed in the homedir.

3. Extract download

CakePHP will be installed in the ~/public_html/cake13 directory, which is served using mod_userdir. The directory will be available at the URL http://yourhost/~username/cake13 . Of course it is possible to install it to another location, but be sure to change the locations in the commands below accordingly.
Make sure mod_rewrite is working and that the AllowOverride property for ~/public_html is set to All.
user@host:~$ cd ~/public_html
user@host:~/public_html$ tar xvzf ~/cakephp-cakephp-1.3.3-43-gd1028a7.tar.gz
cakephp-cakephp-d1028a7/
..........
.  SNAP  .
..........
user@host:~/public_html$ mv cakephp-cakephp-d1028a7/ cake13
user@host:~/public_html$ cd cake13/

4. Configure the database

Rename the default database config.
user@host:~/public_html/cake13$ mv app/config/database.php.default app/config/database.php
Edit the file freshly renamed file and change the values of the database configuration '$default' to match those on your system (you should probably change the values for login, user, password).
user@host:~/public_html/cake13$ vi app/config/database.php

5. Edit the core configuration file

Open the file app/config/core.php and edit the settings of 'Security.salt' and 'Security.cipherSeed'  (I always search for the word 'salt'). Change the strings by entering a new line of random characters, or by changing the existing ones. Note that the value of Security.salt can be alphanumeric, while Security.cipherSeed just uses digits..

user@host:~/public_html/cake13$ vi app/config/core.php

Set Permissions

The tmp dir needs write permissions. Use chmod -R 777 only in development environments. In production environments the owner of the files need to be the user running the webserver.
user@host:~/public_html/cake13$ cd app/
user@host:~/public_html/cake13/app$ chmod -R 777 tmp/
user@host:~/public_html/cake13/app$ cd ..

The CakePHP installation should now ready and available.

Please use the comments if you have any questions or comments :)

Monday, February 8, 2010

CakePHP in Subversion: ignore the tmp dir

Here is the one-liner I use to let Subversion exclude my projects tmp dir, I fetched it somewhere from the net... I thought I'd share it with you :)

svn propset svn:ignore "*" tmp -R

You should run it from your app folder :)


Linking models in a CakePHP plugin

Last weekend I finally found the solution to a problem I was facing for a couple of days. I was trying to add data to a model in my plugin from the main app. The problem was that the data validation of that model did not work in the main app, while it worked from within the plugin.

After looking at this problem and asking on #cakephp on Freenode, the user rich97 eventually figured out what my problem was.

In my plugin I have two models which have a relationship, one for Articles and one for the Comments on that article. The problem was the definition of the relationships. I was refering to the correct className but without a refering to the plugin it is in, like this: 'className'=> 'SystemComment' while I ofcourse should mention the plugin too, like this: 'className'=> 'System.SystemComment' (with System being the name of my plugin).

Below you see the models with the correct className references.

Articles Model
<?php
class SystemArticle extends SystemAppModel {
   /* ... */
   var $hasMany = array(
       'SystemComment' => array(
           'className'     => 'System.SystemComment',
           'foreignKey'    => 'article_id',
           'dependent'=>true
       )
   );
}
?>
Comments Model
<?php
class SystemComment extends SystemAppModel {
   /* ... */
   var $validate = array(
       'email' => array(
           'rule' => array('email', true),
           'message' => 'Please supply a valid email address.'
       ),
       'comment' => array('notempty')
   );

   var $belongsTo = array(
       'SystemArticle' => array(
           'className' => 'System.SystemArticle',
           'foreignKey' => 'article_id'
       )
   );
}
?>
Thanks rich97! :)

Thursday, January 21, 2010

Cakestyle: Drop-in replacement for the default CakePHP stylesheet (cake.generic.css)

Cakestyle is a stylesheet that is designed to be used with the default CakePHP configuration.

Just place the updated stylesheet in app/webroot/css/ and off you go.

The stylesheet is found here.

Of course things are made more clear with some screenshots, so here we go:

This is the default CakePHP screen after installation, with this Cakestyle:

Compared to the default layout it looks way better, but i may be biased ;) :

If you have any additions or comments, they are more then welcome!


Thursday, November 19, 2009

Javascript Regular Expression to test the last character in string

Today my colleague Martin asked me to look over his shoulder at a small problem he was facing, he was building a Search function in his application but got stuck with certain input.

If the search string ended on a character like ")" or a space (and maybe other non-alphanumeric characters), the search function returned nothing, even though it should have some matches.

We decided that the easiest way to solve this would be to delete all the faulty characters from the end of the string. This had to be done in (Server-side) JavaScript.

I came up with the following function: (and check below for the updated version)

<script type="text/javascript">
function testString(input){
    var regex = /[a-zA-Z0-9]$/;
    if(input.match(regex)){
        var match = input;
    } else {
        var nomatch = input.substring(0,input.length-1);
        var match = testString(nomatch);
    }
    return match;
}
</script>

What it does is the following:

  • We first define our regular expression
  • If this matches our input string, we will just return it
  • If it does not match our input string, we strip the last character off the string, and test it again.

This recursive test will continue as long as the regex is not macthed.

To test this functionality you can run the function like this:

document.write(testString("This is a string that end with some special chars & $ !!! ) "));
Which will output: "This is a string that end with some special chars".

Maybe someone enjoys this code, so I thought I'll share it :)

Update as my other colleague Marcus points out, there is a way more elegant way to implement this, making it so that is does not need to recurse. The updated code is below. Thanks Marcus :)

<script type="text/javascript">
function testString(input){
    while (!input.match(/[a-zA-Z0-9]$/))  {
        input = input.substring(0, input.length-1);
    }
    return input;
}
</script>


Thursday, November 5, 2009

Easy trees with Tree Behavior in CakePHP

This tutorial shows how to use the Tree Behaviour in CakePHP.

The only requirement is a working CakePHP application. You can refer to this post to help you get one.

Step 1: Create the table in your database

First we need to create a table to store our tree. To keep things a bit generic we create a simple table called 'Nodes'. There are various ways to extend this table with other functionality or data, but that is beyond the scope of this tutorial.

Run the following SQL code to create a table called Nodes:

CREATE TABLE `nodes` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `parent_id` int(11) unsigned DEFAULT NULL,
  `lft` int(11) unsigned DEFAULT NULL,
  `rght` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

The Tree Behaviour needs at least the fields parent_id, lft and rght, and they should all use type int().

Step 2: Create the model

To use table in the application we have to create the model. In this case the file will be app/models/node.php.

<?php

class Node extends AppModel {
    var $name = 'Node';
    var $actsAs = array('Tree');
}

?>

The model Node is defined as any other model in CakePHP. With the line var $actsAs = array('Tree'); we tell CakePHP to use this model as a tree.

Step 3: Create the controller

The controllers task is to take care of handling the data (adding, showing, updating, deleting, and more). At first we will create a controller with 2 functions, one called 'index' to display the tree and the other called 'add' to add items to the tree. The controller will be app/controllers/nodes_controller.php.

<?php

class NodesController extends AppController {

    function index() {
        $nodelist = $this->Node->generatetreelist(null,null,null," - ");
        $this->set(compact('nodelist'));
    }

    function add() {
        if (!empty($this->data)) {
            $this->Node->save($this->data);
            $this->redirect(array('action'=>'index'));
        } else {
            $parents[0] = "[ No Parent ]";
            $nodelist = $this->Node->generatetreelist(null,null,null," - ");
            if($nodelist) {
                foreach ($nodelist as $key=>$value)
                    $parents[$key] = $value;
            }
            $this->set(compact('parents'));
        }
    }
}

?>

The index function is pretty straightforward, it uses the generatetreelist function to generate a formatted tree, and return it in an array.

The Add function is a bit more complex. It first checks if it received any data. If it did, it saves the data to the database and then redirects to the index again. If the add function did not receive any data, it will create the array needed to populate the 'Parent' select box in our Add screen (see below).

Step 4: Create the views

We now create 2 view files in the folder app/views/nodes/, which needs to be created first.

The file app/views/nodes/index.ctp is used to display the tree:

<?php

echo $html->link("Add Node",array('action'=>'add'));

echo "<ul>";
foreach($nodelist as $key=>$value){
    echo "<li>$value</li>";
}
echo "</ul>";

?>

In the above code we first create a link to our 'add' functionality. After that we start a new Unordered list and fill it with the data from the tree.

For adding new items to the tree we will use the file app/views/nodes/add.ctp

<?php

echo $html->link('Back',array('action'=>'index'));

echo $form->create('Node');
echo $form->input('name',array('label'=>'Name'));
echo $form->input('parent_id',array('label'=>'Parent'));
echo $form->end('Add');

?>

First there is a link back to the index. After that a new HTML Form of the type Node is create. It then add two fields, one for the Node name, the other to select the parent of the new node. The last function add the submit button with the text 'Add'.


You should now be able to show your tree, and add nodes to it. Actually you first need to add a node because there is nothing to display yet. In the next step we will add some more functionality to it, but this seems a good moment to check if it all works so far :) ...


Step 5: Adding more functionality

In this step we will add the posibility to edit and delete the nodes, as wel as move them up and down. We do this by adding some links to the Index page, add the edit view and add some functions to the controller.

First we update the file app/views/nodes/index.ctp so it looks like this:

<?php

echo $html->link("Add Node",array('action'=>'add'));

echo "<ul>";
foreach($nodelist as $key=>$value){
    $editurl = $html->link("Edit", array('action'=>'edit', $key));
    $upurl = $html->link("Up", array('action'=>'moveup', $key));
    $downurl = $html->link("Down", array('action'=>'movedown', $key));
    $deleteurl = $html->link("Delete", array('action'=>'delete', $key));
    echo "<li>[$editurl|$upurl|$downurl|$deleteurl] $value</li>";
}
echo "</ul>";

?>

The part that loops trough the nodelist is changed here. We define 4 URL's for the needed actions, and put them all in front of the Node name, seperated by a pipeline character. This is not the most elegant solution, but it will get the job done.

Next we add a view for the edit functionality by creating the file app/views/nodes/edit.ctp:

<?php

echo $html->link('Back',array('action'=>'index'));

echo $form->create('Node');
echo $form->hidden('id');
echo $form->input('name');
echo $form->input('parent_id', array('selected'=>$this->data['Node']['parent_id']));
echo $form->end('Update');

?>

This view is mostly the same as add.ctp. Two differences: the edit view needs a hidden field called 'id', and the parent_id selectbox has a 'selected' parameter, which selects the right parent when in Edit mode.

In the last step we add four functions (edit, delete, moveup, movedown) to the Controller, app/controllers/nodes_controller.php, so it will look like this:

<?php

class NodesController extends AppController {

    function index() {
        $nodelist = $this->Node->generatetreelist(null,null,null," - ");
        $this->set(compact('nodelist'));
    }

    function add() {
        if (!empty($this->data)) {
            $this->Node->save($this->data);
            $this->redirect(array('action'=>'index'));
        } else {
            $parents[0] = "[ No Parent ]";
            $nodelist = $this->Node->generatetreelist(null,null,null," - ");
            if($nodelist)
                foreach ($nodelist as $key=>$value)
                    $parents[$key] = $value;
            $this->set(compact('parents'));
        }
    }

    function edit($id=null) {
        if (!empty($this->data)) {
            if($this->Node->save($this->data)==false)
                $this->Session->setFlash('Error saving Node.');
            $this->redirect(array('action'=>'index'));
        } else {
            if($id==null) die("No ID received");
            $this->data = $this->Node->read(null, $id);
            $parents[0] = "[ No Parent ]";
            $nodelist = $this->Node->generatetreelist(null,null,null," - ");
            if($nodelist) 
                foreach ($nodelist as $key=>$value)
                    $parents[$key] = $value;
            $this->set(compact('parents'));
        }
    }

    function delete($id=null) {
        if($id==null)
            die("No ID received");
        $this->Node->id=$id;
        if($this->Node->delete()==false)
            $this->Session->setFlash('The Node could not be deleted.');
        $this->redirect(array('action'=>'index'));
    }

    function moveup($id=null) {
        if($id==null)
            die("No ID received");
        $this->Node->id=$id;
        if($this->Node->moveup()==false)
            $this->Session->setFlash('The Node could not be moved up.');
        $this->redirect(array('action'=>'index'));
    }

    function movedown($id=null) {
        if($id==null)
            die("No ID received");
        $this->Node->id=$id;
        if($this->Node->movedown()==false)
            $this->Session->setFlash('The Node could not be moved down.');
        $this->redirect(array('action'=>'index'));
    }
}

?>

The first part of the edit function works much like the add function, it checks on received data and tries to save it. When there is no data received, it checks if there is a paramater called ID. If not it dies with an error message. If the parameter is given it fetches the node data, and gets the data needed to populate the 'Parent' select box, just like in the Add screen

The rest of the three functions are almost identical, and don't need any matching views as the code will redirect to the index page anyway. All these functions check if the parameter ID is passed, and dies with an error if not. If the parameter is given it selects the right node and runs the action.

Done!

This should be it, you should now have a tree created in CakePHP, with complete Create, Read, Update and Delete functionality (and even more)...

Good luck using this code, and please use the comments if you have any questions about it.


Saturday, October 31, 2009

Variable in Android's strings.xml

Today I was wondering how to create a variable in strings.xml, a file used in Android application development.

It is actually very simple. :)

First define a string in the strings.xml file (usually res/values/strings.xml).

<string name="unread_messages">You have %d unread messages</string>

This string has a variable %d that will be replaced in the next step.

In the Java code the string is fetched with the getString method and the variable is replaced with the right content using Java's String formatter.

String message = String.format(getString(R.string.unread_messages), 10);

The output will be You have 10 unread messages... :)

Update: As Romain Guy points out in the comments it is even simpler, documented here:

String message = getString(R.string.unread_messages, 10);