CSI: PHP

"Looking at your tweets I cannot even fathom what your job is. CSI:PHP?" — @grmpyprogrammer

Cover All the Bases

| Comments

Criminal? No, but it’s pretty damned funny. Something of a palate cleanser, if you will.

1
2
3
4
<?php
if (stripos(strToUpper($string), $needle) !== false) {
    // . . .
}

Happy Friday, have an awesome weekend, and we’ll see you next week.

Thanks to Philip Gabbert for today’s snippet.

No Register_globals, No Problem

| Comments

Today’s entry comes courtesy of CSI: PHP Investigator Westin Shafer.

“So I found this today in a commercial product that we purchased and I know you’ll want to pull your eyes out their sockets as I did when I found this. The script was originally written for php4 and now they’ve gone through the refactoring to make this php 5 compliant ::cough::. This script apparently relied on register_globals to be set (which most php apps at the time did). No problems, per-say, it was the trend back then… But now that PHP 5 is out and register_globals is no more, here’s this guys solution to fix the problem… Lazy like.

Who needs register globals when you can just mock up your own?”

1
2
3
4
5
6
7
8
9
10
11
<?php
function get_all_vars() {
    $array=array('_GET', '_POST', '_SESSION', '_COOKIE');
    foreach($array as $key => $var) {
        global $$var;
        foreach ($$var as $k => $v) {
            global $$k;
            $$k=$v;
        }
    }
}

Who Needs Regular Expressions?

| Comments

Thanks to Moses Ngone for submitting this super sweet router snippet.

Moses writes:

“If the regular expression route gets tough, be more specific.”

Indeed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

$vars = str_replace(".php", "", $_SERVER['REQUEST_URI']);
$vars = str_replace(".html", "", $vars);
$vars = str_replace("pics/",'',$vars);
$vars = str_replace("/pics",'',$vars);
$vars = str_replace("pix/",'',$vars);
$vars = str_replace("/pix",'',$vars);
$vars = str_replace("photos/",'',$vars);
$vars = str_replace("/photos",'',$vars);
$vars = str_replace("photo/",'',$vars);
$vars = str_replace("/photo",'',$vars);
$vars = str_replace("gallery/",'',$vars);
$vars = str_replace("/gallery",'',$vars);
$vars = str_replace("galleries/",'',$vars);
$vars = str_replace("/galleries",'',$vars);
$vars = str_replace("//","/",$vars);
$var_array = explode("/",$vars);

Pro tip: If you find yourself doing something like this, don’t. There are tons of excellent PHP frameworks that take care of routing for you. If you really want to write your own, spend some time reading some framework’s source code and unit tests to see how a few other folks have done it.

Templating Gone Horribly Wrong

| Comments

Using any form of templating, even if it’s just including a header.php and footer.php in each page, is supposed to make a developer’s life easier. Imagine the confusion the below code causes when, as you’re reviewing some new code, you run across a seemingly random </head> tag. Where is the rest of the markup, you might ask? Why, take a look at doPageHead(), silly.

1
2
3
4
5
6
7
8
9
10
<?php

function doPageHead($lang='en') {

    print "
    <html lang="$lang" xmlns="http://www.w3.org/1999/xhtml" xml:lang="$lang">
    <head>
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type"></meta>
    ";
}

I’m not a big fan of functions that simply echo HTML, but if you’re going to do something like this, at least include the closing head tag. Seriously. Or I might come find you and punch you square in the jeans.

Another, better option is to find a PHP templating engine that’ll do most of your templating for you.

I’m Not Sure I Can Think of a Worse Way to Do This

| Comments

If I think about it real hard, I might be able to find a worse way, but I don’t think I’m that smart.

Behold, the awesome power of xlate()! (FYI, I added the documentation and refactored variable names for clarity)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

/**
 * Translates a string from one language to another.  If the requested translation
 * is not found in the database, inserts new translation to the database and returns
 * original string.
 * 
 * @param resource $connection Database connection resource
 * @param string $databaseName
 * @param string $string String to translate
 * @param string $languageFrom Two letter representation of language (en, sp, etc.)
 * @param string $languageTo Two letter representation of language (en, sp, etc.)
 * @return string Translated string if $string found in db, orignal $string if not
 */
function xlate($connection, $databaseName, $string, $languageFrom, $languageTo)
{
    $query = "SELECT $languageTo FROM xlate WHERE $languageFrom = '$string' ";
    $result = doQuery($connection, $databaseName, $query);
    $rows = mssql_num_rows($result);
    if ($rows > 0) {
        $row = mssql_fetch_assoc($result);
        $translation = $row[$languageTo];
        if ($translation == '') {
            return $string;
        } else {
            return $translation;
        }
    } else {
        $query = "INSERT INTO xlate($languageFrom) VALUES('$string')";
        $result = doQuery($connection, $databaseName, $query);
        return $string;
    }
}

I found this function because an app started timing out while building a form, exceeding the maximum execution time of 30 secs. Turns out that for every single piece of text in that form, xlate() was being called, resulting in about 20 – 25 queries per page load.

A typical query looks like this.

1
SELECT en FROM xlate WHERE en = 'First Name *';

Considering the query is being called over and over in multiple loops, guess what that means? Loops within loops and queries within queries. FML.

The code is immediately suspect for another reason. Notice how there’s no key for the translation? The English text (complete with the “required field ‘ *’”) is used as the key, and coincidentally is exactly what’s being returned. That made me suspicious about the xlate table’s schema. Here it is.

1
2
3
4
5
6
7
8
9
10
11
12
13
-- SQL Server 2000
-- Translation table

CREATE TABLE [dbo].[db_xlate](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [en] [nvarchar](100) NULL,
    [sp] [nvarchar](100) NULL,
    [fr] [nvarchar](100) NULL,
    [de] [nvarchar](100) NULL,
    [it] [nvarchar](100) NULL,
    [ru] [nvarchar](100) NULL,
    [pt] [nvarchar](100) NULL
) ON [PRIMARY]

Brilliant! There’s no message/translation key, so if you want an English translation, you call the string that’s going to be returned anyhow in the where clause. If you want to add a new language, you have to have to alter the schema to include a new column for language, and then add a translation for every single English item in the table (or risk null returns during translations). If your translation isn’t found, the function adds the translation, meaning a new row in the DB with a null value for all other language columns. What’s perhaps craziest is the fact that you have to know the translation in order to retrieve the translation, otherwise a wrong translation might be added to the DB, making the entire implementation null, void, and dangerous.

Like I said, I don’t think I could do a worse job if I tried.

Doing it better

This one’s easy; Translation is a solved problem, don’t roll your own. Did you hear me? Do. Not. Roll. Your. Own. You’re wasting your time, you’re making life harder on yourself, and you’re making maintenance and continuing development near impossible for a maintenance programmer (or on the developer hired to take your place). This is a perfect example of how dangerous NIH syndrome can be.

My experience with translation has been with Zend Translate, and I’m sure there are many other great options out there is you’re not a Zend Framework person. Pick something and run with it, and don’t roll your own.

The Cure Is Worse Than the Disease

| Comments

Oh, snap.

1
2
3
4
5
6
7
8
9
10
<?php

function is_really_int(&$var) {
    $num = (int) $var;
    if ($var == $num) {
        $var = $num;
        return true;
    }
    return false;
}

This little bundle of joy really caught my attention. It’s the exact type of code that drove me to launch CSI: PHP.

Context

Apparently, the developer wanted is_int(sqrt(16)) to return true. Since the return value of sqrt() is always a float, the dev wrote is_really_int() to return true whenever the return value of sqrt() could be accurately represented as an integer.

The horror

Fair enough. I even feel dude’s pain. But the cure is worse, oh so much worse, than the disease.  Here are a few reasons why.

  1. Userland method and function names should be descriptive.  I want to be able to know exactly (or have a good idea, at least) what’s going to happen when I call your function.  If is_int() returns false, then why is asking, “Really?” going to change matters?
  2. Passing by reference? Why? Functions prefaced by “is” should do one thing only: test something and return a boolean.  Changing the value of $var when the input can be an int violates this principle, is totally unexpected, and will lead to unexpected behavior in your application.
  3. This is the kind of code that you’ll look at 6 months later and think, “Now what the hell does this do again?”  You’ll want to shoot yourself in the face when you finally figure it out.
  4. This is the kind of code that turns a maintenance developer into a homicidal maniac, and the maintainer is a bad mother— (Shut your mouth!). I’m just talkin’ about funkatron! (Then we can dig it)
  5. The function returns wildly unexpected results (and I can prove it).

Something better?

Again, I feel dude’s pain.  If I get a float that can be represented as an integer, I’d like to be able to test for that without jumping through hoops.  I whipped this up to try and solve the problem without the issues caused by is_really_int().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

/**
 * Tests if $var can be accurately represented as an integer.
 * Note that boolean values return false, just like is_int()
 * 
 * @param mixed $var Value to test
 * @return boolean 
 */
function valueCanBeInt($var)
{
    if (is_numeric($var) && $var == (int) $var) {
        return true;
    }

    return false;
}

It’s true that I might wonder what the hell valueCanBeInt() does 6 months down the road, but a quick glance at the documentation and the function will make it clear. I might even come up with a better name for the function at that point and do a little refactoring. I’d be able to refactor with confidence, as I’ve written some …

Unit tests

Like I said, this one really captured my attention. I’ve written some unit tests to demonstrate to myself and to you the dangerous side effects of is_really_int(). I ran the same tests against valueCanBeInt. The code and tests are available on github, if you’re interested.

UPDATE: I totally forgot to thank @Tyr43l for the submission. Thanks Ferenc!

Katy Perry Nude Pics - Available Now at PECL!

| Comments

If this isn’t criminally hilarious, I don’t know funny.

Zoomed in a bit:

Somehow, I feel obligated to warn you against visiting the link to the “NUDE-EXCLUSIVE-VIDEO” page, as if any of you would.

Thanks to reader Dan for the link.

style.css.php

| Comments

The concept makes sense – minify static assets) in order to make your pages smaller and therefore more responsive. The execution leaves quite a lot to be desired.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

// minifying the CSS
header('Content-type: text/css');
ob_start("compress");

function compress($buffer) {
    // remove comments
    $buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer);
    // remove tabs, spaces, newlines, etc.
    $buffer = str_replace(array("\r\n", "\r", "\n", "\t", '  ', '    ', '    '), '', $buffer);
    return $buffer;
}
?>

body {
    background-color: #FFF;
    color: black;
    margin: 0;
    font-family: Verdana, Arial, Helvetica, sans-serif;
    font-size: 10px;
}

table {
    margin: 0;
    padding: 0;
    border: 0;
}

// . . . snip . . .

<?php ob_end_flush(); ?>

Here are a few better ideas:

  • Don’t minimize anything at all. Is your site really that highly trafficked?
  • Manually minimize your CSS and JavaScript files using freely available tools.
  • Combine and minify your static assets during the build process using something like Phing.

God Help the Dev I Catch Doing This

| Comments

  1. Make decision to prematurely optimize application
  2. Choose the least helpful optimization first
  3. Append the static asset text files with .php (AJS.jsmin.js.php in this case)
  4. Use the header() function to manage caching of static assets
  5. Include new awesome cache controlled files in application
  6. Leave it to maintenance programmer to resolve all the “Cannot modify header information” warnings
1
2
3
4
5
6
7
8
9
10
11
12
<?php
// let the user cache this for to speed up their experience -- 10/23/2008
header("Content-type: text/javascript; charset: UTF-8");
header("Cache-Control: must-revalidate");
$offset = 60 * 60 * 24 * 90;90// 90 days for starters
$ExpStr = "Expires: " .
gmdate("D, d M Y H:i:s",
time() + $offset) . " GMT";
header($ExpStr);
?>

AJS={BASE_URL:"",drag_obj:null,drag_elm:null,_drop_zones:[], . . .

(NOTE: The ellipsis above indicates snipped, minified javascript)

Along with promising to visit death upon developers using a technique like this, I’ll pass along at least one better idea. Here’s an alternative from Greg Aker in his post, “Bust ‘yo cache!”. Add yours in the comments

Not Confusing Enough

| Comments

No, go ahead, add a little more abstraction. Don’t mind me, I’m just trying to maintain it is all.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

class core_output
{

    static public function factory($output_engine, $module)
    {
        switch ($output_engine) {

            case 'smarty':
                $file = OUT_SMARTY;
                break;
            default:

                $file = OUT_SMARTY;

                break;
        }

        if (include($file)) {
            $class = 'core_output_' . $output_engine;
            if (class_exists($class)) {
                $presenter = new $class($module);
                if ($presenter instanceof core_output_common) {
                    return $presenter;
                }

                echo 'Invalid presentation class: ' . $output_engine;
            }

            echo 'Presentation class not found: ' . $output_engine;
        }

        echo 'Presenter file not found: ' . $output_engine;
    }

//end factory
}