debuggable

 
Contact Us
 
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14

What is a Testcase - especially in CakePHP?

Posted on 9/6/09 by Tim Koschützki

Hey folks,

it's amazing how many people on the public CakePHP irc channel don't know what a test case is. This has to stop. To clear up some confusion, let's have a look at it.

What is a Test Case? A Possible Definition

This is what wikipedia has to say about a testcase:

A test case in software engineering is a set of conditions or variables under which a tester will determine whether an application or software system is working correctly or not. The mechanism for determining whether a software program or system has passed or failed such a test is known as a test oracle. In some settings, an oracle could be a requirement or use case, while in others it could be a heuristic. It may take many test cases to determine that a software program or system is functioning correctly. Test cases are often referred to as test scripts, particularly when written. Written test cases are usually collected into test suites.

So is this me testing stuff in my browser?

Yes and no. The definition does not necessarily imply that a testcase has anything to do with Unit Testing, or other testing approaches.

You testing your menu bar's javascript in several browsers in different resolutions can also be considered to be a testcase (or many for that matter) according to the definition. However, most of the time a testcase is meant to be a written test. When it comes to CakePHP, it's mainly a Unit Test.

Where can I learn about test cases?

There are a ton of resources on the web for tests. If you are looking for material for CakePHP, try out these:

  1. The Article "Introduction to Unit Testing Part 1" written by yours truly
  2. The CakePHP CookBook Chapter on Testing
  3. Your First Testcase
  4. Mark Story's Unit Testing Talk Slide for CakeFest Argentinia December 2009
  5. My slides for the Unit Testing Talk for CakeFest Orlando February 2008

Lets get a discussion started to clear up confusion.

-- Tim Koschuetzki aka DarkAngelBGE

 

Migrating a Database Table for use with the CakePHP Sluggable Behavior

Posted on 8/6/09 by Tim Koschützki

Hey folks,

if you are not familiar with Mariano's Sluggable Behavior, you should definitely check it out. It's a nice tool to generate SEO-friendly urls in your application. If you are anything like debuggable.com with our long urls, you might want to look at it. ;)

If you don't know what a url slug is:

A slug is a few words that describe a post or a page. Slugs are usually a URL friendly version of the post title, but a slug can be anything you like. Slugs are meant to be used with your site's urls as they help describe what the content at the URL is. They might or might not help your SEO ranking as well for the keywords in the slug/content.

Example post url: http://flashfun247.com/games/play/racing/tg-motocross-2.

The slug for that game content is "tg-motocross-2".

This blogpost deals with migrating an existing production table into using Mariano's sluggable behavior and thereby increasing SEO friendliness of your site.

Step 1: Back up the database table

Back up the table for which you want to add slugs. We cannot be blamed for any database damage this might cause.

Step 2: Modify your database

Add a "slug" field to the table. The default length that the behavior uses is 100 chars which should be enough in most cases. So VARCHAR(100) is what you need or the equivalent for your db driver.

Step 3: Add your $actsAs declaration

Add the proper call to $actsAs to the model that you want to migrate. Put in any existing behaviors that you need as well:

var $actsAs = array(
  'Containable',
  'Lookupable',
  'Sluggable' => array('overwrite' => true)
);

Make sure to set the overwrite option, as otherwise the behavior will refuse to overwrite the slug field the way we will do it. Also make sure to set the 'label' option if the table field that your slugs will depend on is not called 'title'.

For the full host of options, check the sluggable behavior.

Important: The field your slugs depend on (title, name or combinations) MUST NOT be empty for any row in your table. The shell takes this into account and provides you with an error log. To save you some headaches, make sure the fields are properly filled before you run the sluggish shell.

Step 4: Modify The Sluggable Behavior Code

You need to change the Sluggable behavior code on two occurances to make it work with the recent Cake releases.

1. Change Line 121 to:

$conditions = array($Model->alias . '.' . $this->__settings[$Model->alias]['slug'] . ' LIKE' => $slug . '%');

2. Change Line 125 to:

$conditions[$Model->alias . '.' . $Model->primaryKey . ' <>'] = $Model->id;

This is simply changing the syntax for NOT and LIKE conditions.

Step 5: Download Debuggable's Sluggish Shell

Copy the following code to a 'sluggish.php' file in your vendors/shells folder. Just mouseover the code to see it in raw text.

<?php
/**
 * Sluggish Shell
 *
 * Set overwrite => true before running this in your $actsAs declaration!
 * This shell allows you to generate unique slugs for a database table ready for use
 * with the sluggable behavior by Mariano Iglesias
 *
 *
 * Sluggish Shell : Make your table sluggable
 * Copyright 2009, Debuggable, Ltd. (http://debuggable.com)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright     Copyright 2009, Debuggable, Ltd. (http://debuggable.com)
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
 */

class SluggishShell extends Shell {
/**
 * undocumented function
 *
 * @return void
 * @access public
 */

  function main() {
    if (empty($this->args)) {
      return $this->out('You need to specify a modelname');
    }

    $model = $this->args[0];
    $force = isset($this->args[1]) ? $this->args[1] : false;

    $Model = ClassRegistry::init($model);
    $behavior = 'Sluggable';

    if (!is_object($Model)) {
      return $this->out('This model does not exist.');
    }

    if (!in_array($behavior, $Model->actsAs) && !array_key_exists($behavior, $Model->actsAs)) {
      return $this->out('The Sluggable Behavior is not yet linked to the model.');
    }

    $label = 'title';
    if (isset($Model->actsAs['Sluggable']['label'])) {
      $label = $Model->actsAs['Sluggable']['label'];
    }

    $Model->recursive = -1;
    $conditions = $force ? false : array('slug' => '');
    $rows = $Model->find('all', compact('conditions'));
    $count = count($rows);

    $i = 0;
    foreach ($rows as $row) {
      $Model->set(array(
        $Model->primaryKey => $row[$model][$Model->primaryKey],
        $label => $row[$model][$label]
      ));
      $Model->save();

      $row = $Model->find('first', array(
        'conditions' => array(
          $Model->primaryKey => $row[$model][$Model->primaryKey],
          'slug' => ''
        ),
        'contain' => false
      ));
      if (empty($row)) {
        $i++;
      } else {
        $this->out('Problem saving the slug for ' . $row[$model][$label]);
        $this->out($Model->validationErrors);
      }
    }

    $this->out('Added ' . $i . ' slugs for ' . $count . ' ' . Inflector::pluralize($model));
  }
/**
 * undocumented function
 *
 * @return void
 * @access public
 */

  function help() {
    $this->out('Debuggable Ltd. Sluggish Shell - http://debuggable.com');
    $this->hr();
    $this->out('Important: Configure your paths in the shell\'s initialize() function.');
    $this->hr();
    $this->out('This shell allows you to migrate a database table to use Mariano Iglesias\' Sluggable Behavior.');
    $this->out('Add a slug field to the table, download the sluggable behavior, add your $actsAs declaration and run this shell.');
    $this->out('');
    $this->hr();
    $this->out("Usage: cake sluggish ModelNameInCamelCase");
    $this->out('');
  }
}
?>

The code is very simple. The shell takes a modelname and checks if your $actsAs declaration is properly set up. If it is, it finds all rows from the table and saves them right away, with the label field in the Model::set() call.
Since we have specified overwrite => true in the $actsAs declaration, the sluggable behavior now overwrites all slug fields in the table, giving you nice slugs.

Step 6: Run The Sluggish Shell

Run "cake sluggish ModelNameInCamelCase" in your terminal.

Step 7: Change your urls

The Sluggable behavior creates unique slugs. If two of your blogposts for example have the same title, the first one will have 'my-blogpost-title' as its slug and the second one will have 'my-blogpost-title-1'. Mariano's behavior attaches an integer to the slug depending on how often the slug was used already.

Now that you have unique slugs, you can change your controller code and view code to take account of this. If you are heavily indexed in google already, you might want to provide 301 (permanent) redirects for the old urls, or just offer both urls to access the same blogpost, but only use slugs throughout the app.

Enjoy! Post any feedback or help requests in the comments below.

-- Tim Koschuetzki aka DarkAngelBGE

 

CakePHP RemoveCache Shell - Remove Your Cache Files Easily

Posted on 4/6/09 by Tim Koschützki

Hey folks,

it's been a while since the last post here on Debuggable. However, this ends now and apart from this very post here we have something bigger brewing for you as well. So please stay tuned a little longer. :)

Anyways, on to some Caching fun.

The Motivation

1. When you work with large projects you can end up with a ton of cache files (models, db cache, etc.) as well as view caching files. When you try removing them with:

cd /app/tmp/cache/models && rm -f cake_*

and

cd /app/tmp/cache/views && rm -f *

.. you can end up with with the "argument list too long" error easily.

2. Also, executing two commands sucks - we are lazy after all. Now you could pull out some fancy bash fun to pipe file names. Have a look at this:

find . -type f | awk '!/empty/ {print "rm", $0}' | bash

The problem is, when you run this in /app/tmp it will not only remove cache files, but also files in /tmp/sessions, /tmp/logs and so on. If you ask me, the command is complex enough, so no need to add more funny stuff there to take this into account.

(For you peeps who want to see this, I bugged Felix to tell me: find . -type f | awk '!/empty$|^.\/logs|^.\/sessions/ {print "rm", $0}' | bash)

3. Once you are on windows, you do not have a powerful bash to your side.

I thought a simple call to a CakePHP shell can do the trick as well and doesn't force you to waste half a minute to remember and type in the proper bash command.

The Solution

The RemoveCache shell allows you to remove your cae cache files easily. It takes two parameter:

  • A boolean to control if you want to remove standard cache files (models, db cache, etc.)
  • A regex pattern to control which view cache files to remove

Here are some common usage scenarios:

Usage: cake remove_cache <std_cache_boolean> <pattern_to_match_viewcache_files>
Usage: cake remove_cache // removes all cache files
Usage: cake remove_cache 0 // removes only view cache files
Usage: cake remove_cache 0 home // removes only the view cache file for your homepage
Usage: cake remove_cache 0 articles_ // removes all view cache files for your articles controller
Usage: cake remove_cache 1 /letter_z$/ // removes all std cache files and view cache files ending with 'letter_z'

I did not put in a pattern for standard cache files, because most of the time you cannot remember your cache keys anyways and most of the time it doesn't harm if a cache file is invalidated to rebuilt the cache. If someone wants a pattern for that too, because they have long-taking queries, just comment and I will add it.

By default, the shell looks in your standard /app/tmp directory (plus subfolders) to find the cache files. If you have a shared Cake installation or any other fancy setup, please adjust the cache paths in the inititalize() method.

The Code

<?php
/**
 * Remove Cache Shell
 *
 * This shell allows you to remove cache files easily and provides you with a couple configuration options.
 * If run with no command line arguments, RemoveCache removes all your standard cache files (db cache, model cache, etc.)
 * as well as your view caching files.
 *
 *
 * RemoveCache Shell : Removing your Cache
 * Copyright 2009, Debuggable, Ltd. (http://debuggable.com)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright     Copyright 2009, Debuggable, Ltd. (http://debuggable.com)
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
 */

class RemoveCacheShell extends Shell {
/**
 * undocumented function
 *
 * @return void
 * @access public
 */

  function initialize() {
    parent::initialize();

    $this->settings = array(
      'view_cache_path' => APP . 'tmp' . DS . 'cache' . DS . 'views',
      'std_cache_paths' => array(
        APP . 'tmp',
        APP . 'tmp' . DS . 'cache',
        APP . 'tmp' . DS . 'models',
        APP . 'tmp' . DS . 'persistent'
      )
    );
  }
/**
 * undocumented function
 *
 * @return void
 * @access public
 */

  function main() {
    $args = $this->args;

    $stdCache = !isset($args[0]) || $args[0];
    $viewCachePattern = isset($args[1]) ? $args[1] : '.*';

    if ($stdCache) {
      $this->_cleanStdCache();
    }

    $this->_cleanViewCache($viewCachePattern);
  }
/**
 * Cleans the standard cache, ie all model caches, db caches, persistent caches
 * Files need to be prefixed with cake_ to be removed
 *
 * @return void
 * @access public
 */

  function _cleanStdCache() {
    $paths = $this->settings['std_cache_paths'];

    foreach ($paths as $path) {
      $folder = new Folder($path);
      $contents = $folder->read();
      $files = $contents[1];
      foreach ($files as $file) {
        if (!preg_match('/^cake_/', $file)) {
          continue;
        }
        $this->out($path . DS . $file);
        @unlink($path . DS . $file);
      }
    }
  }
/**
 * Cleans all view caching files. Takes a pattern to match files against.
 *
 * @param string $pattern
 * @return void
 * @access public
 */

  function _cleanViewCache($pattern) {
    $path = $this->settings['view_cache_path'];

    if ($pattern{0} != '/') {
      $pattern = '/' . $pattern . '/i';
    }

    $folder = new Folder($path);
    $contents = $folder->read();
    $files = $contents[1];
    foreach ($files as $file) {
      if (!preg_match($pattern, $file)) {
        continue;
      }
      $this->out($path . DS . $file);
      @unlink($path . DS . $file);
    }
  }
/**
 * undocumented function
 *
 * @return void
 * @access public
 */

  function help() {
    $this->out('Debuggable Ltd. Remove Cache Shell - http://debuggable.com');
    $this->hr();
    $this->out('Important: Configure your paths in the shell\'s initialize() function.');
    $this->hr();
    $this->out('This shell allows you to remove cache files easily and provides you with a couple configuration options.');
    $this->out('If run with no command line arguments, RemoveCache removes all your standard cache files (db cache, model cache, etc.) ');
    $this->out('as well as your view caching files.');
    $this->out('');
    $this->out('Set the first parameter to 0 (zero), to not remove standard cache files.');
    $this->out('Set a regex pattern for the second argument, to match viewcache files to delete.');
    $this->hr();
    $this->out("Usage: cake remove_cache <std_cache_boolean> <pattern_to_match_viewcache_files>");
    $this->out("Usage: cake remove_cache \t\t// removes all cache files");
    $this->out("Usage: cake remove_cache 0 \t\t// removes only view cache files");
    $this->out("Usage: cake remove_cache 0 home \t// removes only the view cache file for your homepage");
    $this->out("Usage: cake remove_cache 0 articles_ \t// removes all view cache files for your articles controller");
    $this->out("Usage: cake remove_cache 1 /letter_z$/ \t// removes all std cache files and view cache files ending with 'letter_z'");
    $this->out('');
  }
}
?>

Enjoy! Feedback welcome.

-- Tim Koschuetzki aka DarkAngelBGE

 

The biggest CakeFest to be held in Berlin

Posted on 25/3/09 by Felix Geisendörfer

I will make this short. The 3rd and so far biggest CakeFest will be held in Berlin, home of Debuggable! From July 9-12, people from all over Europe and the rest of the world will travel to Germany in order to celebrate and learn about the best PHP framework there is.

If you have not attended a CakeFest so far, here are some good ideas of what to expect:

The event consists of two parts:

  • July 9-10: CakePHP Workshop - (Lead by the CakePHP core team)
  • July 11-12: CakePHP Conference - (Presented by the core team + community)

The 2-day workshop is packed with a series of tutorials designed to give developers, both new and seasoned, a solid understanding in building reliable CakePHP applications. Veterans and experts can skip over stuff they already know and use this time for 1 on 1 sessions with the non-presenting developers. The Workshop + Conference ticket is 599 EUR (499 EUR if you do not attend the conference).

The conference itself is going to be packed with talks, delivered by both the CakePHP core team as well as interested community members. At just 199 EUR (student discounts to be announced soon) there is no excuse for not attending.

I will talk on JavaScript for (Cake)PHP developers as well as share my experience in project management using GitHub + Lighthouse. Tim is still making up his mind, but will probably talk about Advanced Debugging.

The location for the conference is a few streets down in my neighborhood, so be assured that we will have more than enough opportunity to gather & celebrate in the evenings.

If you have any questions, please feel free to post them here or email them to me at felix@debuggable.com.

Otherwise go ahead and sign up, you will not regret it as the conference will also feature some major announcements exclusively made there.

- -Felix Geisendörfer aka the_undefined

 

Git alias for displaying the GitHub commit url

Posted on 18/3/09 by Felix Geisendörfer

If you often find yourself pointing your team members to commit urls in GitHub, this might be fun for you.

I created git alias called 'hub' that automatically guesses the github repository url for the repository you are currently in:

$ git hub
https://github.com/felixge/my-project

Based on that I created a second alias called 'url', which gives you the url to HEAD commit:

$ git url
https://github.com/felixge/my-project/commit/0bdc57323a1ffec7ffe10bf83147cab5d6838d45

You can however also provide another sha1 you want to link to:

$ git url 22db8914220b717b0954b84365030ae3c9602a17
https://github.com/felixge/my-project/commit/22db8914220b717b0954b84365030ae3c9602a17

If you find those aliases useful, here are my ~/.gitconfig alias definitions for them:

[alias]
  hub =! echo "https://github.com/"`git config remote.origin.url` | sed -E s/[a-z]+@github\.com:// | sed s/\.git$//
  url =!sh -c 'HEAD=`git rev-parse HEAD` && SHA1=`[ "$0" = "sh" ] && echo $HEAD || echo $0` && echo `git hub`"/commit/"${SHA1i}'

(Bash gurus: I am sure you can do the above much more elegantly, wanna give it a try?)

Further I also have this little bash script in ~/bin/tiny:

#!/bin/bash
curl "http://tinyurl.com/api-create.php?url=$1"
echo ""

This allows me to make tiny urls for my links if I need to paste them on long-url unfriendly territory:

$ git url | xargs tiny
http://tinyurl.com/cva44t

Or if you are on OSX, you can use this to open the git url in your browser:

$ git url | xargs open

Now all of the above could probably be done much smarter, but as far as I am concerned it works great ; ).

-- Felix Geisendörfer aka the_undefined

PS: What command line tricks are part of your daily workflow?

 
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14