debuggable

 
Contact Us
 
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35

CakePHP RC2 Released!

Posted on 30/6/08 by Tim Koschützki

Hey folks,

in case you haven't noticed yet, Cake has gone into RC2 phase. Please check it out.

I think extra kudos go this time to gwoo for putting up countless hours to get many tickets fixed during the last two weeks. Cheers mate. :]

Here are some links that might interest you:

-- Tim Koschuetzki aka DarkAngelBGE

 

Working with databases on OSX will soon be as awesome as everything else on OSX

Posted on 24/6/08 by Nate Abele

Howdy people (ha! there's a new one),

As we all know, database apps on most platforms... well, suck... even on OSX. The best we have is CocoaMySQL, which, while okay, could certainly not be called great. This shouldn't be the case. We have higher standards. We can do better.

Enter Shift. Shift started life as a direct port of the apparently-no-longer-maintained CocoaMySQL, and indeed, much of the code and interface is still the same. However, the interface mockups of the upcoming 0.5 release make this a project worth keeping a close eye on.

 

How to bend Cake's Model::find() method to your needs

Posted on 21/6/08 by Tim Koschützki

Hey folks,

CakePHP allows you to use your own "find-types" for the Model::find() methodology. Those of your who are familiar with the find() method know that there are currently four types in the core: 'list', 'all', 'first' and 'count'. However, sometimes it is nice to specify your own type so you can have a call like this:

$this->Comment->find('pending');

So how would you go about implementing this? Correct, you would overwrite the find() method in your model and provide the default functionality if there is one of the default types used. Let's have a look at how you could implement this pending find from above:

class Comment extends AppModel {
  var $belongsTo = array('User');

  function find($type, $queryData = array()) {
    switch ($type) {
      case 'pending':
        return $this->find('all', array(
          'fields' => array('User.email', 'User.first_name', 'User.last_name', 'Comment.name')
          , 'conditions' => array(
            'Comment.active' => 0
            , 'Comment.blocked' => 0
          )
          , 'contain' => array('User')
        ));
      default:
        return parent::find($type, $queryData);
    }
  }
}

So this is actually not very difficult code, but it is very powerful. You could specify different sets of conditions, fields, orders, groups by's and containments just by adding a new entry to the switch statement. Please note that there is no break needed within the switch as we return home.

Let's add some code for finding pending users that were invited by the currently logged-in user. This is what we could use in the controller:

$this->Comment->find('pending', array('created_by_id' => User::get('id')));

Now for the model we would need to add some code that tracks if $queryData['conditions'] is set, and use that as well in conjunction with the conditions specified in our find type. While we are at it, let's also add some code that would handle fields, order, group, recursive and contain statements:

class Comment extends AppModel {
  var $belongsTo = array('User');

  function find($type, $queryData = array()) {
    $defaults = array(
      'conditions' => null
      , 'fields' => array()
      , 'order' => null
      , 'recursive' => null
      , 'contain' => null
    );
    $queryData = am($defaults, $queryData);

    switch ($type) {
      case 'pending':
        return $this->find('all', array(
          'fields' => am(
            array('User.email', 'User.first_name', 'User.last_name', 'Comment.name')
            , $queryData['fields']
          )
          , 'conditions' => am(array(
            'Comment.active' => 0
            , 'Comment.blocked' => 0
          ), $queryData['conditions'])
          , 'contain' => am(array('User'), $queryData['contain'])
        ));
      default:
        return parent::find($type, $queryData);
    }
  }
}

With some default values we can happily use our custom find type alongside dynamic conditions inserted by our controllers. If you think this am() stuff is overkill, you could as well just provide another find type:

$this->Comment->find('pending-created-by-logged-in-user');
class Comment extends AppModel {
  var $belongsTo = array('User');

  function find($type, $queryData = array()) {
    switch ($type) {
      case 'pending':
        return $this->find('all', array(
          'fields' => array('User.email', 'User.first_name', 'User.last_name', 'Comment.name')
          , 'conditions' => array(
            'Comment.active' => 0
            , 'Comment.blocked' => 0
          )
          , 'contain' => array('User')
        ));
      case 'pending-created-by-logged-in-user':
        return $this->find('all', array(
          'fields' => array('User.email', 'User.first_name', 'User.last_name')
          , 'conditions' => array(
            'Comment.active' => 0
            , 'Comment.blocked' => 0
            , 'Comment.created_by_id' => User::get('id')
          )
          , 'contain' => array('User')
        ));
      default:
        return parent::find($type, $queryData);
    }
  }
}

... well you get the idea. By the way, do you notice how useful using a static functions like this User::get() methods are for fetching properties and data from the currently logged in user? More on that later.

Anybody willing to share some dough on this one?

-- Tim Koschuetzki aka DarkAngelBGE

 

How To Execute Only Specific Test Methods in CakePHP Unit Tests

Posted on 19/6/08 by Tim Koschützki

Hey folks,

most of us familiar with unit testing with CakePHP and SimpleTest know that
SimpleTest always executes all methods starting with the string "test".

This can be problematic sometimes:

  1. As you unit test your application you realize that the same test fails again and again and you cannot make it work and therefore want to tackle it in isolation from the other tests.
  2. Many tests fail and you want to tackle them one at a time without getting distracted by the failure messages of the others.
  3. Say you debug a model class of yours and have many tests for the same model function. Now, in order to get the one failing test working, you want to output some debug information within that model method. However, as there are many tests for that method, the debug information will always show for every test and your screen is full of debug information and you spend five or more seconds searching for the correct one.
  4. Executing all test functions takes a tremendous amount of time and you just need to get one test to work. You obviously don't want to spend 30 seconds waiting just to get the same failure message again and again. ; )

At this point you have two options:

  1. Create a new testcase, copy all the code that is needed to setUp() it properly, copy the test function over and observe and debug it there. Then copy back when you are done and then for every test method. This isn't very fast.
  2. Put an 'x' before every test method name except before the one you want to debug. This really slows you down if there are many test functions.
  3. Read the following tip and make your life a lot easier. ; )

As the CakeTestCase class extends SimpleTest's UnitTestCase class, it inherits all their functionality. Fortunately, SimpleTest internally has a useful mechanism to choose which functions to execute in the test class.

The name of this method is getTests(), which returns an array of methods that it shall execute. By default it looks like this:

function getTests() {
    $methods = array();
    foreach (get_class_methods(get_class($this)) as $method) {
        if ($this->_isTest($method)) {
            $methods[] = $method;
        }
    }
    return $methods;
}

function _isTest($method) {
    if (strtolower(substr($method, 0, 4)) == 'test') {
        return ! SimpleTestCompatibility::isA($this, strtolower($method));
    }
    return false;
}

Pretty straightforward. Fetches all method names from the current class and tests if they start with 'test' (and looks if they are tests). So we see all that getTests() really does is return an array of test methods.

Let's look at an example on how to put this to use. Say you have the following tests (which absolutely make no sense whatsoever) in your custom CakePHP Test class:

class MyHumbleTest extends CakeTestCase {
  function setUp() {}

  function testOfIdenticalClass() {
    $this->assertIdentical('class', 'class');
  }

  function testOfIdenticalName() {
    $this->assertIdentical('name', 'name');
  }

  function tearDown() {}
}

.. and then some. Now you want to check the testOfIdenticalName method alone. You would add a getTests() implementation:

class MuHumbleTest extends CakeTestCase {
  function setUp() {}

  function testOfIdenticalClass() {
    $this->assertIdentical('class', 'class');
  }

  function testOfIdenticalName() {
    $this->assertIdentical('name', 'name');
  }

  function getTests() {
    return array('testOfIdenticalName');
  }

  function tearDown() {}
}

Very cool and handy, yes? Life is getting easier with such things, especially with many test functions.

One thing to note is that if you want to keep fixtures working, you have to tell it to still execute the start(), startCase(), endCase() an end() callbacks:

class MuHumbleTest extends CakeTestCase {
  function setUp() {}

  function testOfIdenticalClass() {
    $this->assertIdentical('class', 'class');
  }

  function testOfIdenticalName() {
    $this->assertIdentical('name', 'name');
  }

  function getTests() {
    $methods = array('testOfIdenticalName');
    return am(a('start', 'startCase'), $methods, a('endCase', 'end'));
  }

  function tearDown() {}
}

One thing to note is that in order for Cake's start(), startCase() callbacks to work you need to add them, too:

$methods = array_merge(array_merge(array('start', 'startCase'), $methods), array('endCase', 'end'));

-- Tim Koschuetzki aka DarkAngelBGE

 

How to Group By in CakePHP's new release Part 2

Posted on 16/6/08 by Tim Koschützki

Hey folks,

having promised it in the first post on how to do Group By in CakePHP I worked on an array() version for the group statement in Model::find() calls. So I implemented it. This is what can be accomplished now:

$result = $Thread->find('all', array(
  'conditions' => array('Thread.project_id' => 1),
  'group' => 'Thread.project_id, Project.id')
);
$this->assertEqual($result, $expected);

$result = $Thread->find('all', array(
  'conditions' => array('Thread.project_id' => 1),
  'group' => 'project_id')
);
$this->assertEqual($result, $expected);


$result = $Thread->find('all', array(
  'conditions' => array('Thread.project_id' => 1),
  'group' => array('project_id'))
);
$this->assertEqual($result, $expected);


$result = $Thread->find('all', array(
  'conditions' => array('Thread.project_id' => 1),
  'group' => array('project_id', 'Project.id'))
);
$this->assertEqual($result, $expected);


$result = $Thread->find('all', array(
  'conditions' => array('Thread.project_id' => 1),
  'group' => array('Thread.project_id', 'Project.id'))
);
$this->assertEqual($result, $expected);

As you can see you can still group via the former string method. In addition to that any combination of available fields can be used in an array to form a GROUP BY statement with values separated by comma.

So this code here:

$result = $Thread->find('all', array(
  'conditions' => array('Thread.project_id' => 1),
  'group' => array('Thread.project_id', 'Project.id'))
);

would result in a GROUP BY statement that looks like:

GROUP BY `Thread`.`project_id`, `Project`.`id`

You can leave out the alias of the model the find is invoked on as long as your columns aren't ambigous.

Happy baking all! Oh and for those who are interested in the code, have a look at the changeset.

-- Tim Koschuetzki aka DarkAngelBGE

 
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35