Key Takeaways
- PHP can exhibit unexpected behavior, such as persisting the reference outside of the first foreach loop, leading to unexpected output, which can be mitigated by using the array’s keys to assign the string back.
- When using PHP for more complex tasks such as shell scripts, understanding how the execution environment is cloned when forking and how various resources can be affected across all the processes is vital. For instance, when connecting to a database, it’s wiser to connect in the parent thread after the children have been forked, and the children should connect themselves if they need to.
- Singletons, which are really nothing more than fancy OOP’ized global variables, can make debugging difficult. It’s recommended to avoid them where possible.
- While unconventional coding practices like “Spooky Scary PHP” can be fun and educational, they are not typically considered good practice for writing production code as they often involve using functions or techniques that are inefficient, unclear, or unpredictable.
The Haunted Array
Once upon a time in a development shop not so far away, Arthur was up late at night hacking out some code. Little did he know the array he was about to use was haunted! He felt a chill run down his spine as he typed out his statements, keystroke by keystroke, but foolishly brushed off his subtle premonition.<?php
$spell = array("double", "toil", "trouble", "cauldron", "bubble");
foreach ($spell as &$word) {
$word = ucfirst($word);
}
foreach ($spell as $word) {
echo $word . "n";
}
Alright, so the array wasn’t really haunted, but the output certainly was unexpected:
Double Toil Trouble Cauldron CauldronThe reason for this spooky behavior lies in how PHP persists the reference outside of the first
foreach
loop. $word
was still a reference pointing to the last element of the array when the second loop began. The first iteration of the second loop assigned “double” to $word
, which overwrote the last element. The second iteration assigned “toil” to $word
, overwriting the last element again. By the time the loop read the value of the last element, it had already been trampled several times.
For an in-depth explanation of this behavior, I recommend reading Johannes Schlüter’s blog post on the topic, References and foreach. You can also run this slightly modified version and examine its output for better insight into what PHP is doing:
<?php
$spell = array("double", "toil", "trouble", "cauldron", "bubble");
foreach ($spell as &$word) {
$word = ucfirst($word);
}
var_dump($spell);
foreach ($spell as $word) {
echo join(" ", $spell) . "n";
}
Arthur learned a very important lesson that night and fixed his code using the array’s keys to assign the string back.
<?php
foreach ($spell as $key => $word) {
$spell[$key] = ucfirst($word);
}
The Phantom Database Connection
More and more, PHP is asked to do more than just generate web pages on a daily basis. The number of shell scripts written in PHP are on the rise, and the duties such scripts perform are increasingly more complex, as developers see merit in consolidating development languages. Often times the performance of these scripts are acceptable and the trade off for convenience can be justified. And so Susan was writing a parallel-processing task which resembled the following code:#! /usr/bin/env php
<?php
$pids = array();
foreach (range(0, 4) as $i) {
$pid = pcntl_fork();
if ($pid > 0) {
echo "Fork child $pid.n";
// record PIDs in reverse lookup array
$pids[$pid] = true;
}
else if ($pid == 0) {
echo "Child " . posix_getpid() . " working...n";
sleep(5);
exit;
}
}
// wait for children to finish
while (count($pids)) {
$pid = pcntl_wait($status);
echo "Child $pid finished.n";
unset($pids[$pid]);
}
echo "Tasks complete.n";
Her code forked children processes to do some long-running work in parallel while the parent process continued on to monitor the children, reporting back when all of them have terminated.
Fork child 1634. Fork child 1635. Fork child 1636. Child 1634 working... Fork child 1637. Child 1635 working... Child 1636 working... Fork child 1638. Child 1637 working... Child 1638 working... Child 1637 finished. Child 1636 finished. Child 1638 finished. Child 1635 finished. Child 1634 finished. Tasks complete.Instead of outputting status messages to stdout though, Susan’s supervisor asked her to log the times when processing started and when all of the children finished. Susan extended her code using the Singleton-ized PDO database connection mechanism that was already part of the company’s codebase.
#! /usr/bin/env php
<?php
$db = Db::connection();
$db->query("UPDATE timings SET tstamp=NOW() WHERE name='start time'");
$pids = array();
foreach (range(0, 4) as $i) {
...
}
while (count($pids)) {
...
}
$db->query("UPDATE timings SET tstamp=NOW() WHERE name='stop time'");
class Db
{
protected static $db;
public static function connection() {
if (!isset(self::$db)) {
self::$db = new PDO("mysql:host=localhost;dbname=test",
"dbuser", "dbpass");
self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return self::$db;
}
}
Susan expected to see the rows in the timings table updated; the “start time” row should have listed the timestamp when the whole process was kicked off, and the “stop time” should have when everything finished up. Unfortunately, execution threw an exception and the database didn’t mirror her expectations.
PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General error: 2006 MySQL server has gone away' in /home/susanbrown/test.php:21 Stack trace: #0 /home/susanbrown/test.php(21): PDO->query('UPDATE timers S...') #1 {main}
+------------+---------------------+ | name | tstamp | +------------+---------------------+ | start time | 2012-10-13 01:11:37 | | stop time | 0000-00-00 00:00:00 | +------------+---------------------+Like Arthur’s array, had Susan’s database become haunted? Well, see if you can piece together the puzzle if I give you the following clues:
- When a process forks, the parent process is copied as the child. These duplicate processes then continue executing from that point onward side by site.
- Static members are shared between all instances of a class.
DB::connection()
first returned the object reference, the parent forked, the children continued processing while the parent waited, the children processes terminated and PHP cleaned up used resources, and then the parent tried to use the database object again. The connection to MySQL has been closed in a child process, so the final call failed.
Naively trying to obtain the connection again before the end logging query wouldn’t help Susan as the same defunct PDO instance would be returned because it’s a Singleton.
I recommend avoiding Singletons – they’re really nothing more than fancy OOP’ized global variables which can make debugging difficult. And even though the connection would still be closed by a child process in our example, at least DB::connection()
would return a fresh connection if you invoked it before the second query if Singletons weren’t used.
But still better would be to understand how the execution environment is cloned when forking and how various resources can be affected across all the processes. In this case, it’s wiser to connect to the database in the parent thread after the children have been forked, and the children would connect themselves if they needed to. The connection shouldn’t be shared.
#! /usr/bin/env php
<?php
$pids = array();
foreach (range(0, 4) as $i) {
...
}
$db = Db::connection();
$db->query("UPDATE timings SET tstamp=NOW() WHERE name='start time'");
while (count($pids)) {
...
}
$db->query("UPDATE timings SET tstamp=NOW() WHERE name='stop time'");
An API Worthy of Dr. Frankenstein
Mary Shelley’s Frankenstein is a story of a scientist who creates life, but is so repulsed by its ugliness that he abandons it. After a bit of gratuitous death and destruction, Dr. Frankenstein pursues his creation literally to the ends of the earth seeking its destruction. Many of us have breathed life into code so hideous that we later wish we could just run away from it – code so ugly, so obtuse, so tangled that it makes us want to retch, but it only wants love and understanding. Years ago I was toying around with an idea focused on database interfaces and how might they look if they adhered more closely to Unix’s “everything is a file” philosophy: the query would be written to the “file”, the result set would be read from the “file.” One thing lead to another, and after some death and destruction coding of my own, I had written the following class which has little relevance to my original idea:<?php
class DBQuery implements Iterator
{
protected $db;
protected $query;
protected $result;
protected $index;
protected $numRows;
public function __construct($host, $dbname, $username, $password) {
$this->db = new PDO("mysql:dbname=$dbname;host=$host",
$username, $password);
}
public function __get($query) {
$this->query = $query;
$this->result = $this->db->query($query);
return $this->numRows = $this->result->rowCount();
}
public function __call($query, $values) {
$this->query = $query;
$this->result = $this->db->prepare($this->query);
$this->result->execute($values);
return $this->numRows = $this->result->rowCount();
}
public function clear() {
$this->index = 0;
$this->numRows = 0;
$this->query = "";
$this->result->closeCursor();
}
public function rewind() {
$this->index = 0;
}
public function current() {
return $this->result->fetch(PDO::FETCH_ASSOC,
PDO::FETCH_ORI_ABS, $this->index);
}
public function key() {
return $this->index;
}
public function next() {
$this->index++;
}
public function valid() {
return ($this->index < $this->numRows);
}
public function __toString() {
return $this->query;
}
}
The result was genius, but repulsive: an instance which looked like an object (with no real API methods), or an array, or a string…
<?php
$dbq = new DBQuery("localhost", "test", "dbuser",
"dbpassword");
// query the database if the user is authorized
// (instance behaves like an object)
$username = "administrator";
$password = sha1("password");
if (!$dbq->{"SELECT * FROM admin_user WHERE username=? " .
"AND password=?"}($username, $password)) {
die("Unauthorized.");
}
// query the database and display some records
// (instance is iterable like an array)
$dbq->{"SELECT id, first_name, last_name FROM employee"};
foreach ($dbq as $result) {
print_r($result);
}
// casting the object string yields the query
echo "Query: $dbq";
I blogged about it shortly thereafter and branded it as evil. Friends and colleagues who saw pretty much reacted the the same: “Brilliant! Now kill it… kill it with fire.”
But over the years since, I admit I’ve softened on it. The only rule it really bends is that of the programmer’s expectation of blandly named methods like query()
and result()
. Instead it uses the query string itself as the querying method, the object is the interface and the result set. Certainly it’s no worse than an overly-generalized ORM interface with select()
and where()
methods chained together to look like an SQL query but with more ->
‘s. Maybe my class really isn’t so evil after all? Maybe it just wants to be loved? I certainly don’t want to die in the Arctic!
In Closing
I hope you enjoyed the article and the examples won’t give you (too many) nightmares! I’m sure you’ve got your own tails of haunted or monstrous code, and there’s no need to let the holiday fun fizzle out regardless of where you are in the world, so feel free to share your scary PHP stories in the comments below! Image via FotoliaFrequently Asked Questions about Spooky Scary PHP
What is Spooky Scary PHP?
Spooky Scary PHP is a unique approach to coding in PHP that involves using unconventional or unexpected methods to achieve certain results. This can include using lesser-known functions, exploiting quirks in the language, or even using code that looks like it shouldn’t work but does. It’s a fun and interesting way to explore the depths of PHP and can often lead to surprising and enlightening discoveries about the language.
How can I start learning Spooky Scary PHP?
The best way to start learning Spooky Scary PHP is to have a solid understanding of PHP basics. Once you’re comfortable with the fundamentals, you can start exploring the more obscure corners of the language. Reading articles, tutorials, and forum discussions about Spooky Scary PHP can also be very helpful. Remember, the goal is not to write efficient or practical code, but to explore and understand the language in a deeper way.
Is Spooky Scary PHP a good practice?
Spooky Scary PHP is not typically considered good practice for writing production code. It often involves using functions or techniques that are inefficient, unclear, or unpredictable. However, it can be a great way to learn more about the language and to challenge your understanding of PHP. It’s more of a learning tool and a fun experiment than a practical coding style.
Can Spooky Scary PHP be harmful?
While Spooky Scary PHP can be fun and educational, it’s important to use it responsibly. Some techniques used in Spooky Scary PHP can potentially be harmful if used in a live environment, such as those that exploit quirks or bugs in the language. Always make sure to thoroughly test any code you write and never use Spooky Scary PHP techniques in sensitive or critical parts of a project.
What are some examples of Spooky Scary PHP?
Examples of Spooky Scary PHP can vary widely, as it’s more of a concept than a specific set of techniques. It could involve using a function in a way that it’s not typically used, exploiting a quirk in the language to achieve a certain result, or writing code that looks like it shouldn’t work but does. The key is to experiment and explore the language in new and unexpected ways.
Why is it called Spooky Scary PHP?
The term “Spooky Scary PHP” is a bit of a joke in the PHP community. It refers to code that is “scary” because it’s unconventional or unexpected, and “spooky” because it often involves exploiting quirks or lesser-known aspects of the language. It’s a fun way to describe this unique approach to coding in PHP.
Can I use Spooky Scary PHP in my projects?
While Spooky Scary PHP can be a fun and interesting way to explore the language, it’s generally not recommended for use in production code. The techniques used in Spooky Scary PHP are often inefficient, unclear, or unpredictable, which can lead to bugs, performance issues, and other problems. It’s best to stick to conventional coding practices for your projects.
How can I improve my Spooky Scary PHP skills?
Improving your Spooky Scary PHP skills is all about exploration and experimentation. Try using different functions in unexpected ways, exploiting quirks in the language, and challenging your understanding of PHP. Reading articles, tutorials, and forum discussions about Spooky Scary PHP can also be very helpful.
Is Spooky Scary PHP popular?
Spooky Scary PHP is more of a niche interest within the PHP community. It’s not a widely used or recognized coding style, but it has a dedicated following of developers who enjoy exploring the language in unconventional ways. If you’re interested in Spooky Scary PHP, there are plenty of resources and communities out there where you can learn more.
Where can I find more resources on Spooky Scary PHP?
There are many resources available online for learning more about Spooky Scary PHP. You can find articles, tutorials, and forum discussions on a variety of websites and blogs. Additionally, there are several online communities dedicated to Spooky Scary PHP where you can ask questions, share your discoveries, and learn from other developers.
Timothy Boronczyk is a native of Syracuse, New York, where he lives with no wife and no cats. He has a degree in Software Application Programming, is a Zend Certified Engineer, and a Certified Scrum Master. By day, Timothy works as a developer at ShoreGroup, Inc. By night, he freelances as a writer and editor. Timothy enjoys spending what little spare time he has left visiting friends, dabbling with Esperanto, and sleeping with his feet off the end of his bed.