How to use the official CakePHP test suite

CakePHP : Posted by cakebaker at March 23, 2007 02:55 PM

If you are using CakePHP 1.2 you probably noticed the folders /app/tests and /cake/tests. They are part of the official CakePHP test suite which comes with Cake.

Before you can use it, you have to download SimpleTest, the testing framework used by the test suite. Extract the package to your vendors folder and you are ready to execute tests. (If you have an advanced setup you also have to change the value of CAKE_CORE_INCLUDE_PATH in app/webroot/test.php)

To execute tests you have to go to example.com/test.php and you will see the following page:

Testsuite

There you can select whether you want to execute the core tests or your own application-specific tests. These application-specific tests must be placed in the respective folders in app/tests/cases resp. in app/tests/groups if they are group tests. The file names of the tests must end with “.test.php” (e.g. “user.test.php” for the user model test) respectively “.group.php” (e.g. “helpers.group.php” for a group test of all helpers). It’s imho a bit illogical, as everywhere else in the framework an underscore is used to separate the name parts. As usual in Cake, the class names are camel-cased, so the class names will be “UserTest” and “HelpersGroup”. Apart from those conventions the tests are “normal” SimpleTest tests, there is no magic (yet). You can find many real-world examples of such tests in /cake/tests.

Personally, I still prefer my own test suite, even though it requires a bit more configuration work.

Happy testing :)

Criki - the creation of a wiki with CakePHP, Part 3

CakePHP : Posted by cakebaker at March 21, 2007 10:15 AM

On IBM’s developerWorks the third part of the series “Create an interactive production wiki with PHP” has been published. This part deals with file uploads and access control (no, it doesn’t cover ACL).

Read the article online (registration needed) or download it as a PDF.

Previous parts:
Part 1 as PDF
Part 2 as PDF

PS1: CraZyLeGs’ blog has a new virtual home: http://www.devmoz.com/blog.
Ps2: I did a little redesign on my blog and switched back to a two-column layout. I hope it is now I bit less cluttered ;-)

othy moves to devmoz !

CakePHP : Posted by othy at March 21, 2007 03:58 AM

Alright,

This blog will move moves here http://www.devmoz.com/blog/ so please update your bookmarks and feedreaders (Sorry for the inconvenience ). This will probably be the last post here, so see you there :)

“Model/field” has been deprecated

CakePHP : Posted by cakebaker at March 19, 2007 05:51 PM

Up to now if you used the HTML or the form helper, you did it in the following way:

$form->input('User/password');

With changeset 4630 this approach has been deprecated in CakePHP 1.2 (but it will still work, of course). The new approach uses a dot notation:

$form->input('User.password');

MVC with Javascript

CakePHP : Posted by cakebaker at March 17, 2007 02:38 PM

Javascript was one of those things I tried to avoid up to now. I don’t know why, but somehow I disliked it (probably because it was a pain to use it in the early days of Javascript) ;-) Sure, I used a bit of Ajax here and there, but thanks to the CakePHP Ajax helper I didn’t had to touch the Javascript.

Lately, I decided to do an Ajax application and so I had to learn Javascript. To make my life easier, I decided to use the JQuery framework. I have chosen JQuery over Prototype because I heard many good things about JQuery, and the API documentation was (and still is) more intuitive.

Of course, my first attempt ended in a total mess. The result looked more like spaghetti than code ;-) That’s when I recalled an article (in German) about a Javascript MVC framework called Jamal, developed by Timo Derstappen. From the Jamal website:

The MVC concept is easy to adopt for javascript

  • Controller: Interaction with the user interface (events)
  • Model: Business Logic and AJAX calls
  • View: DOM, CSS modifications

That makes sense. So I had a closer look at Jamal. It looked nice, but it bothered me that you would have to use a CSS class “jamal” in the HTML code to make it work. So I wrote my own implementation called “jscake” which avoids that. You can find the first version in the downloads section.

Let’s create a simple “Hello world” example with jscake. Our example (CakePHP) view will contain a link and a div to show the messages:

<a href="">click</a>
<div id="messages"></div>

First we create our model. All models are in the “$m” namespace (please correct me if that is the wrong term), which is a shortcut for “jscake.models”. So our model is called “$m.Example”. The model contains one function to return the text which should be displayed:

// app/webroot/js/models/example.js
$m.Example = {

    getText: function() {
        return "hello world";
    }
};

The view is similar to the model, it contains only one function, too. In the function the element with the id “messages” is located and the text appended to that element. Similar to the models the views are in the “$v” namespace.

// app/webroot/js/views/examples.js
$v.Examples = {

    showMessage: function(message) {
        $('#messages').append(message);
    }
};

Last, but not least, we have to create the controller. The index function is automatically called when the JQuery ready event occurs. In this function we simply say that when someone clicks on the link, the other function of the controller (sayHelloWorld) should be executed.

//app/webroot/js/controllers/examples_controller.js
$c.ExamplesController = {

    index: function() {
        $('a').click(this.sayHelloWorld);
    },

    sayHelloWorld: function() {
        $v.Examples.showMessage($m.Example.getText());

        return false;
    }
};

To make our example work we have to include all needed Javascript files in our view, with CakePHP 1.2 it looks like:

<?php $javascript->link(array('jquery-1.1.2', 'jscake', 'controllers/examples_controller',
'models/example', 'views/examples', 'start'), false); ?>
<a href="">click</a>
<div id="messages"></div>

Have fun with jscake! Feedback is welcome :)

BaseUrl finding, Simple event broadcasting and element sorting in JS

CakePHP : Posted by Felix Geisendörfer at March 15, 2007 02:52 PM

Hey folks,

it's been a while since my last post on JS (and in general) and I apologize for that. There is just way too much stuff for me to do right now (client work, my own web app, school finals, etc.) but most of that will be over at some point and then I'll have a thousand interesting things to blog about as well as the time for it. Meanwhile here comes some fun JS stuff I'm using in the web app I'm working on right now and I hope you'll enjoy it. When you see the '$' sign in the code then it's the jQuery library I'm absolutely in love with and not the evil prototype one ; ). All code I'll post now can be written without it, but I don't have the time to present alternative solutions right now.

Alright let's get started. First of all I advocate the usage of a namespace for the functions I'll present since that makes it less likely to run into conflicts with 3rd party scripts as well as allowing for the code base to stay more maintainable. I'm assuming you are using CakePHP so please create a file called 'common.js' in /app/webroot/js/. The other file you put in there is the latest 'jquery.js' that you downloaded from jquery.com. In your html layout you include jquery.js first and common.js second.

The first thing I got for you is a function capable of determining the base url where your application is installed. This is very useful when doing lot's of ajax requests and you want to be able to always use '/controller/action' style url references like you do in CakePHP internally. It's also nice to know image files will always be located in '/img/...'. The way I do it is inspired by the script.aculo.us lib and looks like this:

JavaScript:
  1. var Common =
  2. {
  3.     baseUrl: null
  4.    
  5.     , setBaseUrl: function(url)
  6.     {
  7.         Common.baseUrl = url || $('script[@src$=js/common.js]')
  8.             .attr('src')
  9.             .replace(/js\/common.js$/,'');
  10.     }
  11.    
  12.     , url: function(url)
  13.     {
  14.         return this.baseUrl+url.replace(/^\/+/, '');
  15.     }
  16. }
  17.  
  18. Common.setBaseUrl();

The code should be pretty straight forward. After defining the Common object (used as a namespace) we call it's setBaseUrl function without parameters. This causes a jQuery selector to find the script element of our common.js file and use it's src attribute to determine the baseUrl. From that point on we can do calls like this in our other JS files:

JavaScript:
  1. $.getJSON(Common.url('/tasks/view/1.json'), function(Task)
  2. {
  3.     alert('It is time to "'+Task.name+'"!');
  4. })

Alright this is useful (at least I hope so), but there is more stuff to come. One thing I found myself working a lot with was collections of objects of the same type that need to exchange messages through events with one another. For example you have a class called 'Task' and all of them are managed by another object called 'TaskList'. Now let's say the User is able to click on each Task in your TaskList which causes this particular Task to get the focus (a couple of DOM elements getting an 'active' class). When one Task get's focused, naturally all other tasks need to loose their focus. Here is some code similar to what I'm using in my application right now which will make this become very easy and fail-safe:

A new generic broadcastEvent function for our Common namespace. It basically loops through all listeners (array of JS objects) and sees if they have listeners for the particular task defined and call those up. It also set's the context of the listener function to the object that is listening and uses whatever additional parameters are passed to broadcastEvent for calling it:

JavaScript:
  1. var Common =
  2. {
  3.     broadcastEvent: function(listeners, event)
  4.     {
  5.         var params = arguments;
  6.        
  7.         $.each(listeners, function()
  8.         {
  9.             if (typeof this.events[event] == 'function')
  10.             {
  11.                 this.events[event].apply(this, [].slice.call(params, 2));
  12.             }
  13.         });
  14.     }
  15. }

And here comes the TaskList that object that manages all Task's on the screen. It basically uses a table (element) as it's construct parameter, loops through all rows with a 'td' in them (those that are not part of the table header) and passes the table row elements as an initialization parameter to the Task object that is created for each one of it and added to a list of tasks. TaskList.broadcastEvent uses Common.broadcastEvent to allow the tasks inside the TaskList to communicate with one another easily which you'll see in the Task class.

JavaScript:
  1. var TaskList = function(element)
  2. {
  3.     this.construct(element);
  4. };
  5.  
  6. TaskList.prototype =
  7. {
  8.     element: null
  9.     , tasks: []
  10.  
  11.     , construct: function(element)
  12.     {   
  13.         this.element = $(element)[0];
  14.        
  15.         var self = this;
  16.        
  17.         $('tr[td]', this.element).each(function()
  18.         {
  19.             self.add(new Task(self, this));
  20.         });
  21.     }
  22.    
  23.     , add: function(TaskObject)
  24.     {
  25.         this.tasks.push(TaskObject);
  26.     }
  27.    
  28.     , broadcastEvent: function(event)
  29.     {
  30.         var params = [this.tasks, event];
  31.         params.push.apply(params, [].slice.call(arguments, 1));
  32.        
  33.         Common.broadcastEvent.apply(null, params);
  34.     }
  35. };

And here comes the Task class that triggers the event broadcasting and listens to events. It's constructor takes two arguments. The first one is a reference to the parent TaskList object and the second on is the table row (tr) element that hold the DOM representation of this Task. When the name of the task ('a.name') is clicked then it causes that Task to get the focus and all others to be blured (as they receive the focus event and react on it):

JavaScript:
  1. var Task = function(parent, element)
  2. {
  3.     this.construct(parent, element);
  4. };
  5.  
  6. Task.prototype =
  7. {
  8.     id: null
  9.     , parent: null
  10.    
  11.     , construct: function(parent, element)
  12.     {
  13.         this.parent = parent;
  14.         this.element = element;
  15.        
  16.         this.bindEvents();
  17.     }
  18.    
  19.     , bindEvents: function()
  20.     {
  21.         var self = this;
  22.        
  23.         $('td.task a.name', this.element).bind('click', function()
  24.         {
  25.             self.focus();
  26.             return false;
  27.         });
  28.     }
  29.    
  30.     , focus: function()
  31.     {
  32.         $('a.name, a.time', this.element)
  33.             .removeClass('active')
  34.             .addClass('active');
  35.            
  36.         this.parent.broadcastEvent('focus', this);
  37.     }
  38.    
  39.     , blur: function()
  40.     {
  41.         $('a.name, a.time', this.element)
  42.             .removeClass('active');
  43.     }
  44.        
  45.     , events:
  46.     {       
  47.         focus: function(Task)
  48.         {
  49.             if (Task !== this)
  50.             {
  51.                 this.blur();
  52.             }
  53.         }
  54.     }
  55. };

Alright that's it. The code above is of course just meant for educational purposes and not the complete code I'm using in my web app which is a little more complex and would only distract from demonstrating the Common.broadcastEvent function. So if it doesn't run it's probably because I didn't test the simplified version presented here.

So let's come to my last little trick, sorting elements in JS. Javascript has a very cool function build into the Array class named sort that is very useful for doing all kinds of sorting as it allows you to define a callback for comparing the array elements yourself. It basically works like this:

The following code uses jQuery to get all list items inside of a unorder list and then sorts the array using the anchor text (for simplicity, innerHTML).

JavaScript:
  1. var elements = $('ul#my-list li').get();
  2.  
  3. elements.sort(sortByName);
  4.  
  5. function sortByName(a, b)
  6. {
  7.     if (a.innerHTML <b.innerHTML)
  8.     {
  9.         return -1
  10.     }
  11.    
  12.     if(a.innerHTML> b.innerHTML)
  13.     {
  14.         return 1
  15.     }
  16.    
  17.     return 0
  18. }

But since we don't only want to sort them inside the array but also in the DOM, we need to get a little more tricky. In the simplest case this means to remove all li elements from our unordered list before sorting and them put them back into our list after we sorted them:

JavaScript:
  1. var elements = $($('ul#my-list li')
  2.     .remove()
  3.     .get()
  4.     .sort(sortByName))
  5.     .appendTo('#my-list');

If you don't like the train-wreck style you'd write the above like this:

JavaScript:
  1. var elements = $('ul#my-list li').remove().get();
  2. elements.sort(sortByName)
  3. $(elements).appendTo('#my-list');

So of course if you work with large lists of items you might want to actually swap elements using the Array.sort callback instead of re-populating the entire list, but for this example I wanted to keep it simple. Also here is a live demo where you can check out how the method presented here works on a small list. You basically can not tell that all elements where removed and then added again.

Alright, I hope you find some of the above stuff useful and if you have any questions feel free to ask them in the comments.

-- Felixd

Referencing CSS files

CakePHP : Posted by cakebaker at March 14, 2007 10:42 AM

Some time ago I wrote a post titled “Referencing Javascript files” in which I explained how you can reference Javascript files.

Now I noticed that the same approach also can be used for CSS files. For this purpose an additional parameter was added to the css() function in the HtmlHelper in CakePHP 1.2. So to load a page-specific CSS file you can add the following code to your view (notice the fourth parameter):

$html->css('page_specific', null, null, false);

Or if you want to load multiple CSS files:

$html->css(array('first', 'second'), null, null, false);

To make those snippets work you must have the variable $scripts_for_layout in your layout:

<head>
    <?php echo $scripts_for_layout; ?>
</head>

It is the same variable used in the approach to reference Javascript files.

Formats for the Selenium IDE

CakePHP : Posted by cakebaker at March 11, 2007 10:42 AM

The Selenium IDE (a plug-in for Firefox) allows you to record tests. A guy called “wishcow” published now a format which makes it possible to export those tests for the Selenium helper. Based on his format I wrote a format for creating test cases for the Selenium test suite. You can find it (plus the helper and the test suite) in the downloads section,

The installation is simple (you can find a graphical installation guide on “wishcow”’s blog):

  • Open the Selenium IDE
  • Select “Options”/”Options…”
  • Switch to “Formats”
  • Choose “Add”
  • Define a name and copy&paste the format in the text area
  • Click on “Ok” to save it

Happy testing :)

Expandable links

CakePHP : Posted by cakebaker at March 10, 2007 03:30 PM

Even though I am not a design/usability specialist I will discuss the design process for a rather minor detail in an application.

I have one of those lists you will find in almost every application which lists the items the user entered. Now it should be possible to move these items to two different categories. How could this be solved?

The easiest solution would be to add two links to the end of each row. But how do you name those links? “Move to Category A” and “Move to Category B”? Hm, that takes too much space and it looks ugly if you have to repeat it on all rows.

The next idea is to use icons instead of text. But this causes a similar problem as above. How should the icons look so people understand them?

Another idea is to put a checkbox in front of each row and to have a select box with the possible actions at the top and/or the bottom of the list. The problem here is that the user has to click at least three times to move an item.

What about a drag and drop solution? Well, drag and drop is not very intuitive, and it is difficult to visualize that an object is draggable.

Maybe we could add a select box to the end of each row? Well, it is still two clicks for the user. Can we reduce it somehow to a one-click action?

We could add a link to the end of each row and then on a mouse over event we show the possible actions. So with only one click the user can move a list item to the desired category. With that I have found the solution for the problem described at the beginning of this post. Here the visualization of this idea which I called “expandable link”:

Closed link
Expanded link

The first image shows simply a link, and the second shows what happens on a mouse over event on this link.

Up to now I didn’t test this solution in the wild so I cannot say how it will be accepted by the users, but if they are like me, they will find it cool ;-)

[blog] Time for a diet...

CakePHP : Posted on Fabio Cevasco (h3rald) at March 09, 2007 06:59 PM

My fiancandeacute;e keeps telling me that too many cakes are not good for me, and I never listen: I always liked cakes! I did like the CakePHP™1 framework too, once, and I did write some articles about it in the past, and I believe at least a bunch of Bakers found them useful, especially at the time. I do believe the Cake™ Software Foundation1 quite liked having their framework featured on popular websites like php|architect and SitePoint, and I believe that I contributed - to some extent - to make it one of the most popular frameworks available for the PHP programming language. Unfortunately though someone decided that two of such articles and my personal website were no longer worth a mention on CakePHP official website frontpage. […]

[blog] Too many cooks spoil the Cake book

CakePHP : Posted on Fabio Cevasco (h3rald) at March 07, 2007 09:45 AM

I am sorry to announce that my upcoming book, CakePHP Recipes, will not be published anymore. As a matter of fact, it wasn’t finished because some of the people involved failed to comply with the terms of their contract in delivering material which was suitable for publication. To quote an email I received from my publisher a few days ago, _”[…] The Cake Software Foundation has informed us they are withdrawing from the CakePHP Recipes project, and returning the advances they’ve received. […]

Criki - the creation of a wiki with CakePHP, Part 2

CakePHP : Posted by cakebaker at March 07, 2007 09:01 AM

On IBM’s developerWorks the second part of the series “Create an interactive production wiki using PHP” has been published. It deals with user registration and custom markup rendering.

I am not so happy with this part. First, it uses functions of the HTML helper which are deprecated since version 0.9.2 of CakePHP, like passwordTag() and submitTag() (you should use password() resp. submit() instead). And second, I think the rendering of the markup shouldn’t be done in the controller. It should be done in a component or a helper (depends on whether you look at it as business logic or presentation logic). The advantages:

  • the code would be easier to test
  • the controller would be easier to read
  • the markup rendering could be reused

Anyway, here the links to the article: article (registration needed), PDF

Part 1

Baking a Cake - From the domain model to Cake models

CakePHP : Posted by cakebaker at March 04, 2007 04:53 PM


Warning: Division by zero in /home/.funtime/dhofstet/cakebaker.42dh.com/wp-content/plugins/tla_83727.php on line 407

Do you remember the domain model from a previous post? No? No problem, here it is again:

Domain model

The next logical step on the way from the domain model to the Cake models would be to refine the domain model to a database diagram. I say “would be” as it is something I almost never do, especially not for small projects. For me it isn’t worth the additional effort as a database diagram doesn’t add much value if you know some basic rules (Cake conventions). And well, I don’t have found a free tool for this task which convinced me ;-)

So I will go on and create the tables manually. For this purpose I will create a create.sql file in app/config/sql.

The first rule is that table names are plural. So we can write the SQL statement for the table for the “User” class (I omit the other classes as it is the same for them):

CREATE TABLE users (
);

The second rule is that each table has a primary key field with the name “id”. So we can modify the script from above to:

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (id)
);

The third rule is optional, but you can add the fields “created” and “modified” to the table, which are later automatically populated by CakePHP:

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

The fourth rule affects 1:n relations: The “n” table must contain a field named “singular name of ‘1′ table” + “_id”. In our domain model the relation between User and Project is such a 1:n relation: one User can have many Projects. So we have to add the field “user_id” to the “projects” table:

CREATE TABLE projects (
    id INT(11) NOT NULL AUTO_INCREMENT,
    user_id INT(11) NOT NULL,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

The fifth rule affects n:n relations. Such relations cannot be mapped directly to the database, we have to introduce a join table. The name of this join table is “plural name of first ‘n’ table” + “_” + “plural name of second ‘n’ table” whereby the names are sorted alphabetically. In our domain model we have only one n:n relation, between User and Project. So the table name is “projects_users”. We also have to apply the fourth rule, as the relations between “projects” and “projects_users” resp. between “users” and “projects_users” are 1:n relations. And so we get:

CREATE TABLE projects_users (
    project_id INT(11) NOT NULL,
    user_id INT(11) NOT NULL
);

Now we can add the attributes from the conceptual classes as fields to the respective tables and write the drop statements. And we get:

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT,
    created DATETIME,
	modified DATETIME,
    PRIMARY KEY (id)
);

CREATE TABLE projects (
    id INT(11) NOT NULL AUTO_INCREMENT,
    user_id INT(11) NOT NULL,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

CREATE TABLE messages (
    id INT(11) NOT NULL AUTO_INCREMENT,
    project_id INT(11) NOT NULL,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

CREATE TABLE feeds (
    id INT(11) NOT NULL AUTO_INCREMENT,
    user_id INT(11) NOT NULL,
    url VARCHAR(255) NOT NULL,
    created DATETIME,
    modified DATETIME,
    PRIMARY KEY (id)
);

CREATE TABLE projects_users (
    project_id INT(11) NOT NULL,
    user_id INT(11) NOT NULL
);

And in app/config/sql/drop.sql:

DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS projects;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS feeds;
DROP TABLE IF EXISTS projects_users;

To execute these SQL scripts I use a simple Ant script (app/config/sql/build.xml):

<?xml version="1.0"?>
<project name="n" default="default">
    <property name="driver" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost/n?characterEncoding=UTF-8"></property>
    <property name="user" value="root"></property>
    <property name="password" value=""></property>

    <target name="default">
        <sql driver="${driver}" password="${password}" url="${url}" userid="${user}" src="drop.sql" encoding="UTF-8"></sql>
        <sql driver="${driver}" password="${password}" url="${url}" userid="${user}" src="create.sql" encoding="UTF-8"></sql>
    </target>
</project>

After executing the Ant script, we are ready to bake the models. I will use the bake script (available in cake/scripts) to generate the User model, as I was asked to show how bake.php is used. I think it should be self-explanatory:

Baking model, part 1

Baking model, part 2

This will create the following model in app/models/user.php:

<?php
class User extends AppModel {
    var $name = 'User';

    //The Associations below have been created with all possible keys, those that are not needed can be removed
    var $hasMany = array(
        'Feed' => array('className' => 'Feed',
            'foreignKey' => 'user_id',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'dependent' => '',
            'exclusive' => '',
            'finderQuery' => '',
            'counterQuery' => ''),
        'Project' => array('className' => 'Project',
            'foreignKey' => 'user_id',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'dependent' => '',
            'exclusive' => '',
            'finderQuery' => '',
            'counterQuery' => ''),
    );

    var $hasAndBelongsToMany = array(
        'Project' => array('className' => 'Project',
            'joinTable' => 'projects_users',
            'foreignKey' => 'user_id',
            'associationForeignKey' => 'project_id',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'unique' => '',
            'finderQuery' => '',
            'deleteQuery' => '',
            'insertQuery' => ''),
    );
}
?>

It generates also a test case for the model in app/tests/cases/models/user.test.php:

<?php 

loadModel('User');

class UserTestCase extends UnitTestCase {
    var $object = null;

    function setUp() {
        $this->object = new User();
    }

    function tearDown() {
        unset($this->object);
    }

    /*
    function testMe() {
        $result = $this->object->doSomething();
        $expected = 1;
        $this->assertEqual($result, $expected);
    }
    */
}
?>

As you see, when using the bake script you have to answer many questions. That’s a bit of a pain when you want to bake multiple models. And if you don’t want to write tests you have to remove them (that’s at least what I do with files which are not used). These are the reasons I prefer to write the models manually.

Here are the other models (please notice that I omit the $name variable as I am a) using PHP5 and b) lazy):

// app/models/feed.php
<?php

class Feed extends AppModel {
    var $belongsTo = array('User');
}
?>
// app/models/project.php
<?php

class Project extends AppModel {
    var $hasMany = array('Message');
    var $hasAndBelongsToMany = array('User');
}
?>
// app/models/message.php
<?php

class Message extends AppModel {
    var $belongsTo = array('Project');
}
?>

To test whether we defined the associations correctly, we can create for all models the corresponding controllers. Here the UsersController (the other controllers are similar):

// app/controllers/users_controller.php
<?php

class UsersController extends AppController {
    var $scaffold;
}
?>

With that, we have something to play, we can add, edit, and remove records. But it isn’t very useful yet ;-)

That’s it for today.

(other articles from this series)

How to create an input field without a label

CakePHP : Posted by cakebaker at March 01, 2007 09:59 AM

If you use the form helper from CakePHP 1.2 to generate an input field, it automatically creates a label for the input field. So this snippet:

echo $form->input('Project/name');

will generate the following HTML code:

<div class="input">
    <label for="ProjectName">Name</label>
    <input name="data[Project][name]" type="text" value="" id="ProjectName" />
</div>

But sometimes you don’t want a label. How can you accomplish that? My first two attempts failed, the label “Name” was still shown:

// first attempt
echo $form->input('Project/name', array('label' => ''));
// second attempt
echo $form->input('Project/name', array('label' => null));

With the third attempt I was then successful, and the label disappeared:

echo $form->input('Project/name', array('label' => false));

In retrospect the solution is logical, but sometimes you don’t see the obvious solution ;-)

drake in progress - part 2

CakePHP : Posted by sentino at March 01, 2007 02:36 AM

This is my second post about Drake which is a combination of Drupal + CakePHP = Drake. I’ve used Drake few days after Felix of thinkingphp.org released it to the public as part of his proof of concept, it was very experimental during that time where in fact CakePHP wasn’t in its stable stage but Drupal was very stable, knowing CakePHP’s core gave me confidence to use Drake in my own production project and it went pretty well, there were some minor glitches but not a show stopper in production.

Drake is a framework(CakePHP) within a framework(Drupal) which lets you build web apps within CP while not limited to using Drupal’s whole functionality (template system, permission, etc).

A few days ago Mariano Iglesias added comments from my previous post “Drake in progress” letting me know about new Drake’s progress; He is now leading the development of Drake though haven’t tried it yet but highly recommended. Great job Mariano!

thanks

Switch your OpenID server on-the-fly

CakePHP : Posted by cakebaker at February 28, 2007 09:28 AM

OpenID allows you to use your own domain as OpenID and to delegate it for authentication purposes to an OpenID server by adding two link tags to the head section of a HTML page. In my case I use http://openid.42dh.com as OpenID and in the head section you will find:

<link rel="openid.server" href="http://www.myopenid.com/server" />
<link rel="openid.delegate" href="http://dh.myopenid.com" />

This approach comes with two potential problems:

  • I cannot authenticate myself if my domain is down,
  • and the same applies if the OpenID server is down

To the first problem I don’t have a solution yet, but for the second problem the solution is easy. Modify the snippet above and you can still authenticate with your OpenID by using a different OpenID server. It is just a bit cumbersome to login to your server and to do that change manually. So I wrote a very simple CakePHP application which allows you to switch the used OpenID server on-the-fly. It is one of those “15 minutes” projects where it is almost overkill to use CakePHP ;-)

You can find the application in the downloads section. The installation is simple, the zip contains a complete app folder (no database needed). You may have to modify app/webroot/index.php and to make the tmp folder writable, and, of course, you have to add the OpenID servers and delegates you want to use. They are defined in app/models/delegate.php. Now, to change the OpenID server you simply call example.com/change and a different OpenID server will be used.

That’s it.

My favourite “bug”

CakePHP : Posted by Felix Geisendörfer at February 27, 2007 06:22 PM

Hey folks,

here comes an example of my favorite "bug" that drives me insane on occasion:

PHP:
  1. if (!$this->hasField($field));
  2. {
  3.     // Do Something
  4. }

It just got me again and caused me to debug Model::hasField, finally implementing my own version of it until I realized what an idiot I am ... ; ). What's your favourite?

-- Felix Geisendörfer aka the_undefined

Should you use Model::query() in the controller?

CakePHP : Posted by cakebaker at February 26, 2007 09:49 AM

The query() function of the model is sometimes very practical as it allows you to execute any SQL statement you want. But to me it seems it is often used in a “wrong” way (I have to include myself *g*), in a way which works but is not that clean. I am talking about calling query() from the controller:

$this->MyModel->query('Here comes the SQL statement');

Sure, following the Cake conventions this is a valid usage of query(), as the function is public. But it introduces database-related code to the controller, which is a violation of the MVC pattern.

I think a cleaner solution is to move the SQL statement to the model. So the snippet from above can be refactored to:

// in the model
function doSomething() {
    $this->query('Here comes the SQL statement');
}

// in the controller
$this->MyModel->doSomething();

Ok, this solution needs more code, but it comes with some additional advantages:

  • it is easier to test
  • it is more readable (at least if you give the function a better name than I did)
  • the query can be re-used without copy & paste in other actions/controllers

What do you think about this approach?

Drake :: Drupal-CakePHP 1.0.1b Released

CakePHP : Posted by Felix Geisendörfer at February 26, 2007 06:32 AM

Hey folks,

this is just a quick announcement that a completely new Drake has has been released by Mariano Iglesias. For those of you who don't remember: I used to have some fun with integrating CakePHP with other PHP applications around such as drupal which in term created a project called Drake. I only released a couple versions and by looking at Mariano documentation for Drakes none of them were as easy to set up.

Here is his announcement from the google group:

My fellow bakers,

After my experience with Jake, and thanks to Felix’s goodwill in giving the catchy name Drake for me to use, I’m pleased to announce the first beta release of Drake, a Drupal module that lets you execute your CakePHP applications inside Drupal.

Project home page: http://dev.sypad.com/projects/drake

Documentation: http://dev.sypad.com/projects/drake/documentation

Download: http://drupal.org/project/drake

Demo (Cheesecake Photoblog on Drupal 5.1): http://dev.sypad.com/projects/drake/demo/drake

Bake on, with Drake ;)

-MI

So if you like to work with an existing system such as Drupal go ahead and give it a try!

-- Felix Geisendörfer aka the_undefined

Cake 1.2’s Set class eats nested arrays for breakfast!

CakePHP : Posted by Felix Geisendörfer at February 24, 2007 12:28 PM

Hey folks,

I was just taking a little trip through the CakePHP core code trying to wrap my head around Acl, Model behaviors and all sorts of stuff. While doing so I saw that the core code starts to be using the Set class more and more that was added a while ago. So far this has been a little dark spot for me in the core and from my previous quick looks at the class I've never been quite able to figure out what it's exact purpose was. Until now all I knew was "well it's probably some fancy array manipulation code that is somewhat obfuscated and undocumented". Oh boy, I wish I had spent more time on this earlier. It's probably one of coolest new features in 1.2 and nobody realizes it ; ).

So before starting to drool over it too much ahead of time, let's take a look at a simple example. You have an array of $users as it could have been returned from a findAll call to your User model:

PHP:
  1. $users = array
  2. (
  3.     0 => array
  4.     (
  5.         'User' => array
  6.         (
  7.             'id' => 1
  8.             , 'name' => 'Felix'
  9.         )                            
  10.     )
  11.     , 1 => array
  12.     (
  13.         'User' => array
  14.         (
  15.             'id' => 2
  16.             , 'name' => 'Bob'
  17.         )
  18.     )
  19.     , 2 => array
  20.     (
  21.         'User' => array
  22.         (
  23.             'id' => 3
  24.             , 'name' => 'Jim'
  25.         )
  26.     )
  27. );

What you really want however, is just a simple array containing all user 'name's: array('Felix', 'Bob', 'Jim'). Hmm. Up until today I'd probably have written some code like this to do it:

PHP:
  1. $userNames = array();
  2. foreach ($users as $user)
  3. {
  4.     $userNames[] = $user['User']['name'];
  5. }

Simple enough, right? Not any more! Using the new Set class we can achieve the exact same outcome like this:

PHP:
  1. $userNames = Set::extract($users, '{n}.User.name');

Doesn't blow you away yet? Well, let's look at another example. Let's say our User model as a hasMany associations to an Item model. Then we would get an array like this:

PHP:
  1. $users = array
  2. (
  3.     0 => array
  4.     (
  5.         'User' => array
  6.         (
  7.             'id' => 1
  8.             , 'name' => 'Felix'
  9.             , 'Item' => array
  10.             (
  11.                 0 => array
  12.                 (
  13.                     'id' => 1
  14.                     , 'name' => 'Mouse'       
  15.                 )
  16.                 , 1 => array
  17.                 (
  18.                     'id' => 2
  19.                     , 'name' => 'KeyBoard'
  20.                 )
  21.             )
  22.         )                            
  23.     )
  24.     , 1 => array
  25.     (
  26.         'User' => array
  27.         (
  28.             'id' => 2
  29.             , 'name' => 'Bob'
  30.             , 'Item' => array
  31.             (
  32.                 0 => array
  33.                 (
  34.                     'id' => 3
  35.                     , 'name' => 'CD'
  36.                 )
  37.             )
  38.         )
  39.     )
  40.     , 2 => array
  41.     (
  42.         'User' => array
  43.         (
  44.             'id' => 3
  45.             , 'name' => 'Jim'
  46.             , 'Item' => array
  47.             (
  48.                 0 => array
  49.                 (
  50.                     'id' => 4
  51.                     , 'name' => 'USB Stick'
  52.                 )
  53.                 , 1 => array
  54.                 (
  55.                     'id' => 5
  56.                     , 'name' => 'MP3 Player'
  57.                 )
  58.                 , 2 => array
  59.                 (
  60.                     'id' => 6
  61.                     , 'name' => 'Cellphone'
  62.                 )
  63.             )
  64.         )
  65.     )
  66. );

Now here is how I would have traditionally turned this into a 'User.name' => 'User.items' array:

PHP:
  1. $userItems = array();
  2. foreach ($users as $user)
  3. {
  4.     foreach ($user['User']['Item'] as $item)
  5.     {
  6.         $userItems[$user['User']['name']][] = $item['name'];
  7.     }
  8. }

But using the new Set class this is still pretty much a simple one-liner (split up in multiple lines so you don't have to scroll):

PHP:
  1. $userItems = array_combine
  2. (
  3.     Set::extract($users, '{n}.User.name')
  4.     , Set::extract($users, '{n}.User.Item.{n}.name')
  5. );

Both methods will output:

PHP:
  1. (
  2.     [Felix] => Array
  3.         (
  4.             [0] => Mouse
  5.             [1] => KeyBoard
  6.         )
  7.  
  8.     [Bob] => Array
  9.         (
  10.             [0] => CD
  11.         )
  12.  
  13.     [Jim] => Array
  14.         (
  15.             [0] => USB Stick
  16.             [1] => MP3 Player
  17.             [2] => Cellphone
  18.         )
  19. )

"But doesn't it cost more performance to loop through the array twice in the Set example?" I hear some of you cry. Yes it does. And? Have you built your application yet? Does it implement all features you are dreaming of? And most importantly: Do your web stats indicate you are going to have 1 million hits / day soon? If so go back into your code and remove the Set example with the less succinct foreach alternative. If not, listen to Chris Hartjes who's motto for 2007 is Just Build It, Damnit!.

Anyway, here comes my last fun thing to do with Set::extract - parsing an RSS feed for all post titles. For my example I'll use the new XML class in Cake 1.2. Right now Set::extract only supports arrays but hopefully it will either natively support Xml objects at some point, or the Xml class get it's own extract function. For now I've written a little function that can turn an Xml instance into an array that looks like this:

PHP:
  1. function xmltoArray($node)
  2. {
  3.     $array = array();
  4.    
  5.     foreach ($node->children as $child)
  6.     {
  7.         if (empty($child->children))
  8.         {
  9.             $value = $child->value;
  10.         }
  11.         else
  12.         {
  13.             $value = xmltoArray($child);
  14.         }
  15.        
  16.         $key = $child->name;
  17.        
  18.         if (!isset($array[$key]))
  19.         {
  20.             $array[$key] = $value;
  21.         }
  22.         else
  23.         {
  24.             if (!is_array($array[$key]) || !isset($array[$key][0]))
  25.             {
  26.                 $array[$key] = array($array[$key]);
  27.             }
  28.            
  29.             $array[$key][] = $value;
  30.         }
  31.     }
  32.    
  33.     return $array;
  34. }

So now let's assume we would want to extract all post titles from my feed: http://feeds.feedburner.com/thinkingphp we could leverage the Set class to make our code as succinct as:

PHP:
  1. uses('Xml');
  2.  
  3. $feed = xmltoArray(new XML('http://feeds.feedburner.com/thinkingphp'));
  4. $postTitles = Set::extract($feed, 'rss.channel.item.{n}.title');

Which will give you a $postTitles array like this:

PHP:
  1. (
  2.     [0] => How-to: Use Html 4.01 in CakePHP 1.2
  3.     [1] => Looking up foreign key values using Model::displayField
  4.     [2] => Bug-fix update for SVN/FTP Deployment Task
  5.     [3] => Access your config files rapidly (Win32 only)
  6.     [4] => Making error handling for Model::save more beautiful in CakePHP
  7.     [5] => Full content RSS feed
  8.     [6] => Visual Sorting - Some Javascript fun I had last night
  9. )

Now that's beauty right there and a good way to end this post ; ). Take a look at the Set classes source to find out about some other cool methods it has, but to me this is by far the coolest.

-- Felix Geisendörfer aka the_undefined

How-to: Use Html 4.01 in CakePHP 1.2

CakePHP : Posted by Felix Geisendörfer at February 21, 2007 05:20 PM

Hi folks,

this is just a little tip for those of you who dislike their xhtml to end up as tag soup and still prefer to send out Html 4.01 strict. Here is how to make all helper functions in Cake 1.2 output Html 4.01 compliant markup instead of xhtml:

PHP:
  1. class AppHelper extends Helper
  2. {
  3.     /**
  4.      * This is the constructor for our AppHelper class that we use to make all helper output html 4.01
  5.      * compatible markup.
  6.      *
  7.      * @return AppHelper
  8.      */
  9.     function AppHelper()
  10.     {
  11.         // Loop through all tags in this helper
  12.         foreach ($this->tags as $tag => $html)
  13.         {
  14.             // Replace all xhtml style tag closings with html 4.01 strict compatible ones
  15.             $this->tags[$tag] = r('/>', '>', $html);
  16.         }
  17.     }
  18. }

If you wonder why somebody would prefer the good old html over the all-so-hyped xhtml, read a little bit about it here.

-- Felix Geisendörfer aka the_undefined

Referencing Javascript files

CakePHP : Posted by cakebaker at February 21, 2007 04:52 PM

If you want to use a Javascript file, you usually reference it in the head section of your layout:

<head>
    <?php echo $javascript->link('script.js'); ?>
</head>

But sometimes you want to use a certain Javascript file only in one view. Then it doesn’t make sense to reference it in the layout. You could take the PHP statement from above and place it in your view, that will work. But it is not a clean solution as the Javascript reference is placed somewhere in the middle of the generated HTML code.

Fortunately, CakePHP 1.2 allows you to define a reference to a Javascript file which is then added to the head section of the generated HTML code. For this purpose the variable $scripts_for_layout has to be added to the layout:

<head>
    <?php echo $scripts_for_layout; ?>
</head>

In the view you can now link to the Javascript file with:

$javascript->link('script.js', false);

That’s it.