debuggable

 
Contact Us
 
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31

I believe in Symmetry

Posted on 26/8/08 by Felix Geisendörfer

Hey folks,

if there is one thing I have consistently noticed while working with other programmers than its the following:

Symmetry is beauty. People who get it are people who recognize and apply symmetry to their work.

If I see new people coming in on a project the first things I look for are this: Do they pick up the coding standard used so far? If not, is at least their own coding standard consistent? Are they using patterns and does everything they do have a distinct "style"? Do they pick up on the overall style of the project and copy the solution patterns used so far?

No matter how "good" a programmer is by himself - being able to work symmetrical is the most important factor for team work. Yes, its not always fun to keep sticking to some old patterns if you have so many new and better ideas. But usually the discipline of not polluting your project with too many different approaches creates beautiful and easy to maintain applications.

-- Felix Geisendörfer aka the_undefined

PS: Sorry for the short post, but we had a little CakePHP get together at my place last night which I'll write about later on today.

 

How to have multiple paginated widgets on the same page with CakePHP

Posted on 25/8/08 by Tim Koschützki

Hey folks,

many of you might have run into the problem of having multiple boxes on the same page that need to be paginated. For example you might have a left column with a list of members of your site and a right column that shows for a example a list of forums. Yeah, that's not the best example, but you get the idea.

So the problem with Cake's paginator is, that its link structure does not know which widget is actually paginated. Let's have a look at a typical url after you clicked a pagination link:

/users/view/488070d4-f5c8-41aa-87aa-4a951490b5da/page:2

Okay great. So imagine we have a UsersController with a view() action and our pagination passes in the page we would like to be on with regards to the list of users. Now, we click a pagination link in the communities box (let's call these boxes "widgets" from now on)... Yes you are right, the link will just look the same. So clicking on the pagination links in the community widget will just paginate our user list and the community list will stay at page 1. : /

Problem

How can we tell the CakePHP paginator which model it shall paginate?

The Solution

There are three things we need for this:

  1. We need to pass in the model that is being paginated to the structure.
  2. We need to ensure we stay on the same page type when we click on a pagination link.
  3. We need to extract the proper page variable from the url depending on the given model and put it into our pagination configuration.

1. Pass in the model that is being paginated to the structure

Okay what I would like to recommend to you here is to have a generic paging.ctp element, which could contain the following code:

if (!isset($paginator->params['paging'])) {
  return;
}
if (!isset($model) || $paginator->params['paging'][$model]['pageCount'] < 2) {
  return;
}
if (!isset($options)) {
  $options = array();
}

?>
<div class="paging">
  <?php
  echo $paginator->prev('<< Previous', array_merge(array('escape' => false, 'class' => 'prev'), $options), null, array('class' => 'disabled'));
  echo $paginator->numbers(am($options, array('before' => false, 'after' => false, 'separator' => false)));
  echo $paginator->next('Next >>', array_merge(array('escape' => false, 'class' => 'next'), $options), null, array('class' => 'disabled'));
  ?>
</div>

By that you ensure that the pagination texts are consistent throughout your application - something that is desirable in most cases. However, the most important reason for this is that we have all the fancy logic to make this hack work in one place and reusable for all widgets.

Here is how you would call the paging element for your Users index list out of your views:

<?php
echo $this->element('paging', array('model' => 'User'));
?>

Let's go over the element's code now. Firstly, we check if we need to do paging - if a model is supplied and if its pageCount is greater than 1. We then supply a link structure for the paging links, including a previous and a next link and numbers. If you have worked with Cake's pagination before, this should be all very familiar to you.

Now on to making our fancy logic work.

if (!isset($paginator->params['paging'])) {
  return;
}
if (!isset($model) || $paginator->params['paging'][$model]['pageCount'] < 2) {
  return;
}
if (!isset($options)) {
  $options = array();
}

$options['model'] = $model;
$options['url']['model'] = $model;
$paginator->__defaultModel = $model;
?>
<div class="paging">
  <?php
  echo $paginator->prev('<< Previous', array_merge(array('escape' => false, 'class' => 'prev'), $options), null, array('class' => 'disabled'));
  echo $paginator->numbers(am($options, array('before' => false, 'after' => false, 'separator' => false)));
  echo $paginator->next('Next >>', array_merge(array('escape' => false, 'class' => 'next'), $options), null, array('class' => 'disabled'));
  ?>
</div>

We are passing the model to the $options array and set a private paginator variable to the $model value. Yeah, that's the ugly part about this hack. However, since we have it only in one place we can live with it. With that code in place, your links render fine and supply the model that needs to be paginated. Our link would now look like:

/users/view/488070d4-f5c8-41aa-87aa-4a951490b5da/page:2/model:User

Keep in mind we have this all configurable when calling the paging element. You can even pass in your own options array to the element if you want to go any further with this.

2. We need to ensure we stay on the same page type when we click on a pagination link

Imagine that you want to display your paginated widgets not only on one page, the users index, but also on /users/view/[ID]. How do we let the paginator know we want to stay on the same user page? Let me rephrase, how do we supply the user ID in a consistent manner to the pagination links, so that if a pagination link in a widget is clicked, it redirects you back to /users/view/[ID], but with the widget being at the proper page.

The code for this is relatively easy, but I am not so happy with the solution yet. What I did is check for a certain variable that would *only* occur on the said pages, like /users/view:

if (isset($community)) {
  $options['url'][] = $community['Community']['id'];
} elseif (isset($user)) {
  $options['url'][] = $user['User']['id'];
}

So, if a $user variable is set, we assume we are on a user specific page. Same for a community... , a forum post, etc.. I feel bad about this part, because that could so easily be buggy. I won't present another approach I had in mind for this, because I would like you guys to discuss the one presented here first. : ]

Here is the pagination element in its entirety:

if (!isset($paginator->params['paging'])) {
  return;
}
if (!isset($model) || $paginator->params['paging'][$model]['pageCount'] < 2) {
  return;
}
if (!isset($options)) {
  $options = array();
}

$options['model'] = $model;
$options['url']['model'] = $model;
$paginator->__defaultModel = $model;

if (isset($community)) {
  $options['url'][] = $community['Community']['id'];
} elseif (isset($user)) {
  $options['url'][] = $user['User']['id'];
}
?>
<div class="paging">
  <?php
  echo $paginator->prev('<< Previous', array_merge(array('escape' => false, 'class' => 'prev'), $options), null, array('class' => 'disabled'));
  echo $paginator->numbers(am($options, array('before' => false, 'after' => false, 'separator' => false)));
  echo $paginator->next('Next >>', array_merge(array('escape' => false, 'class' => 'next'), $options), null, array('class' => 'disabled'));
  ?>
</div>

3. Extract the proper page variable from the url

So it seems we got the frontend part done. Let's have a look at some code for the backend to make this work. For the users list, your code to paginate the users might likely look like this:

$this->paginate['User'] = array(
  'contain' => array('Profile'),
  'order' => array('User.name' => 'asc'),
  'limit' => 20
);
$users = $this->paginate('User');

Straightforward, we need 20 users paginated. Now we need to investigate the url to check if the user model is given in a reusable manner, so that this will work for all paginations of all models:

/**
 * undocumented function
 *
 * @param string $model
 * @return void
 * @access public
 */

  function pageForPagination($model) {
    $page = 1;
    $sameModel = isset($this->params['named']['model']) && $this->params['named']['model'] == $model;
    $pageInUrl = isset($this->params['named']['page']);
    if ($sameModel && $pageInUrl) {
      $page = $this->params['named']['page'];
    }

    $this->passedArgs['page'] = $page;
    return $page;
  }

The trick is this handy little function that you place in your AppController class. It analyzes the url's given model and extracts the page. If the model is not given, it defaults to page 1. By that we ensure the widget that is being paginated is put on the right page and the other ones stay at page 1.

Here is the rewritten controller code:

$page = $this->pageForPagination('User');
$this->paginate['User'] = array(
  'contain' => array('Profile'),
  'order' => array('Profile.name' => 'asc'),
  'limit' => 20,
  'page' => $page
);
$users = $this->paginate('User');

Don't forget to supply the calculated page in your pagination config.

Now you are totally free for the other widgets. For example the pagination code for your community widget would look like this now:

$page = $this->pageForPagination('Community');
$this->paginate['Community'] = array(
  'contain' => false,
  'order' => array('Community.title' => 'asc'),
  'limit' => 5,
  'page' => $page
);
$communities = $this->paginate('Community');

Alrighty, that's it for paginating multiple widgets on the same page with CakePHP. As you see, the hack did not involve a lot of fancy logic, which really is a plus for Cake's paginator. You might wonder why CakePHP does not offer this functionality in the first place. Well, it doesn't yet, but since pagination might be redone in the future chances are good it will support this out of the core at some point.

Also keep in mind we want to promote the use of the controller's $paginate property. In this article I overwrite it for the action used. You might want to move the parameters that aren't changed up to the $params property initialisation.

Have a good one!

-- Tim Koschuetzki aka DarkAngelBGE

 

Simple Data Access Control

Posted on 25/8/08 by Felix Geisendörfer

Hey folks,

this is post #6 of my 30 day challenge.

If your application is like most, then you have some basic permission requirements for your data. A simple scenario is the following. Blog posts can only be edited by their owners and administrators. Same goes for viewing unpublished blog posts. Given that requirement I usually wrote code like this in the past:

class PostsController extends Controller{
  function view($id = null) {
    $post = $this->Post->findById($post);
    Assert::notEmpty($post, '404');
    $ownPost = $post['Post']['user_id'] == User::get('id');
    Assert::true($post['Post']['published'] || ($ownPost || User::isAdmin()), '403');
  }

  function edit($id = null) {
    $post = $this->Post->findById($post);
    Assert::notEmpty($post, '404');
    $ownPost = $post['Post']['user_id'] == User::get('id');
    Assert::true($ownPost || User::isAdmin(), '403');
  }
}

Note: See this post about the Assert implementation. If you wonder about the User::get('id') call - that is part of our custom Auth system we hope to publish at some point.

One could argue that the above is not ideally DRY and therefor should be refactored. In the past I probably would have followed that logic. However, these days I'm more often like fuck DRY if a little dupplication here and there makes code easier to read and to maintain (yes, that is very much possible with certain one liners).

Anyway, what I don't like about the above is that I feel the logic for deciding record permissions falls into the Model realm. It simply makes more sense if you think of it as a business requirement which should be abstracted in the model layer. So here is how I deal with the problem these days:

class User extends AppModel{
  static function can($action, $record, $options = array()) {
    if (is_string($options)) {
      $options = array('model' => $options);
    }
    $options = array_merge(array(
      'action' => $action,
      'model' => key($record)
    ), $options);
    if (method_exists($options['model'], 'userCan')) {
      return call_user_func(array($options['model'], $method), $record, $options);
    }
    return $record[$options['model']]['user_id'] == User::get('id');
  }
}

class Post extends AppModel{
  static function userCan($record, $options) {
    if ($options['action'] == 'view' && $record['Post']['published']) {
      return true;
    }
    return ($record['Post']['user_id'] == User::get('id')) || User::isAdmin();
  }
}

class PostsController extends Controller{
  function view($id = null) {
    $post = $this->Post->findById($post);
    Assert::notEmpty($post, '404');
    Assert::true(User::can('view', $post), '403');
  }

  function edit($id = null) {
    $post = $this->Post->findById($post);
    Assert::notEmpty($post, '404');
    Assert::true(User::can('edit', $post), '403');
  }
}

This is quite some code for this refactoring, yet I found it extremly nice to work with this pattern torwards data access control. It is fairly light weight compared to most other approaches, yet gives you a per-Model and per-Context kind of flexibility on access control.

Let me know if you have any questions!

HTH,
-- Felix Geisendörfer aka the_undefined

 

Two CakePHP tricks

Posted on 24/8/08 by Felix Geisendörfer

Hey folks,

here we go with post #5 of my 30 day challenge

Tip 1: Debugging frequently called functions

Imagine you got 100 assertions in the unit test of a function. Suddenly one or two unit tests fail. The returned result makes no sense whatsoever and you need to debug the function. If you just put a debug() statement in, you will see hundreds of outputs and would not know which one belongs to the case you are interested in. My favorite solution to this problem is to use the Configure class as a toggle for showing the debug information. But lets look at an example:

$expected = array(2);
$r = Set::extract('/User[2]/id', $a);
$this->assertEqual($r, $expected);

// Test that is failing
$expected = array(4, 5);
Configure::write('debug', 1);
$r = Set::extract('/User[id>3]/id', $a);
Configure::write('debug', 0);
$this->assertEqual($r, $expected);

$expected = array(2, 3);
$r = Set::extract('/User[id>1][id<=3]/id', $a);
$t his->assertEqual($r, $expected);

// Function to debug
function extract() {
  // do stuff
  debug($valueOfInterest);
}

This makes sure you only get the debug output for the one particular case you are interested in.

Tip 2: Custom config file

This is a very simple one. To make your application configurable via a global config file, all you need to do is the following. Put this code inside your /app/config/bootstrap.php file:

Configure::load('config');

This causes a file called /app/config/config.php to be included. You can of course use any other name, but I like this one. Created the file inside /app/config and then fill it with code like this:

$config = array(
  'App.name' => 'Debuggable.com',
  'App.guestAccount' => 'guest@debuggable.com',
  'App.feedbackEmail' => 'feedback@debuggable.com',
  'App.loginCookieLife' => '+1 year',
);
date_default_timezone_set('UTC');

Note: CakePHP expects an array named $config to be present in this file. So you could use App => array(name => val) to assign the sub values of the App key, however CakePHP also use the same key (App) for some of its settings. So you shouldn't completely overwrite it unless you know what you are doing. The keys used by CakePHP right now are: 'base', 'baseUrl', 'dir', 'webroot' and 'encoding'.

You can now globally access any of your config values. Here is an example inside a layout for setting the title element:

<title><?php echo $title_for_layout.' :: '.Configure::read('App.name'); ?></title>

Alright, I hope you found something useful in the tips above,

-- Felix Geisendörfer aka the_undefined

 

Private methods - Follow Up

Posted on 23/8/08 by Felix Geisendörfer

Hey folks,

as I expected almost everybody disagreed with my views on private methods. Here is the deal:

If you can use the private / protected feature of your language to do more good than evil in the world, all power to you.

If you have doubts ... read on.

First I have to make some confessions. I have not studied computer science. I have not done much Java. I have little trust in theory that I cannot apply to the real world. I do not think modern programming is much of a scientific endeavor. I believe the best code is no code at all. And I think there are lots of reasons to question anything people call fundamentally true.

But more than everything else, I believe the following:

We are in the stone age of programming. We are really just talking Fire, Water, Wind and Earth here.

It has only been 50 years since modern programming appeared. Chances are that 100 years from now most of what we consider good ideas and best practices will seem like a joke. But here I go lecturing you people with what in reality isn't more than a bunch of hunches and ideas of some guy without formal education in the field.

But ... I do have some experience with applications heavily using private functions. Once upon a time I worked on a CakePHP application. It was big. 300++ database tables. We're talking about what is probably the biggest CakePHP install out there. The previous programmers seemed to love private methods. They made sure to put 4-5 in at least every controller for good measure. I refactored this code, at least a lot of it. And I noticed a consistent pattern: 9 out of 10 times private methods were used as a rug that shit the programmers didn't want to architect properly was swept under.

Now this is just one example Felix, you might say. You haven't seen my glorious use of this most awesome of all OOP concepts. And you are right. I haven't. But I want my theory to be true. I feel I'm onto something. I saw this pattern to emerge every time I looked at private functions. And I either want you guys to proof me that I'm an idiot (I can live with that, no worries) or see the elegance of avoiding private methods yourself.

So I decided to join Nate Abele's I will insult your code challenge. Send me some CakePHP code that you cannot imagine to be elegant without private or protected methods / properties. Limit yourself to a class or two tops (like controller + model). Send this code to me. I will either refactor it to my idea of beauty and elegance or admit my mistake : ).

This is not exactly going to be a scientific proof or anything close, but as long as I might be able to convince or get myself convinced somebody is going to learn from it!

-- Felix Geisendörfer aka the_undefined

 
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31