Technology


22
Dec 10

5 principles of web application development productivity

My early New Year’s resolution is to ship more next year (why wait for next year to start?). With this in mind, I decided to write about increasing productivity in web app development (more posts coming!). In this first part, I will look at the principles behind getting more done quicker. In the next parts, I will look into the points marked with ** in more detail.

Based on a look at the top 400 or so questions tagged “productivity” on Stackoverflow, the five principles of web application development productivity are:

1. Know your tools

Learn all you can about your particular language, framework and tools. Doh!

Explore other people’s solutions to your problems and learn best practices.

Use version control; diff tools, project/bug/feature tracking; virtual machines; VPS/platform-as-a-service providers when needed.

2. Define your problem well

Get obsessed about your project: think about specifications, UI and implementation mechanisms when you’re not coding. **

Don’t get bogged down on details, and try to identify the most valuable parts of the project. Don’t get too “clever” about features; gold-plating carries an opportunity cost.

If you use something more than three times, make it reusable and think about how future features will influence the architecture.

Invest time in creating tools for recurring and error-prone parts of your project.

3. Keep yourself and your team on track

Eliminate distractions; communicate frequently enough but keep it brief.

Stick to a MVP; avoid scope creep.

Have a detailed to-do list. Keep to-dos small and specific enough that you can tick them off. If you can’t determine whether the task is done, then refactor the description until it is unambiguous and small enough. Finish one task at a time.

Time-box your iterations and tasks (e.g. Pomodoro technique): set deadlines and create pressure by making a commitment (e.g. tell someone).

“Accept that everything is a draft. It helps to get it done. Laugh at perfection. It’s boring and keeps you from being done.”

“The point of being done is not to finish but to get other things done.” In other words, get the first version done and then move on to the next most important thing. Get back to it if feedback indicates it’s important.

Try to keep any projects/project iterations to less than a week long, so that your ideas about how it should work won’t start diverging wildly from how your users actually think. Get feedback from real customers to re-orient yourself. If this not possible, try to involve someone else to bounce ideas off.

4. Write less code

Think more about what you are writing and how to do it more intelligently.

Think about code organization for code reuse.

Use a language that allows for easy/quick constructs.

Use a framework that provides good classes/functionality for your problem.

Use standardized boilerplate code: CSS frameworks, boilerplate distributions… **

Use libraries: emailing, captchas, admin, accounts… **

5. Create fewer bugs

Use xUnit for your language and do TDD.

Automate testing: e.g. Selenium, continuous integration.

Next part

In the next part I will discuss boilerplate code and problem definition in detail. See you soon!

References:


1
Dec 10

How to deploy web applications using Mercurial

Does deploying changes to your site take too long? Are you tired of manually sorting out the update? Here is how to deploy your projects using Mercurial.

Why?

  • Ease of updating. Mercurial keeps track of the changes and only sends the necessary changes -  you don’t need to worry about transferring files.
  • Make it possible to roll back changes on the deployed site. You can use hg to roll back from a bad update if necessary.
  • Once set up, it’s beautiful. “hg deploy”. How can you not like that?

How is this different from the other guide you wrote about setting up private repo hosting?

  • While the differences aren’t that big, this setup is better for deployment rather than code distribution via repositories:
    • Minimal dependencies. This approach only uses the hg-ssh script from the Mercurial core contrib.
    • Manual configuration. You can set different directories for each repository which allows you to work with your existing webroot setup. However, you will need to manually add new repositories, since hg-ssh does not support adding new repositories remotely.

If you want a private version of Bitbucket (without any additional features, of course), e.g.  to be able to remotely init/clone new repositories, check out my other tutorial about setting up private repo hosting.

1. Make sure that the .hg directories are never served to the public

To prevent .hg directories from being accessible to other people, you can take a number of (optional, but recommended) precautions:

Move the web root of your project to a different folder

You can probably alter your project  repository so that you have an explicit webroot folder in which you only have the files that should be accessible to the public (bootstrap + js/css resources). This makes it less likely that you accidentally serve your .hg directory. You move the files within hg do this using hg move:

mkdir webroot
hg move . webroot

Or you can use hg addremove –similarity=100 after moving the files manually. It will detect identical files as moved.

After this, you may need to update your apache config to serve from the webroot (e.g. adjust the DocumentRoot directive for the site / virtual host).

Set up Apache not to serve .hg directories

You can also prevent Apache from serving directories with .hg in them by adding the following to your httpd.conf:

<DirectoryMatch \.hg>
   Order allow,deny
   Deny from all
</DirectoryMatch>

Restart Apache if you change httpd.conf.

2. Setup the server

Create the user

# make a system user (-r) with a home directory (-m)
useradd -r -m hg
# lock the user account
usermod -L hg

Setup ssh

#install mercurial if not previously installed
yum install mercurial
su hg
cd /home/hg/
wget http://www.selenic.com/repo/hg-stable/raw-file/tip/contrib/hg-ssh
chmod u+x ~hg/hg-ssh
mkdir /home/hg/.ssh/
nano /home/hg/.ssh/authorized_keys
# remember to chmod the ssh config
chmod -R 0700 /home/hg/.ssh/

Copy the public key: ssh-rsa …(key data)== to /home/hg/.ssh/authorized_keys, one line for each authorized key.

Add the following in front of each authorized key in /home/hg/.ssh/authorized_keys:

command="~hg/hg-ssh /path/to/repository",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-rsa ...==

If necessary, enable public key auth in /etc/ssh/sshd_config and add hg to the AllowUsers directive. Restart sshd if you change sshd_config.

3. Init the repo

New repo on the remote server

Just run hg init in the new repo folder, then hg clone it on your computer from the remote server:

hg clone -v --debug ssh://hg@server:port/path/to/repo

The -v and –debug make the clone show more information about what is going on.

From existing sources

Unfortunately hg-ssh does not support the hg init/hg clone command remotely. If you have an existing repo, you have to copy it first to the repository directory on the server. Since there are many files to move, I recommend gzipping the whole directory before moving it, then unzipping on the server. Do a hg log and hg status to see that everything transferred correctly.

Adding more repos

To add more repositories, simply add another repository path (separated by a space) immediately after the first repo path in .ssh/authorized_keys, and either init a new repo or copy an existing repo.

4. Push updates

After you have the same repository on both the server and locally, you can start pushing  stuff to the server:

hg push ssh://hg@server:port/path/relative/to/home/dir

If your repo path is not relative to the home dir, you need an extra slash in front of the push:

hg push ssh://hg@server:port//path/relative/to/base/dir

If you run into problems, try connecting via ssh – see my previous post for some tips on this.

5. Automate hg update

You should automate hg update so that each push causes the repository to be updated automatically to the pushed version. You can automate hg update on the remote repo by adding the following to the remote .hg/hgrc:

[hooks]
changegroup = hg update >&2

Note that output has to be redirected to stderr (or /dev/null), because stdout is used for the data stream -which is why there is the >&2 at the end of the command.

If you get errors in updating automatically, check the permissions. In particular, make sure that .hg/ is owned by hg and that the content is readable by apache.

6. Set up default push or create an alias

If you only push to one location, then you can set up the default locations for pull and push in the local  .hg/hgrc:

[paths]
default = ssh://hg@servername/reponame
default-push = ssh//:hg@servername/reponame

This allows you to simply use “hg push” to deploy.

If you need more than one push location, create an alias in the local  .hg/hgrc:

[alias]
deploy = push -v --debug ssh://hg@server:port/path/to/repo

This allows you use “hg deploy” as an alias for deploying.

What about configuration files?

One simple solution is to use “hg forget” to forget them once you have deployed the configuration files. This means that hg will not track the configuration files, but will not delete them either.


16
Nov 10

Simple tweaks: make Gnome look and work better

These are the things I do to customize Gnome after a new install. These are all rather simple tweaks, but they help a lot!

1. Install Guake

Guake is a top-down terminal for Gnome (in the style of the terminal used in Quake). It allows you to switch to the console just by pressing F12. Highly recommended, since a lot of things are still done in the console.

2. Change the terminal to half-transparent and dark

Start Terminal -> Edit -> Profile preferences: change Colors to White on Black instead of the system theme, then set Background -> Transparent background.

3. Get rid of the top panel, and get rid of “Applications | Places | System”

Right-click and unlock all the panel items on the top panel, and move them to the bottom panel . Remove the default menu completely, and add the more compact “Main menu” (Add to Panel -> Main Menu). Change the date widget to only show the time.

4. Install compiz (not really for the 3D effects, but for the productivity options)

httpv://www.youtube.com/watch?v=7eaI9YcxD9s

yum -y install compiz-fusion-extras libcompizconfig ccsm 
compiz-manager libcompizconfig compiz-fusion-extras-gnome fusion-icon-gtk

Enable the following effects using compizconfig from the Gnome menu:

General Options

  • Key bindings: Set Maximize Window to Super + Up, Unmaximize Windows to Super + Down and Show Desktop to Super + d, just like on Windows 7.

Desktop effects:

  • Desktop Wall. Visually shows your desktop moving to the left/right when you switch between virtual desktops using Ctrl+Alt+Right/Left. I think it looks nice, and makes virtual desktops feel a bit more tangible and usable.

Window management:

  • Application Switcher. A visual version of Alt+Tab, which shows window previews. No need to configure.
  • Grid. A better version of Aero Snap. Allows you to move windows around the desktop, a bit like the Win+Left / Win + Right shortcuts in Windows 7. I always just bind the “Put Left” and “Put Right” shortcuts to Super + Left and Super + Right (which is the same convention as Windows) and ignore the rest, since I don’t have a keypad on my laptops.
  • Snapping Windows. Makes the windows stick to the edges of the screen when moved/resized.

Also potentially useful:

  • Scale. Basically a copy of Mac OS X Expose; scales down your windows and allows you to select via mouse the one you want.

All the other stuff is mostly useless, but feel free to try out all the different plugins.

5. Enable font smoothing and install better fonts

Enable subpixel rendering for fonts under System -> Preferences -> Apperance -> Fonts, and have a look at this tip for installing more fonts.

6. Install gnome-color-chooser and customize the taskbar background by changing the image

I like to have a dark taskbar (panel). However, this is hard to do with the default tools in Fedora, because the “Appearance” app does not allow you to configure the panel background separately from the window background even though GNOME supports different colors for these two parts.

So you need to install gnome-color-chooser. Ubuntu has in the repos:

sudo apt-get install gnome-color-chooser

For Fedora, you need to follow my instructions to install it from source. Tested with F13 and F14.

Then go under System -> Preferences -> Gnome Color Chooser -> Panel and set all the backgrounds to black and all the foreground colors to white or near-white colors. Apply.

To make your panel look better, use one of the following background or Google for more:

36 px height background: 

24 px height background: 

Win7 style: (use panel item background color: #8194aa and white text)

Right-click, save as image, then right-click on the panel and set the downloaded image as the background.

7. Result

This is how the resulting desktop looks like:

If you want to customize the window chrome, look into enabling emerald and find a theme that works for you, or just find a GTK theme from the link under the “Appearance” app.


15
Nov 10

How to install gnome-color-chooser for Fedora

Gnome-color-chooser is a tool which allows you to control the colors used in GNOME with a lot more granularity than the default “Appearance” app.

Unfortunately, Fedora does not have a repository with a gnome-color-chooser rpm, so you will need to compile it yourself.

Here are the instructions, tested with gnome-color-chooser v. 0.2.5 and Fedora 13/14:

su
yum install wget gcc-c++ glib-devel intltool gtkmm24-devel libglademm24-devel libgnomeui-devel libgnome-devel libxml2-devel

wget http://downloads.sourceforge.net/project/gnomecc/gnome-color-chooser/0.2.5/gnome-color-chooser-0.2.5.tar.gz

tar -xzvf gnome-color-chooser-0.2.5.tar.gz

mv gnome-color-chooser-0.2.5 /opt/gnome-color-chooser

cd /opt/gnome-color-chooser/

./configure

make

make install

After installing, you can find it under System -> Preferences -> Gnome Color Chooser.


11
Nov 10

Kohana 3 i18n tutorial

Continuing my series on Kohana 3 (see Auth here and Validation here), I’m tackling Kohana 3 internationalization in this post.

How Kohana 3 I18n works

Kohana 3′s i18n functionality is implemented in two files:

  • /system/classes/i18n.php – Implements the i18n class, which provides all the functionality.
  • /system/base.php – Implements the __() function, which is most commonly used for translating strings.

The Kohana_I18n class provides the following functions:

  • I18n::lang($lang = NULL). Gets or sets the target language for translation. Call without parameters to get the current target language to which strings will be translated to.
  • I18n::get($string, $lang = NULL). Returns the translation of the source string, optionally specifying what the target language should be. You should not usually use this function, however it has some uses (discussed later).
  • I18n::load($lang). Loads the translation table for a given language, caches it and returns it.

The base file provides one translation function, the double underscore:

  • __($string, $values, $lang = ‘en-us’). When the current target language is the same as the param $lang, returns the same string. When the current target language is something else, returns the translated string if it exists, or the same string if it does not.The $values array determines replacements made in the string.

__() is the main function you should be using. Just wrap all your strings into it.

Storing and loading the translation strings

As you notice if you look at the source, there are no translation strings here. They are stored under /application/i18n/languagename.php, where languagename is the name of the language you want to use. Kohana uses the string “en-us” as the default target language.

Note how the default language string has two parts: one specifying the language (en for English) and another for the region (us for the US).

Loading is done automatically on-demand when you call __(). The loading function I18n::load() works in a way that it can flexibly search for a file. It explodes the string on the “-” character, so the default target language “en-us” results in a search for the following files:

  • /application/i18n/en.php
  • /application/i18n/en/us.php

Note that the order means that you can have a single language file for English, and then override some parts of the file – for example with the target language string “en-gb”, Kohana would first load the en.php file and then the /en/gb.php file; this makes it easy to have regional variants for strings (“color” vs. “colour”).

What should I store in the i18n language files?

This is an important question, and Kohana does not make it for you. There are basically two approaches:

Option 1: Storing identifiers

// in View
echo __('about.description');
// in /i18n/latin.php
return array(
    'about.description' => 'Lorem ipsum dolor amet...',
);

Option 2: Storing the translated string itself

// in View
echo __('This is a sample text...');
// in /i18n/latin.php
return array(
    'This is a sample text...' => 'Lorem ipsum dolor amet...',
);

Which one is better? This is a matter of preference, and I strongly advise that you store the translated string itself. I absolutely HATE storing identifiers, having done that and worked with some applications that store identifiers. shadowhand (Kohana’s benevolent dictator) agrees, see this discussion and this discussion.

The problem with identifiers is that they make you remember a million different strings and hunt around for the right string to change in your file. Your translators have to remember what “about.description” meant when they translate your files, and you have to remember what is behind “about.description” when you make code changes. This is a maintenance nightmare, and if you forget to translate a string, the user will see a cryptic identifier.

The pros of identifiers are that the translation files are slightly smaller and use slightly less memory, and that you can change the translation files without changing the code. However, these are in my opionion rather meager advantages compared to the maintenance problems that are created.

Storing the string itself is better, because it allows you to see the text in your files and allows you to edit the translation in-place. This 1) allows for direct editing, and 2) makes it obvious that a new translation is needed since the English text is shown, and 3) if you forget to translate a string, the user will see something in English rather than just a identifier.

The nice thing is that if you write your application in the default target language, no I18n calls are performed when you use __()! So you don’t even pay the cost of a lookup. You do not need to create a translation file for the default language, because strings in the default language will be returned as-is by __().

TL;DR: Store the translated string itself.

How do I tell Kohana I want to translate a string?

Use __(‘This is my string’). If the string is in a language other than en-us, then use __(‘String’, null, ‘language’); you will then need to have a /application/i18n/en.php file for the English equivalent.

If you need to replace items in the string, do something like:

echo __('Dear :firstname, your username is: :user', array(
      ':firstname' => $user->name,
      ':user' => $user->username,
      ));

How do I start collecting the i18n strings?

Put a file like this as /application/languagename.php:

<?php defined('SYSPATH') or die('No direct script access.');

return array(
    'Hello World' => 'Terve maailma',
);

and start writing the English-to-your-language strings.

Now that you know how to do that – don’t do it manually! Instead take a look at my automatic I18n string collector, which extends I18n and automatically detects missing translations (for whatever language is currently set as the target), and keeps updating your translation files. It saves some serious amounts of time.

How do I allow the user to switch the language dynamically?

Use a cookie to store the user language, then load the value in /application/bootstrap.php:

// default value for the cookie is 'fi' for Finnish
$lang = Cookie::get('lang', 'fi');
if(!in_array($lang, array('fi', 'sv', 'en-us'))) {
   // check the allowed languages, and force the default
   $lang = 'fi';
}
// set the target language
i18n::lang($lang);
Then provide some sort of interface to changing the cookie in a Controller:
function action_change_language($lang) {
   if(!in_array($lang, array('fi', 'sv', 'en-us'))) {
      $lang = 'fi';
   }
   Cookie::set('lang', $lang);
   I18n::lang($lang);
   Request::instance()->redirect('page/index');
}

The link would look something like Html::anchor(‘/controller/change_language/fi’, ‘Finnish’) to change the language.

How do I get a string in a different language than the one currently in use?

This is the use case for I18n::get($string, $lang). It will get you the string in a different language than the one currently in use. An example would be when you want the user to select the language an invitation email is sent to a friend (who may speak a different language), and you already have the invitation email translated in multiple languages.

Remember, in __($string, $values, $lang) you set the source language (what language is the string in) while in I18n::get($string, $lang) you set the target language (what language you want the string to be returned in).

Update: An important thing to note about newline characters in multi-line strings

I noticed that there is one area which causes problems with translations: newline characters.

Since lookups are character-sensitive, if your newline characters change in the translation file, you may not be able to look up the correct string!

I strongly recommend using UNIX line endings in your translation files and explicitly using “\n” when performing multi-line string lookups (e.g. __(‘”String\nNewline\nSecond newline”‘). Otherwise, there is the risk that you accidentally save with Windows file endings, causing translation lookups to fail for multi-line strings. If you need different line endings (e.g. emails), do a strtr afterwards.

Update: Remember to save as UTF-8

The default output from Kohana is UTF-8 encoded, so make sure your translation files are saved as UTF-8.

How do I make it easier for someone to translate my strings?

Updated: I’ve now released the Kohana-translate module, which is an application for collecting translations for KO3 applications. Read about & download Kohana-translate here.

Questions, comments, thanks, contributions?

Leave a comment below… or ask on the Kohana forums.


30
Oct 10

How to use Kohana 3 validation (with forms)

Update: I am gradually updating my Kohana 3.0 articles to Kohana 3.1.

Overview of Kohana 3 validation

Validation is done using the Validation class, or via ORM using $model->check() automatically via exceptions. It uses the messages functionality in KO3, which is a system for specifying validation messages for various forms (I will discuss this).

The steps in validating a form are:

  1. Initialize the Validation class.
  2. Call Validate->check() and check whether the validation succeeded.
  3. If the validation failed, call errors($file) to get the error messages.
  4. Display the validation errors in the correct context on the form.

The first steps are covered by existing documentation, so I will focus on the last two steps here.

Initializing the Validation class

In Kohana 3.1, the Validation class is simply initialized by setting up rules. The class has also changed from Validate (KO 3.0) to Validation (KO 3.1).

Rules. The Validation class has a nice set of basic validation rules, including rules for email addresses, credit cards, URLs and regexps.

KO 3.0 migration notes

KO 3.1 validation no longer has callbacks or filters. Callbacks have been merged into rules, while filters have been removed altogether.

Filters were used to do things like trim() the input in KO 3.0.x – you can do array_map($array, ‘trim’).

Callbacks are now specified using the rule() function, see this excellent tutorial from Lysender for the details!

Some sample code

 

// Create a validation object
$validation = Validation::factory(
   array(
    'email' => 'no-reply@test.com',
    'name' => 'Test'
    );
// Add some basic rules
$validation->rule('name', 'not_empty');
$validation->rule('email', 'email');
// Check the result
$validation->check();

// Using a single Kohana_Valid::rule
// for just checking a single field
if(Valid::email('test@test.com')) {
   // do something with the email
}

 

Retrieving and customizing error messages

Validation can be done for both models (on save) and various other forms. Because of this the messages are stored in separate, reusable files. You should put your message files under /application/messages/filename.php.

I think the logic behind this has gotten a bit more complicated for KO 3.1 – in particularly with validation messages from the ORM… have to get back to you on that.

The files look like this:

array(
'username' => array(
   'username_available' => 'This username is already...',
   'not_empty' => 'Username must not be empty.',
   'invalid' => 'Password or username is incorrect.',
),
'password' => array(
   'matches' => 'The password and password confirmation ..',
   'range' => ':field must be within the range of :param1 to :param2',
),
);

Each field is a sub-array and each rule has it’s own field. Note that you can use :field to specify the field name.

To retrieve the messages, use $validate->errors($file) where $file is the filename (no extension).

The return value looks something like:

array(
   'password' => 'password must be less than 42 characters long',
   'password_confirm' => 'The password and password confirmation are different.',
   'username' => 'This username is already registered, please choose another one.',
   'email' => 'This email address is already in use.');

The simplest way to show these is the just print them in as a list. However, I will show one way to show these messages in context on the actual form.

Displaying validation errors in context on the form

Here is how we want to show the forms:

To accomplish this, I have created a new Appform helper which uses the Form class, but wraps its input with application-specific markup for errors.

The basic idea is that you can pass default values, form values and error messages to Appform prior to outputting the fields. It will then create the contextual markup for each of the fields. I have maintained compatibility with the Form API, with additional properties for added functionality.

Note: for Kohana 3.1, you need to move the error messages from _external to the base for Appform to work.

$errors = $e->errors('register');
$errors = array_merge($errors, (isset($errors['_external']) ? $errors['_external'] : array()));
$view->set('errors', $errors);

As a side note, I think this is exactly how the division of labor between framework and application developer should be: the framework should not impose a particular markup on the developer, but to keep application-specific code shorter, the developer should be able to create an extended version of the Form helper for the basic form layout. There are too many different preferred styles of markup, and the framework should not try to guess how you like your forms but rather provide the backend. Kohana gets this right.

Here is how a sample invocation would look like (note that the Appform class is not static, since each form has its own contextual data):

$form = new Appform();
if(isset($defaults)) {
  $form->defaults = $defaults;
}
if(isset($errors)) {
  $form->errors = $errors;
}
if(isset($values)) {
  $form->values = $values;
}
echo $form->open('user/login');
echo '<ul>';
echo '<li>'.$form->label('username', __('Username')).'</li>';
echo $form->input('username', NULL, array('info' => __('You can also log in using your email address instead of your username.')));
echo '<li>'.$form->label('password', __('Password')).'</li>';
echo $form->input('password');
echo '</ul>';
echo $form->submit(NULL, __('Login'));
echo $form->close();

You can download my form validation helper from Bitbucket. It’s only a couple of hundred lines so it is easy to improve – a useful start for building more complex functionality for your app and also to reduce the lines of code needed for each form.

ORM and form validation (still need to update to KO 3.1)

The Kohana ORM provides support for field validation. However, it is very much oriented towards each model having one set of validations. With ORM, the function calls are:

if ( !empty($_POST) && is_numeric($id) ) {
   $model = ORM::factory('user', $id); // create
   $model->values($_POST); // load values to model
   // check() initializes $model->_validate with a Validation object containing the
   // rules, filters and callbacks from Model_User (e.g. $_rules, $_callbacks..)
   if ($model->check()) {
      $model->save(); // save the model
   } else {
      // Load errors. The first param is the path to the
      // message file (e.g. /messages/register.php)
      $content->errors = $user->validate()->errors('register');
   }
}

You may want to do something different, either using a different set of rules or a different set of features! The classic example is user profile editing, where you do not want to force the user to re-enter their password – so you need to exclude the password rules from the Validation check.

There are many different suggestions regarding how you should handle this case. For example, in the unofficial docs they define two functions which each initialize a different validation object. I did this in my KO3 Auth sample implementation, and biakaveron (author/maintainer of KO3 Auth) suggested it is a “WTF” – since ORM already has the properties and methods for initializing a validation object from the given rules.

I would suggest using the following pattern: rely on the ORM $_rules property when possible, and when you need different types of validation (e.g. create, update, change subset of fields), define a checkCreate/checkUpdate/checkXYZ function on your model which loads the alternative set of $_rules and then calls the ORM check() to actually perform the validation.

Here is an example (model function, see also sample code):

public function check_edit() {
   $values = $this->as_array();
   // since removing validation rules is tricky (this is needed to ignore
   // the password),we will just create our own alternate _validate
   // object and store it in the model.
   $this->_validate = Validate::factory($values)
      ->label('username', $this->_labels['username'])
      ->label('email', $this->_labels['email'])
      ->rules('username', $this->_rules['username'])
      ->rules('email', $this->_rules['email']);
   // if the password is set, then validate it
   // Note: the password field is always set if the model was loaded
   // from DB (since there is a DB value for it)
   // So we will check for the password_confirm field instead.
   if(
      isset($values['password_confirm']) &&
      (trim($values['password_confirm']) != '')
   ) {
   $this->_validate
      ->label('password', $this->_labels['password'])
      ->label('password_confirm', $this->_labels['password_confirm'])
      ->rules('password', $this->_rules['password'])
      ->rules('password_confirm', $this->_rules['password_confirm']);
   }
   // Since new versions of Kohana automatically exclude the current user from
   // the uniqueness checks, we no longer need to define our own callbacks.
   foreach ($this->_callbacks as $field => $callbacks) {
      foreach ($callbacks as $callback) {
         if (is_string($callback) AND method_exists($this, $callback)) {
            // Callback method exists in current ORM model
            $this->_validate->callback($field, array($this, $callback));
         } else {
            // Try global function
            $this->_validate->callback($field, $callback);
         }
      }
   }
   return $this->_validate->check();
}

This way you can still keep using the same, or almost identical code e.g. $model->values(), $model->validation->errors() and $model->checkXYZ(), while benefiting from the builtin code.

For example (in the Controller, see also sample code):

if ( !empty($_POST) && is_numeric($id) ) {
   $model = ORM::factory('user', $id); // create
   $model->values($_POST); // load values to model
   // check() initializes $model->_validate with a Validation object containing the
   // rules, filters and callbacks from Model_User (e.g. $_rules, $_callbacks..)
   if ($model->check_edit()) {
      $model->save(); // save the model
   } else {
      // Load errors. The first param is the path to the
      // message file (e.g. /messages/register.php)
      $content->errors = $user->validate()->errors('register');
   }
}

A note about ORM: $_ignored_columns

This field is used to specify fields which will not be saved to the database, but which may be written and accessed in while the model class exists. If you use validation through ORM, then you should specify any additional fields which are part of the model validation here (e.g. fields which will be combined during the actual save, but are transmitted separately).


10
Oct 10

CakePHP gripes

I’ve used CakePHP for quite a while now, so here are the things I find most problematic about it:

Auth and ACL functionality

Many superficial reviews point this out as a strength of CakePHP. It is definitely NOT a strong point, because the included ACL is too darn complex. In particular, the ACL functionality has multiple different modes: actions mode (e.g. read, write permissions), CRUD (e.g. create, read, update, delete permissions) mode  (both DB-based) and a file based mode.

These modes each require different types of database table setup in terms of how you will structure the permissions. Unfortunately, doing that is also rather complex because the ACL stores the rights using the MPTT (Modified Preorder Tree Traversal) method. If you aren’t using MPTT elsewhere, then that’s another thing to figure out, and manually figuring out what is going on is quite painful. I also remember reading someone gripe about the number of queries it generates.

Furthermore, there is a large number of tables and keeping these in sync is left as an exercise to the implementer. In particular, while creating a new user will update the appropriate ARO table rows, subsequent updates the user or groups will need to have their ARO synchronization implemented manually since Cake’s magic does not handle these parts.

Code generation

It is easy to get started with generating models, views, controllers and tests, but code generation is a rather minor benefit in my opinion, since once you get past the first version of the program you will still end up writing more UI code and scrapping most of the generated code.

A minor annoyance is that the generated controllers and views separate the view and edit actions, which is in many cases not really necessary. You can often use the edit view for viewing data – which leads to less code to maintain (one view vs. two views). Here is how you can do that (CakePHP tips booklet).

Scaffolding

Scaffolding is nice in the first five minutes. However, in particular with models it tends to get in your way when you least expect it. In particular, the scaffolding for plugins is easy to trigger accidentally because CakePHP expects a particular naming scheme – and then you end up wondering why the models you just created don’t seem to have any of the functions you added.

I’ve ended up patching the core so that CakePHP dies if it tries to load a scaffolded model – and that has made these bugs easier to prevent. Here is how I did it.

Furthermore, the scaffolding gets things wrong in complex cases – for a simple example, an hasOne and a hasMany have identical table structures, so it isn’t possible to correctly determine which one should be used. And then there are the HABTM join tables, which you might want to retrieve as separate entities (e.g. due to relation data in the join table) in addition to the actual tables.

ORM

The most awesome part of CakePHP is the Containable behavior. The base ORM is a bit greedy, and reads all attached models – and setting the recursive param to a particular level is a very blunt tool for including/excluding attached data.

However, I really like the Containable behavior, which allows you to specify arbitrarily deep nesting paths. This is a huge time saver.

Of course, the ORM returns arrays rather than objects, which is a limitation, but one that I can live with – and PHP5 will be in the core sooner or later.

Helpers

CakePHP’s Form helper is a bit of a pain in the neck if you want to use your own markup rather than whatever CakePHP wants you to use. Some unwanted markup gets added automatically. The element creation functions differ slightly from each other, which means you need to look them up (e.g. form->radio vs. form->select) from the manual quite frequently. Kohana 3 has a nicer API.

The magic, while helpful for standard forms is problematic if you really need to build a custom form. It would be better if the base Form helper were simpler and there would be an extended version which would implement things like the surrounding markup rather than having these two in the same helper.

The Paginator helper is quite nice for implementing paginated views.

Models

CakePHP does provide callbacks such as afterSave for models, but at least for now these do not actually fire in all of the “edge cases” such as in associated models. This means that one can’t rely on those to actually work in all cases, which is a shame.

Is it worth it?

All in all, these are the problems I’ve run into using CakePHP. It’s not a bad framework, there are just cases in which it is a bit too magical. I recommend keeping these things in mind when considering whether to use Cake – you might not run into any of these issues at all for some types of applications, while others will.


4
Oct 10

Setting up private SSH-based Mercurial repo hosting on Centos

In this tutorial, I walk through the steps to setup Mercurial repo hosting using Mercurial-server on Centos, and setting up Putty+Mercurial on Windows to enable access. There are a lot of small things that seemed to be missing from the existing guides, so I try to cover those to help you save time in getting this set up.

1. Install mercurial-server

As stated on http://mercurial.selenic.com/wiki/SharedSSH, Mercurial-server is not a server but rather a management interface which uses a single shared SSH login for access and some additional logic to allow user rights to be limited.

Installing mercurial-server (version 1.0.1) on Centos requires that you tweak the makefile a bit, because it was originally designed for Debian-based systems and there are a few problems. Unfortunately, I haven’t found a repo that would have mercurial-server, so there are a couple of manual steps involved.

First, install the prerequisites:

yum install python python-setuptool* mercurial make

Then, download the source from http://www.lshift.net/mercurial-server.html and untar it on the server.

Open “Makefile” in the source directory, and make the following changes:

  1. For the line  ”installfiles: installetc installdoc pythoninstall”, remove “installdoc”. Result should be “installfiles: installetc pythoninstall”. The reason why this is necessary is documented here.
  2. For the line saying “useradd –system –shell /bin/sh \”, change to “useradd -r –shell /bin/sh \”. At least for me, the useradd command did not recognize the –system switch, so I had to use -r instead. I guess you could also remove it.

After this, run:

make setup-useradd

(need root privileges) . I remember getting some minor warning, but it didn’t seem to be a problem.

2. Copy your public key which you want to use to login onto the server

As stated in the Mercurial-server docs, you need to add the public key you want to use into /etc/mercurial-server/keys/root/put-your-username-here/put-an-easy-to-remember-name-for-your-keyfile-here .

If you are using Putty, make sure you have allowed agent forwarding under Connection -> SSH -> Auth -> Authentication parameters -> Allow agent forwarding so that ssh-add will work properly. This way you don’t have to mess around with Puttygen (well, the alternative is just to open the file, then paste the OpenSSH format public key to a file and then uploading it).

Then run refresh-auth. It is actually under /usr/local/share/mercurial-server/refresh-auth instead of /usr/share (as it is in the documentation). Here are the steps on the remote server after logging in (from mercurial-server documentation):

$ ssh-add -L > my-key
$ sudo mkdir -p /etc/mercurial-server/keys/root/your-username
$ sudo cp my-key /etc/mercurial-server/keys/root/your-username/an-easy-to-remember-name-for-keyfile
$ sudo -u hg /usr/share/mercurial-server/refresh-auth

3. Ensure that you can login using hg@yourserver

You first need to be able to login as hg before you can use the system. So test this out by connecting to the server using Putty (obviously, this has to be public key auth, not password auth – Google for a better tutorial on that if necessary).

Most likely, you will need to first unlock the hg account. This is because under Red Hat-based distros, users are created in a locked state (= unable to login, see also: documentation). This will show up under /var/log/secure as “sshd[15245]: User hg not allowed because account is locked”.

To unlock the hg user, setup the account password using:

passwd hg

This should result in the account becoming unlocked.

If you are still unable to login, check the sshd_config file under /etc/ssh/sshd_config.

There are a couple of settings that have to be in place, most obviously:

PubkeyAuthentication yes
AuthorizedKeysFile      .ssh/authorized_keys
AllowUsers hg and-all-the-other-allowed-users

and have a look for AllowGroups/DenyGroups/DenyUsers. Remember that AllowUsers is a whitelist, you have to specify all the accounts that are allowed to log in using ssh (separated by spaces). Not having it set should be equivalent to “AllowUsers *”.

Remember to restart sshd if you do change the settings.

4a. Setup Mercurial on Windows to use the correct key

If you are using Putty on Windows for your client repo access, you need these settings.

In C:\Users\accountname\Mercurial.ini, add the following under the [ui] section:

ssh="C:\Program Files (x86)\PuTTY\plink.exe" -ssh -2 -batch -C -i "D:\some-path\key-file.ppk"

To test the connection, run:

"C:\Program Files (x86)\PuTTY\plink.exe" -ssh -2 -batch -C -v -i "D:\some-path\key-file.ppk" hg@servername ls

You should be able to login.

If you keep getting another IP address, make sure your Putty default settings IP is empty. And if you get a “Disconnected: User aborted at host key verification”, log in once using Putty and accept the server’s host key.

4b. Setup Mercurial on Fedora/Centos/Redhat to use the correct key

If you are using ssh+hg on Linux for your client repo access, you need these settings.

You will need to add your key to the /var/lib/mercurial-server/.ssh/authorized_keys file, either manually or by using the steps in stage 2.

To set up the private key, edit or create ~/.ssh/config:

Host servername-or-ip
User hg
IdentityFile ~/path-to-keyfile

To test the connection, run:

ssh servername-or-ip

To set up the default pull and push locations for hg push and hg pull, change .hg/hgrc:

[paths]
default = ssh://hg@servername/reponame
default-push = ssh//:hg@servername/reponame

Further, much more detailed documentation on the hgrc file is available from Selenic.

5. That’s it, clone your repo to the server

hg clone --debug -v . ssh://hg@servername/name-of-the-repo

The additional switches will help in showing what is being done.

BTW, remember to specify the source for what you are cloning (e.g. “.”).


14
Sep 10

Kohana 3 auth: sample implementation and documentation

This is the 3rd part of my mini-series on Kohana 3 auth. I figure the best way to show the Kohana Auth module works is to provide a sample application module which uses auth. In this series of posts, I discuss:

  1. Setting up the basic Auth in KO3 (part 1)
  2. An overview of the functionality provided by the Auth module (part 2)
  3. Tips on implementing Auth in a custom application (part 3; this part)
  4. Getting started with Useradmin, my Kohana 3 auth admin module (part 4)

The new release is AWESOME :) , and now is based on the Kohana 3.1.x API. (You can get the old version for 3.0.x via Github.)

Here are a few screenshots:


New UI, more providers supported! Get it from:

This is a custom module, and the code is in /modules/user. The code contains way more comments than usual. I’ve tried to make things clear, and keep the UI basic but pretty enough.

Further, the code is released under the BSD licence, so you can use it in commercial applications. While I don’t restrict you in using the code, it is good karma to send improvements to this module back: in particular, I would like to have the features in the roadmap below.

What functionality is there:

  • Use the base Ko3 auth module and do not reinvent the wheel — OK
  • Support translation; no strings without __() — OK
  • Boilerplate for administration (create user, edit user, delete user, list users and last logins) — OK; roles have to be added via DB but that’s OK
  • Boilerplate for users (login, logout, view profile, edit profile, unregister, access denied page; error handling/validation) — OK
  • reCAPTCHA optionally supported for registration — OK; implemented for registration, thank you jnbdz!
  • Optional support for 3rd party login:
    • Facebook — OK
    • Google — OK
    • Yahoo — OK
    • Twitter — OK (with the caveat that Twitter does not allow you to get the email address via api)
  • Login  using username, email or both — OK
  • Optional lost password reset via email — OK; workflow is a bit clunky (user has to type the new password one time)
  • Optional autologin support via cookies — OK but not documented here; read about it Ko3 docs. Need to add checkbox to UI.
  • Optional enforcement of number of failed login attempts — is there but configuration/activation is clunky.
  • UI design that doesn’t make your eyes bleed — OK
  • Kohana 3.1.x support – OK, thank you gartz!

What needs to be done – contribute this!

  • reCAPTCHA optionally supported for too many failed logins
  • Optional account activation via confirmation email + expire unactivated account/welcome message via email
  • Optional password strength indicator for client
  • Separation between module and sample app on Github — e.g. migrate development to Github only

Nice-to-have (perhaps release as compatible modules?)

  • Profile picture loading from Facebook, Twitter or uploaded image — not yet.

So if you implement any of those, and don’t it to be too much of a burden to publish them under the BSD licence, let me know via comments or via email (firstname.surname@gmail.com).

Changelog is in the readme file on Github/Bitbucket.

Useful code snippets

Here are some code snippets which show you the basics of Auth:

Create a new user (e.g. if you have not set up any users yet and want to do that programmatically)

$model = ORM::factory('user');
$model->values(array(
   'username' => 'admin',
   'email' => 'admin@example.com',
   'password' => 'admin',
   'password_confirm' => 'admin',
));
$model->save();
// remember to add the login role AND the admin role
// add a role; add() executes the query immediately
$model->add('roles', ORM::factory('role')->where('name', '=', 'login')->find());
$model->add('roles', ORM::factory('role')->where('name', '=', 'admin')->find());

Check whether current user has a role

Auth::instance()->logged_in('admin') // for current user

Check whether some other user has a role

// Load the role
$role = ORM::factory('role', array('name' => 'admin));
// Check that the user has the given role
$status = $user->has('
roles', $role);

Add a role to a user

// add() executes the query immediately, and saves the data (unlike the KO2 docs say)
$user->add('roles', ORM::factory('role')->where('name', '=', 'admin')->find());

Remove a role from a user

// remove() executes the query immediately
$user->remove('roles', ORM::factory('role')->where('name', '=', 'admin')->find());

Retrieve all users that have a particular role

// find all the reviewer users in order of their username
$role = ORM::factory('role')->where('name', '=', 'reviewer')->find();
$users = $role->users->order_by('username', 'DESC')->find_all();
foreach($users as $user) {...}

Documentation on the interesting bits

The template view

The template view in /views/default/template.php uses the Thematic-inspired markup which I discuss in this post. You can do a lot by adding a few more divs into the header div. I have tried to follow my own advice about CSS, but the included CSS file is pretty basic. It includes the Yahoo CSS reset.

Controller_App: base controller class

This is the “base” controller I use for all the classes. It handles template autoloading and auth checks. You should inherit all your controllers from Controller_App to benefit from its features.

There is also one additional neat thing: in case of session expiry, Kohana may show an error in session loading. There is an additional check for this in Controller_App, which discards the session in case of loading errors.

Template autoloading loads the file from /views/default/template.php, so that you just have to set $this->template->content = $view->render() in your controller.

Auth checks are configured through two additional properties in the controller:

/**
 * Controls access for the whole controller, if not set to
FALSE we will only allow user roles specified.
 *
 * See Controller_App for how this implemented.
 *
 * Can be set to a string or an array, for example array('login', 'admin') or 'login'
 */
public $auth_required = FALSE;

/** Controls access for separate actions
 *
 *  See Controller_App for how this implemented.
 *
 *  Examples:
 * 'adminpanel' => 'admin' will only allow users with
the role admin to access action_adminpanel
 * 'moderatorpanel' => array('login', 'moderator') will
only allow users with the roles login and moderator to access action_moderatorpanel
 */
public $secure_actions = array();

Controller_User: Controller for user self-management

Particularly interesting code is in the register view, which shows how you can load validation error messages from a file, and how you might alter classes to show errors.

You might want to improve that code, I haven’t had the time to create a proper helper for this.

  • action_index shows how to check for a particular role using auth
  • action_noaccess is the generic “access denied” view.
  • action_profile and action_profile_edit show how to load data, and how to validate data on save. See application/modules/user.php for details on how the validation is done.
  • action_register shows how to create a new user.
  • action_unregister shows how to delete the current user, and log them off programmatically.
  • action_forgot is a basic example of how forgotten passwords might be recovered. I haven’t implemented the email sending functionality for now, so if you do it, please contribute here.
  • action_reset resets the user password if the user knows the 32-letter recovery key which should be emailed to them.

Controller_Admin_User: Controller for administering users

You need the admin and login privileges to access this controller.

There is a sample implementation of how to use Kohana 3 pagination in the index view. This could be improved e.g. by a helper that would take into account the sort state.

  • action_index is a sample of how pagination can be used.
  • action_edit is the administrative edit and add for new users. It shows how to add roles to users and also how to combine editing and adding new users into one action
  • action_delete is the administrative delete function for deleting users.

Model_User: Model for users

This code is mostly based on Model_Auth_User (in /modules/auth/classes/model/auth/user.php). However, I extended the login() function to support failing after too many attempts over a 5-minute period.

The “too many recent failed logins” functionality is commented out, since it will need a few additional fields.

Also, you might want to add the “created” and “modified” columns, there is a comment showing how this can be done using ORM functionality.

Questions, comments, thanks, contributions?

Leave a comment below… or ask on the Kohana forums.


7
Sep 10

Kohana 3 auth: the auth module functionality

The Kohana 3 Auth model implements a set of basic functionality for authentication (login) and authorization (user rights based on roles). In this series of posts, I will discuss:

  1. Setting up the basic Auth in KO3 (part 1)
  2. An overview of the functionality provided by the Auth module (part 2; this part)
  3. Tips on implementing Auth in a custom application (part 3)

In this second post in the series I will discuss the different functionality provided by the Auth module.

An overview of the files in the KO3 Auth module:

  • modules/auth/classes/kohana/auth/file.php – contains the file driver for Auth.
  • modules/auth/classes/kohana/auth/orm.php – contains the ORM driver for Auth.
  • modules/auth/classes/kohana/auth.php – implements the Auth singleton.
  • modules/auth/classes/model/auth/user.php – User model (Model_Auth_User).
  • modules/auth/classes/model/auth/role.php – Role model (Model_Auth_Role).
  • modules/auth/classes/model/auth/user/token.php – User_token model (Model_Auth_User_Token).
  • modules/auth/config/auth.php – Auth module configuration.

Different drivers: File and ORM

There are two different drivers for Auth. The ORM driver is most commonly used, and it stores and retrieves data in the database. The File driver is the alternative, which stores credentials in the auth module config file.

ORM functionality vs. Auth functionality

It is important to note that the Auth class models (user, role, token) extend the ORM base class. This means that all the ORM methods can be used e.g. to retrieve users using different conditions in the same way as other ORM models would.

Have a look at the ORM documentation for specifics.  The Auth functionality for the ORM driver is built upon the ORM, and it uses the existing functionality such as relationships ($_has_many), validation rules ($_rules, $_ignored_columns), callbacks ($_callbacks).

Much of what you will be doing with the users model will be adding extended information such as new relationships and new fields. As long as you keep the default relationships, rules and field names, you will be able to flexibly extend the user model to suit your needs.

The Auth singleton

Auth class (/modules/auth/classes/kohana/auth/orm.php):

  • Auth::instance(). Retrieves the instance of the Auth singleton.
  • logged_in($role = NULL). If called without arguments, checks whether the user is logged in. If an argument is given, then checks whether the logged in  user has the given role (string/Role object) or roles (array of strings / Role objects).
  • get_user(). Retrieves the user model for the user who is currently logged in (or FALSE).
  • login(array & $array, $redirect = FALSE). The array should contain the password and username. If the redirect parameter is set, then the login will be redirected to the given URL.
  • logout(). Logs the current user out.
  • force_login($username). Forces the username given as the first argument to log in – without a password.
  • auto_login(). Logs in the user automatically.

The User model

Model_Auth_User defines two relationships:

// Relationships
protected $_has_many = array
(
	'user_tokens' => array('model' => 'user_token'),
	'roles'       => array('model' => 'role', 'through' => 'roles_users'),
);

These relationships connect the user to Roles and tokens. Roles are particular sets of rights, such as “admin”. Tokens are used for two purposes: automatic login using cookies and password reset using password reset tokens. A user can have multiple tokens.

Model_Auth_User defines additional rules and callbacks for the username, email and password fields.

	// Rules
	protected $_rules = array
	(
		'username'			=> array
		(
			'not_empty'		=> NULL,
			'min_length'		=> array(4),
			'max_length'		=> array(32),
			'regex'			=> array('/^[-\pL\pN_.]++$/uD'),
		),
		'password'			=> array
		(
			'not_empty'		=> NULL,
			'min_length'		=> array(5),
			'max_length'		=> array(42),
		),
		'password_confirm'	=> array
		(
			'matches'		=> array('password'),
		),
		'email'				=> array
		(
			'not_empty'		=> NULL,
			'min_length'		=> array(4),
			'max_length'		=> array(127),
			'validate::email'	=> NULL,
		),
	);

It also provides a number of functions:

  • login(). First argument is an array containing username and password, the second argument is an optional URL to redirect to.
  • change_password(). Allows the password to be changed. First argument is an array containing password and password_confirm, second is an optional URL which if given causes the value to be saved immediately and redirected to the given URL.
  • username_available(). A callback used in the validation process which checks that the username is available when creating a user.
  • email_available(). A callback used in the validation process which checks that the email address is unique.
  • save(). A save() function that causes the password to be hashed before saving if it was changed.

The Role model

Roles have a name and a description (a line of text). They are associated with users through the roles_users table. The model does not implement any functions, only a number of rules stored in $_rules.

The User_token model

User_tokens have a unique token and an expiry date. The token can be generated by using create_token() and housekeeping (removing the expired tokens) can be performed using delete_expired(). When the token is saved, the user agent of the current request is saved.

Tokens are essentially unique authorizations either related to password resets or to auto login.