Saturday, July 5, 2008

PHP POST Variable Not Detecting Dynamically Created Form Elements Through Javascript

Right now I'm working on a project where I need to generate an unlimited number of form elements. Let's say it's a T-shirt store. And we need to be able to add any number of sizes for each t-shirt design. We could do that a number of ways, but a simple way would be to add a Javascript link:
<a href="#" onclick="addSize('sizes');return false;">Add Size</a>
You would probably set up the elements in a table, like so:
<table id="sizes">
<tr>
<th>Size</th>
<th>Quantity</th>
</tr>
<tr>
<td><input name="txtSize[]" type="text"></td>
<td><input name="txtQuantity[]" type="text"></td>
</tr>
</table>
I like using the [] to make the form elements an array. I believe most browsers will automatically make a form element an array if it has the same name, but I like to be doubly sure. Then you could use the HTML DOM functions insertRow and insertCell.
function addSize(tableId) {
tableObj = document.getElementById(tableId);
numRows = tableObj.rows.length;
lastRow = tableObj.rows[numRows - 1];
numCells = lastRow.cells.length;
newRow = tableObj.insertRow(numRows);
for(var i = 0; i < numCells; i++) {
oldCellHTML = lastRow.cells[i].innerHTML;
newRow.insertCell(i);
newCell = newRow.cells[i];
newCell.innerHTML = oldCellHTML;
}
}
All seems to work fine, you see the new fields populate. But once you post the form, you may run into the problem I did. There was only one element in each fields array.

I started looking into it, and couldn't figure it out. So I started writing this simple example, and it worked fine. But my actual project still didn't work. So I was on the hunt again to figure out why.

I found this post, which helped me find the issue with my original project. Turns out it all had to do with how you nest your form tag. My mistake was that I was putting my form tag between the table and tr tag, instead of nesting the entire table inside the form.

Private Variables Accessible via Print_r and Other Functions

I've recently been learning OOP (object oriented programming), and have started to adopt the common security practices that come with it in PHP 5. Like private, protected and public variables. Or rather, making variables private or protected (for inherited classes), and using get and set methods to limit access to those variables to certain users. Essentially, private variables cannot be read or written to except for within the object (via get and set methods). Protected variables are only accessible to the object or any object that inherits (extends) this class.

The php manual says this about the private and protected keywords.
The visibility of a property or method can be defined by prefixing the declaration with the keywords: public, protected or private. Public declared items can be accessed everywhere. Protected limits access to inherited and parent classes (and to the class that defines the item). Private limits visibility only to the class that defines the item.
http://www.php.net/manual/en/language.oop5.visibility.php
Now, I've recently discovered that this isn't exactly true. In general, it is true, but not exactly. The wording here is VERY misleading, and I know I"m not the only one that thinks so. Just how many people have been fooled by this, I'm not sure.

Try the following code, which is how I discovered the problem in the first place.
final class Database {
private $connection;
private $database;
private $host;
private $user;
private $password;

public function __construct($host = null, $database = null, $user = null, $password = null) {
// Some code that validates input
$this->connection = null;
$this->database = $database;
$this->host = $host;
$this->user = $user;
$this->password = $password;
$this->connect();

}

private function connect() {
$this->connection = mysql_pconnect($this->host, $this->user, $this->password);
if($this->connection) {
mysql_select_db($this->database, $this->connection);
}
return $this->connection;
}
}

$db = new Database('localhost', 'myDatabase', 'myUser', 'myPass');
print_r($db);
The following outputs:
Database Object
[connection:private] => Resource id #5
[database:private] => myDatabase
[host:private] => localhost
[user:private] => myUser
[password:private] => myPass
)
As you can see, those private variables aren't so private anymore. A regular print of any one of those variables fails, but a print_r of the object itself shows everything. Now, some of you might be thinking, "so don't print_r anything you want private", and you are missing the point. Part of the joy of OOP is that YOU aren't the only one developing your application. You may have a team of developers, and maybe you don't want anybody else on the team to have direct Database access, so you write the Database object that limits their access to it.

The following is a bug report on this, which was basically ignored, despite the obvious concern. http://bugs.php.net/bug.php?id=39118 Which also brings up that var_dump and var_export both do the same thing.

This sounds like a bug to me, as this defeats the purpose of private and protected variables. True, you can disable print_r, var_dump and var_export, through your php.ini but that seems like a crappy solution, as it eliminates alot of functionality. Just because you don't want to show private and protected variables, doesn't mean you don't want to have to disable print_r and write a new function to do the same thing, without showing private and protected variables. Because you can't just rewrite print_r, even if it is disabled because:
PHP does not support function overloading, nor is it possible to undefine or redefine previously-declared functions.
http://us2.php.net/manual/en/language.functions.php

Note: A function name may exist even if the function itself is unusable due to configuration or compiling options
http://us2.php.net/manual/en/function.function-exists.php

So there really isn't a way for a developer to change the way the behavior of the print_r function when it comes to private and protected variables. And if you thought the magic function __toString might do it, nope, it only works when concatinating strings or with echo and print.

And you can't really expect PHP to change the way print_r works, unless they finally do admit it is a bug. So the more logical remedy would be to have a magic function that would be called whenever the print_r, var_dump or var_export functions was called, similar to __toString. That way, print_r would still function as it does now, with the ability for the developer to increase their applications security without reducing functionality.