Matt Pierce

Web Developer

Utilities

If you look through the code from the old Montclair website you'll find utilities.php referenced just about everywhere. You might think that you know what it is - it's a bundle of convenience functions that I used to speed up development - but that's really only how it started.

Excerpts are presented below, but you can also view the full source.

Actual Convenience Functions

In reality there are only three functions here that I would group into being general convenience functions.

json_indent

This function pre-dates the JSON_PRETTY_PRINT flag in PHP's json_encode() function. It takes a string of JSON data and applies indentation to make it easier to read.

function json_indent($json) {

    $result      = '';
    $pos         = 0;
    $strLen      = strlen($json);
    $indentStr   = '  ';
    $newLine     = "\n";
    $prevChar    = '';
    $outOfQuotes = true;

    for ($i=0; $i<=$strLen; $i++) {

        // Grab the next character in the string.
        $char = substr($json, $i, 1);

        // Are we inside a quoted string?
        if ($char == '"' && $prevChar != '\\') {
            $outOfQuotes = !$outOfQuotes;

        // If this character is the end of an element, 
        // output a new line and indent the next line.
        } else if(($char == '}' || $char == ']') && $outOfQuotes) {
            $result .= $newLine;
            $pos --;
            for ($j=0; $j<$pos; $j++) {
                $result .= $indentStr;
            }
        }

        // Add the character to the result string.
        $result .= $char;

        // If the last character was the beginning of an element, 
        // output a new line and indent the next line.
        if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
            $result .= $newLine;
            if ($char == '{' || $char == '[') {
                $pos ++;
            }

            for ($j = 0; $j < $pos; $j++) {
                $result .= $indentStr;
            }
        }

        $prevChar = $char;
    }

    return $result;
}

GetIncludingFile

This function will tell you what file included the code that's currently running. Although I only used this a few times, the idea was that I didn't want to make it possible for someone to include a settings file and read the database credentials so the settings file would check to see who included it. If it was included from an unexpected place, it would unset the settings data structure.

function GetIncludingFile() {
    $file = false;
    $backtrace =  debug_backtrace();
    $include_functions = array('include', 'include_once', 'require', 'require_once');
    for ($index = 0; $index < count($backtrace); $index++)
    {
        $function = $backtrace[$index]['function'];
        if (in_array($function, $include_functions))
        {
            $file = $backtrace[$index]['file'];
            break;
        }
    }
    return $file;
}

This , of course, would never prevent anybody from using file_get_contents() to get the same information so at best it just makes things one step harder for someone who gets into our system.

ObjectToArray

This function turns a PHP object (stdClass - though this function far pre-dates that nomenclature) into an associative array. I'm relatively certain I copied & pasted this from StackOverflow because I normally don't put a new line before an opening brace.

function ObjectToArray($data) 
{
  if(is_array($data) || is_object($data))
  {
    $result = array(); 
    foreach($data as $key => $value)
    { 
      $result[$key] = ObjectToArray($value); 
    }
    return $result;
  }
  return $data;
}

This one is actually here as a helper function for the rest of the file.

MySQL Migrations

The rest of the convenience functions in the file were actually written to help us do in-place upgrades to PHP. A handful of common MySQL tasks were abstracted away into this file so that we could support the three different paradigms of MySQL code - functional code with a global connection, functional code with a connection handle, and object-oriented code.

MySQLi Helper Functions

The first thing I would need to do is to detect what kind of mode we're running in - object-oriented or not. That's easy enough to turn into a quick one-line check.

function IsMySQLi($db_link) {
    if (gettype($db_link)=="object") {
        return true;
    } else {
        return false;
    }
}

We also had a function for escaping strings, and this is where you get to see how the different connection paradigms come into play. The original version of this function didn't even have the $db_link parameter - it assumed a global connection. Later it was updated to have that optional parameter so that code could be transitioned to using a handle to reference the connection. Then, later still, the function was updated to detect whether we were in object-oriented mode.

The obvious question here is "why not just update your various applications?" and the answer is PHP version mismatches between sandboxes, test environments and production environments. I needed my code to be able to function on multiple versions of PHP simultaneously so that I could keep making updates to the production website while also preparing for future PHP upgrades on my sandbox.

function GenericEscape($str, $db_link=null) {
    if ($db_link && IsMySQLi($db_link)) {
        return $db_link->escape_string($str);
    } else if ($db_link) {
        return mysql_real_escape_string($str, $db_link);
    } else {
        return mysql_real_escape_string($str);
    }
}

The remaining functions are fairly long so I won't display them in their entirety (especially since they more or less do the same thing 3 different ways), but they helped to automate some repetitive tasks. I never wanted to let my own laziness compromise security so MySQL insertions and updates are handled through two functions called GenericInsert and GenericUpdate.

The first thing these functions do is to get a description of the MySQL table, unless one was provided.

function GenericInsert($data, $table, $Description=null, $db_link=null) {
    // data should be an associative array of $data["field"] = value;
    $table = GenericEscape($table, $db_link);

    // get table description (if not supplied)
    if (!$Description) $Description = GetTableDescription($table, $db_link);

The next step is to run through all of the supplied data (in associative array form - this is why I put ObjectToArray() in the file) and build two arrays of field names and values - but also to sanitize those values based on the field type.

If a piece of data is supplied that's not in the table description, or if the data isn't in one of the formats I support, I simply ignore it.

// get our fields & values
$fields = null;
$values = null;
foreach ($data as $key => $val) {
    if ($val === NULL) {
        $fields[] = $key;
        $values[] = "NULL";
    } else if (isset($Description[$key])) {
        if (strpos($Description[$key]->Type, "enum")!==FALSE) {
            $fields[] = $key;
            $values[] = "'".GenericEscape($val, $db_link)."'";
        } else if (strpos($Description[$key]->Type, "int")!==FALSE) {
            $fields[] = $key;
            $values[] = intval($val);
        } else if (strpos($Description[$key]->Type, "varchar")!==FALSE) {
            $fields[] = $key;
            $values[] = "'".GenericEscape($val, $db_link)."'";
        } else if (strpos($Description[$key]->Type, "text")!==FALSE) {
            $fields[] = $key;
            $values[] = "'".GenericEscape($val, $db_link)."'";
        } else if (strpos($Description[$key]->Type, "datetime")!==FALSE) {
            $ts = strtotime($val);
            if (! ($ts===false || $ts===-1)) {
                $fields[] = $key;
                $values[] = "'".date("Y-m-d H:i:s", $ts)."'";
            }
        } else if (strpos($Description[$key]->Type, "date")!==FALSE) {
            $ts = strtotime($val);
            if (! ($ts===false || $ts===-1)) {
                $fields[] = $key;
                $values[] = "'".date("Y-m-d", $ts)."'";
            }
        } else if (strpos($Description[$key]->Type, "time")!==FALSE) {
            $ts = strtotime($val);
            if (! ($ts===false || $ts===-1)) {
                $fields[] = $key;
                $values[] = "'".date("H:i:s", $ts)."'";
            }
        }
    }
}

And then finally I build a MySQL insert statement and attempt to insert based on whatever the MySQL connection paradigm is.

// attempt to insert
$fields = implode(", ", $fields);
$values = implode(", ", $values);
$q = "INSERT INTO $table ($fields) VALUES ($values)";
if (IsMySQLi($db_link)) {
    return $db_link->query($q);
} else if ($db_link) {
    return mysql_query($q, $db_link);
} else {
    return mysql_query($q);
}

The function GenericUpdate() works pretty much the same way, pulling all the data together, escaping based on the field type in the table description, then building a query with the results. However, there's one important difference: GenericUpdate() can take a $where parameter, which would be the "where" clause in your MySQL statement. If you leave that blank (which I usually did), the function will find the primary key in the table description and use the provided value.

Note: in this excerpt, $tmp is a string in the format of field=(escaped value).

if ($where==null && $Description[$key]->Key=="PRI") {
    $where = $tmp;
} else {
    $Updates[] = $tmp;
}

The one critical problem with this set of functions is that if you need to insert a lot of data it can really bog things down if you're also pulling the table description before each insertion. To that end I have another helper function that will pull the table description once, then pass it to GenericInsert() over multiple calls.

function MassInsert($data, $table, $db_link=null) {
    $description = GetTableDescription($table, $db_link);
    if ($description) {
        foreach ($data as $d) {
            GenericInsert($d, $table, $description, $db_link);
        }
    } else {
        return false;
    }
}

And that pretty much covers the utilities file. In my time working at Montclair we've had to re-factor our MySQL code site-wide as PHP upgrades depreciated the paradigms we were using and both times the utilities file helped us to handle the transition period without any interruption in service.