debuggable

 
Contact Us
 
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47

False == 0, or not?

Posted on 13/8/07 by Felix Geisendörfer

So far I've always thought false would evaluate to 0 when used in a computational context. Turns out that this isn't always the case.

// The world as we know it:
echo 0 + 1; // 1
echo false + 1; // 1
echo 0  > -1; // true

// But what is that ...?
echo false  > -1; // false

Now if anybody does have an explanation for this, I'd be glad to hear it. I randomly stumbled upon this when arguing with Mariano today if setting Model::recursive to 'false' has the same effect as setting it to '-1'. Turns out that cake uses a statement like this: if ($recursive > -1) in the core which in turn makes -1 and false do exactly the same thing.

Btw., if you need to work around this behavior you can use something like this:

echo (int)false > -1; // true

More posts are to come,
-- Felix

 

Encode HTML entities with jQuery

Posted on 21/7/07 by Felix Geisendörfer

Hey folks,

just a quick tip, if you ever need to use something like PHP's htmlentities() function in JS and happen to use the excellent jQuery library, here is how to do it:

$('<div/>').text('This is fun & stuff').html(); // evaluates to "This is fun &amp; stuff"

Enjoy the magic,
-- Felix Geisendörfer aka the_undefined

 

Getting serious with jQuery - Adding custom CSS selectors

Posted on 17/7/07 by Felix Geisendörfer

Hey folks,

in my upcoming task management application (which will btw. *fix* task management : ) I'm doing a *lot* of Javascript. In fact I want to make a statement before beginning this post: Javascript is now officially my favourite language. But don't worry, I won't give up on the PHP/etc. for the server side just yet (seen what John Resig has been up to as of lately?) - JS doesn't compete in that category so far. I will however probably start to spend equal amounts of time working in JS and PHP which means you could see a lot more JS on this blog in future (you know, the future where I'll have time for some hardcore blogging again : ).

Anyway, back to the topic - I'm writing a task management application and this is a greatly simplified version of the markup I'm using:

<table class="tasks">
    <tr>
        <th>Task</th>
        <th>Time</th>
    </td>
    <tr class="task">
        <td>Finish project X</td>
        <td>1 hour</td>
    </tr>
    <tr class="task">
        <td>Write a blog entry</td>
        <td>1 hour 30 minutes</td>
    </tr>
</table>

Since I'm getting fancy with JS these days I've written some jQuery plugins that allow me to interact with my DOM in a very nice way:

$('tr.task:nth(1)')
    .task('field', 'name', 'Write a good blog entry')
    .task('field', 'time', 2*60)
    .task('save');

The sample above will select the second row (:nth is 0-indexed) in my table, change the 'Task' name to 'Write a good blog entry' and the 'Time' to '2 hours'. Pretty neat. But - I was lying earlier. At least partially. My actual markup is more complicated then the example above, but there is no 'task' class on the tr columns. Why? It's because I'm always looking for excuses to use jQuery's awesome selector engine and practice my CSS3 skills : ). So my code from above needs to be rewritten like this:

$('tr[:not(th)]:nth(1)')
    .task('field', 'name', 'Write a good blog entry')
    .task('field', 'time', 2*60)
    .task('save');

So what does that do? Well instead of selecting all rows with a class 'task' it selects all rows that do not contain a 'th' element which comes down to the same elements. But you all know I'm a friend of simplicity, so even if jQuery offers us some nice selectors it might be possible to get even fancier. I've been thinking about it for a while and came to the conclusion that what I really want this selector to look like is this:

$(':task(2)')
    .task('field', 'name', 'Write a good blog entry')
    .task('field', 'time', 2*60)
    .task('save');

Now that's crazy (!) I can hear some of your say, but I really wanted it so I've tried to figure out how. Today I came across one of Mike Alsup's pages (who btw. makes some of the best jQuery plugins out there) and finally saw how to do it. My initial attempt was very much oriented at how most selectors work and got pretty complex because this seems to be meant for more simple selectors. Anyway here is the code for it:

jQuery.expr[':'].task = '(i==0 && (arguments.callee.c = 0) || 1)
                        && /tr/i.test(a.nodeName)
                        && !jQuery.find("th",a).length
                        && (arguments.callee.c++ || 1)
                        && (m[3] === undefined || m[3] == arguments.callee.c)'
;

Here is the short version of what it does: This expression is called up for every element the jQuery CSS engine loops through. If it evaluates to true then the element "matches", otherwise it's not included in the resulting jQuery array. My expression here only matches if the element is a 'tr', does does not contain a 'th' and if the :task(n) parameter matches the nth-task that was found (kept track of in arguments.callee.c). This of course is a very unreadable as it heavily depends on JS's short circuit logic (something PHP has as well) so I came up with a more readable version of it:

// Selector for :task and :task(n)
jQuery.isTask = function(a, i, m) {
    // Reset our element counter if this function is called for the first time on the current set of elements
    if (i == 0) {
        arguments.callee.count = 0;
    }
    // If this is not a <tr> or contains <th> then we are not interested in it
    if (!/tr/i.test(a.nodeName) || jQuery.find('th', a).length) {
        return false;
    }
    // Increment our element counter
    arguments.callee.count++;
    // If no task# was given or it matches our current counter then return true
    return (!m || m[3] === undefined)
        ? true
        : arguments.callee.count == m[3];
};
jQuery.expr[':'].task = 'jQuery.isTask(a, i, m);';

As you can see, this should pretty much do the same thing but is easier to maintain. For those of you still totally confused some hints about the code: 'a' is always the element we currently test. 'i' is the index of the element we are currently testing, but since we only count the ones we are interested in, we do our own counting in arguments.calllee.c / arguments.calllee.counter. 'm' is the array matched by the regular expression in jQuery internally. The regex used there are rather complex but in our case m looks like this: m = [':task(2)', ':', 'task', '2', undefined];. So when we check against m[3] we're accessing the :task(n) parameter.

Soo, what value does this have if we're back at where our initial 'tr.task:nth(1)' has brought us? Good question ; ). How about this: Has anybody ever thought about providing a JS API for their application? Not one where you can remotely access the service - that's oldschool. No, I mean one where interested developers can directly hack the way your app works without using greasemonkey? I have, and I think there is some real potential to be unleashed here given that you can provide the right tools and security for the job. I mean I'm using Thunderbird for my emailing right now, but if Google would give me tools that would easily allow me to modify the way Gmail works to my liking - I'd be switching to it as my main interface right away! The possibilities this opens are really endless and I only see limited appliance in my little task management app. However some stuff like this surely would be worth implementing:

$(':task(@done)')
    .task('remove');

$(':task(@name*=work)')
    .task('field', 'date', 'tomorrow')
    .task('save');

Anyway, now you know what has been getting me really excited as of lately. It's yet another proof of what a beautiful piece of art jQuery is. I checked EXT + prototype today and it doesn't seem as easy to extend them like this (if I'm wrong, please let me know!). So it's bedtime for me now and I can finally do this for tonight:

$('*:task').task('remove');

Thanks for reading and hopefully you got some useful information out of this,
-- Felix Geisendörfer aka the_undefined

PS: If that technique I use to manipulate the tasks in my app interests you - I'll be releasing the JS script making it possible soon.

PS2: I did not proof read this post and hitting bed now - it probably contains more typos and grammar issues then usual ^^.

 

Composing Methods: Remove Assignments to Parameters

Posted on 6/7/07 by Tim Koschützki

When your code assigns to a parameter in a function/method, use a temporary variable instead.

Motivation

With assigning to a parameter I mean that when you pass in an object foo, you do not make it refer to a different object. Changing a parameter object is perfectly cool and I do that myself all the time. Just do not make it reference another object, like:

function aFunction($foo) {
  $foo->modify();
  $foo = new SomeOtherClass;
}

The reason this is so awkward is the confusion between pass by value and pass by reference in PHP. Whereas in PHP4 everything was
passed in by value as default, PHP5 passes objects around by reference. In PHP4 when you assign the parameter another object this will not be reflected in the calling routine, which can lead to quite nasty bugs if you forget to insert the by-reference operator (&).

In PHP5 this is not so much of a problem as it passes objects around by reference anyways. So if, in PHP5, you assign to a parameter you must have a good reason anyways, because you will change the parameter object also in the calling routine with your assignment. However, I find that in PHP5 there is another area of confusion making this refactoring worthwhile for both PHP4 and PHP5. That area of confusion is within the body of the code itself. It is much clearer to use only the parameter to represent what has been passed in, because that is consistent usage and therefore a good coding practice.

It will make your code much more readable and prevents by-reference confusion and therefore big problems in the future. In PHP5, assigning to an parameter object within a method will not be reflected outside the method. So I suggest you just don't do it anyways. There seems to be no point in it anyways.

Think of hairloss. ;] Haha, I seem to be bringing up hairloss all the time in connection with bug fixing. :]

Mechanics

  • Create a temporary variable for the parameter.
  • Replace all references to the parameter, made after the assignment, to the temporary variable.
  • Change the assignment to assign to the temporary variable.
  • If you are on PHP5 or PHP4 (using the by-reference operator), look in the calling method to see if the parameter is used again afterward and try to understand what is changed where in your code and document that. Make sure you only return one value from the method. If more than one call-by-reference parameter is assigned to and returned, try to use a data clump or Extract Method.

Code Example

Let's start with the following routine:

function price($inputVal, $quantity, $anotherFactor) {
  if ($inputVal > 50) $inputVal -= 30;
  if ($quantity > 20) $inputVal -= 5;
  if ($anotherFactor> 1000) $inputVal -= 2;
  return $inputVal;
}

echo price(50, 10, 1200);

Replacing with a temp leads to:

function price($inputVal, $quantity, $anotherFactor) {
  $result = $inputVal;
 
  if ($inputVal > 50) $result -= 30;
  if ($quantity > 20) $result -= 5;
  if ($anotherFactor> 1000) $result -= 2;
  return $result ;
}

echo price(50, 10, 1200);

An easy refactoring, yet quite cool. I do it all the time. Will you, too?

 

How Table Migrations In CakePHP 1.2 Can Save Your Life

Posted on 2/7/07 by Tim Koschützki

Migrations are a wonderful technique to keep your database in sync with your code. When working on a project as a team, migrations can save your life. Please join me and get a quick introduction to migrations in cakephp and become a happier coder. :]

Yes, they are finally here! Migrations in cake! They are so powerful - especially in a team programming environment. However, what are they in the first place?

What Are Migrations In CakePHP?

Migrations are .yml files that contain information about how to construct a new SQL table and how to drop it. A typical migration file looks as follows:

[code]
UP:
create_table:
posts:
user_id:
type: integer
title:
type: text
length: 255
body:
type: text
highlighted:
type: boolean
default: false
flagged:
type: boolean
default: false
resolved:
type: boolean
default: false

DOWN:
drop_table: [posts]
[/code]

Notice the "UP" and "DOWN" in the code. They represent what to do when you want to migrate "up" to the next database scheme version and what to do when you want to go down. The code above does not need much explanation I think. When migrating up, we create a table named posts, that has six fields with some default, type and length restrictions. When migrating "down", we drop that table again.

How Does One Execute Migrations?

For this to work you will need PEAR installed along with its MDB2 Package. Installing pear is as easy as executing a bat file on windows. Too bad I am a WAMP guy and haven't done much yet with Linux. However, detailed instructions on how to install pear under linux can be found here.

The second step is altering your php.ini's include_path to look in your pear installation, too, or else it will not be able to find the MDB2 package. My include_path directive looks like the following:

[code]
; UNIX: "/path1:/path2"
;include_path = ".:/php/includes"
;
; Windows: "\path1;\path2"
include_path = ".;C:\wamp\php\pear;"
[/code]

Note, that "." stands for "current directory". On windows you separate directories with a semicolon and on *nix with a colon. Php.ini's include_path is a powerful thing, allowing you to customise your php installation to a great deal. However, don't mess with it. :]

Important: PHP's command line interface will grab the ini file that is in the same directory as your php.exe or php-cli.exe (respectively). Set that one up correctly, especially enable the mysql module. Without mysql support, migrations will be useless. :]

The next step is to grab Joel Moss' Migration Shell Code. Place it into app/vendors/shells/ and then start up your shell. Navigate to your project folder and then into /cake/console.

Now type in php cake.php migrate and it will migrate to the latest version. With php cake.php migrate -v 3 you will migrate to the third migration and so on.

Your command could look like "php5 cake.php migrate", too. On windows you can register any php.exe with your "Path" environment variable.

A Bit More On Migration Files

Okay, now that we got it working, here is a bit more about the construction of migration files.

First thing to note is that migrations can only keep your code in sync with the database scheme. To insert some default data into your tables you will have to rely on Cakephp's fixtures, which I will write about in a later article.

Your migration files have to be placed in app/config/migrations and they need to include some kind of number at the beginning of the file name. This is to express the version number of the migration. Here is a possible migration folder:

[code]
001_initial_tables.yml
002_network_table.yml
003_posts_table.yml
003_comments_table.yml
004_tags_table.yml
[/code]

You can also include more than one table setup within one migration. Here is an example migratiion file:

[code]
#
# migration YAML file
#
UP:
create_tables:
tip_categories:
name:
fkey: parent
tips:
title:
content:
type: text
fkey: category
DOWN:
drop_tables: [tip_categories, tips]
[/code]

Should be self-explaining. Instead of the "create_table:" command we use "create_tables:" and go through the scheme data one table at a time.

Conclusion

Migrations are extremely powerful. They will save you much time, energey and prevent you from getting hairloss. Having a working codebase with the correct information in the database is all happy programmer needs on his local test installation, no? :]

If you run into problems getting migrations working, please let me know by commenting on this article. I will try my best to help you out.

Have a good one! By the way, that migraine is actually inspiring me to blog. :)

 
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47