Following a tag from Emily…
- I enjoy cold weather.
- I eat and enjoy almost all foods, with notable exceptions being button mushrooms and raw celery.
- I roast my own coffee.
- I can reach over my toes and touch my heels when I stretch.
- I don’t watch television when I’m at home.
- I think Babylon 5 is the best television series ever.
- I know the Greek alphabet, but am in no way Greek.
- I get agitated when anything moves or makes noise unexpectedly on the Web.
In a recent project, I found a couple more reasons to dislike PHP (not that there aren’t plenty already). While, admittedly, PHP 5 is a substantially better language than PHP 4 (for instance, it kinda supports object-oriented programming), it’s nonsense like this that makes it nastier to work with than better-designed programming languages.
According to the documentation, PHP “does not support unsigned integers”. Like many things in PHP, it’s true, except when it’s not. Obviously there’s an expectation that integers will be treated as unsigned values, since functions like sprintf() include provisions for handling them. I stumbled across this when using the ip2long() function. It always returns an integer for the IP address, even though it may be negative if your integers are 32 bits long! I ended up having to wrap every use of the function with a clever but nasty hack to avoid storing negative IP addresses in the database.
The “proper” behavior for dealing with values larger than the integer type can handle, according to the docs, is to promote it to a float. That might be reasonable if it were consistent…but it’s not. For instance, if you’re on a 32-bit platform, you’ll probably find that 0x7fffffff is equal to 0x1000000000, because hex values don’t get promoted consistently. Going the other direction isn’t any better. If you cast 2147483648 on a 32-bit system to an int, it comes back as -2147483648, without so much as a warning. In general, integer math in PHP is risky.
Bizarre NULL semantics
PHP has a NULL type. What it doesn’t appear to have are any rules, conventions, or even vague notions about how to handle it. In general, I’d expect one of two results if I use a NULL value in a computation or pass it to a function that doesn’t have obvious NULL semantics (e.g., is_null()):
- A returned NULL value, Ã la SQL. If I try to do a computation with a missing value, I get no value as a result.
- An error. Python, for instance, will throw exceptions if you try to evaluate something like “1 + None”.
What does PHP do? …who knows?. “1 + NULL” is 1, “ip2long(NULL)” is -1, and “NULL + NULL” is NULL. Nothing In, Garbage Out.
While this may sound niggling, keep in mind that PHP’s stereotypical use is database-driven web apps, and SQL-speaking databases all support NULL values. You generally don’t want to handle NULL in the same way that you handle a value that’s present, and you certainly don’t want to handle it in an arbitrary and indeterminate fashion. This caused much consternation on my aforementioned project, as NULLs were “helpfully” converted to zeros and saved into a column in the database which had a UNIQUE constraint on it. It worked okay for the first person save the form without filling out that value…
Update:I got flak on Reddit for posting an
X sucks article, which is probably fair. I added a comment there finishing my thought, which I hope will turn it into
X sucks, here are better alternatives.
These are indicative of fundamental problems with way the language is developed; namely, the apparent lack of any consistency or thought regarding the consequences of the designers’ decisions. I’ve had an app crash during minor-version PHP upgrades because they changed the semantics of one of the LDAP functions and didn’t note it in the changelog. I’ve fought with data-input bugs in existing PHP apps that invariably crop up because somewhere, someone didn’t wrap a request variable with the four lines required to un-magic-quote-it, or un-magic-quoted a variable on a system where it wasn’t turned on. And it took them until version 4.2 to realize that it was maybe a bad idea to, by default, allow web requests to set arbitrary variables in the global namespace.
Why fix PHP when, as I noted in the post, there are already plenty of other languages that don’t suck? Python’s well-thought out and has an enormous set of libraries and development frameworks available for free. The same can be said for Java. (I personally find the repetition it often requires maddening, but at least it’s consistent.) I haven’t played with it myself, but lots of folks back Ruby too, and Perl’s still got a sizable fanbase. The changes required to make PHP enterprise-ready are immense:
- audit all functions to ensure proper handling of NULLs
- completely redo how integers are handled
- modify all functions to throw exceptions when exceptional cases occur, rather than returning 0/FALSE/-1/”ERROR”
- cull unnecessary nearly duplicate functions
- standardize function naming
- introduce namespaces
…and that’s just what I’m coming up with off the top of my head. Why bother fixing something so fundamentally broken when better things are already out there? (To be fair, I know that PHP 6 is going to try to resolve some of these issues, but that’s going to be a hard transition that’s going to break most existing PHP 5 apps, and it’s still a ways down the road.)
Update, 19 June 2017: I got a note from Craig B. Peck at iFlexion with an article they posted on the resilience of PHP. I can’t say that the problems generated by its use are the ones that I’d want to spend my time solving, but I can certainly believe that there will be plenty of them to work on for years to come for those who are so inclined.
In an application that I developed recently, I needed to be able to choose an unused IP address from a set of available address ranges. I didn’t really want to have to write some stored procedure to iterate over the entire space to until it found a gap. After a while I came up with (roughly) this:
SELECT MIN(addresses.available) FROM ( SELECT i1.ip_addr + 1 AS available FROM interfaces i1 INNER JOIN assignable_ip_ranges r ON i1.ip_addr >= r.start AND i1.ip_addr < r.end LEFT OUTER JOIN interfaces i2 ON i1.ip_addr + 1 = i2.ip_addr WHERE i2.id IS NULL UNION SELECT r.start AS available FROM assignable_ip_ranges r LEFT OUTER JOIN interfaces i ON r.start = i.ip_addr WHERE i.id IS NULL ) addresses;
A quick explanation of what’s happening here:
If you do a left outer join of a table with itself with the criteria that some field on the left side, plus one, is equal to that same field on the right, the rows in the result that are NULL on the right side will have the maximum values of sequences on the left. For example, say you have table xyzzy containing four records with field foo containing the values 1, 2, 3, and 5. This query:
SELECT * FROM xyzzy a LEFT OUTER JOIN xyzzy b ON a.foo + 1 = b.foo;
That explains the LEFT OUTER JOIN and WHERE clause in the first sub-SELECT. The INNER JOIN just restricts the addresses to being chosen from the ranges in assignable_ip_ranges. (Note that the “end” comparison is a strict less-than, though. An address that has a gap following it at the end of an assignable range doesn’t do us any good!)
This technique has one big problem: it has no way of filling in the gap between the beginning of an available range and the first used address! That’s where the second sub-SELECT comes in. It uses roughly the same LEFT OUTER JOIN technique, but instead of doing a self-join, uses the start of the available IP ranges and the used IPs for joining. In essence, we’re pretending that there’s an assigned IP just before the beginning of every range.
At this point, we’ve got a list of the beginnings of all gaps, and we just take the minimum for the next address. Shazam!
Just finished “Over There”.
It’s better. Not great, or even really good, but I wouldn’t brand it DNPIM like “Over Here”.
The first part of the episode was pure filler, though. Lookee, President Sheridan still hates ISN reporters. This matters how? And, he’s old, yes, but that doesn’t mean he has to ramble uninterestingly like a semi-senile great-grandpa. Also, you don’t have to explain your jokes. We can imagine that most Pak’Ma’Ra jokes are just like the one you told, thanks.
It helped a lot that Galen was in the episode. Apparently, JMS hasn’t forgotten how to write that character.
I just got my pre-ordered copy of Babylon 5: The Lost Tales
today. I watched the first of the two tales, “Over Here”.
Summary: It’s BAD. REALLY REALLY BAD.
The only thing worse than the acting is the dialogue, the special effects,
and the theology. And the acting isn’t great.
This isn’t a Babylon 5 story at all; you could have set it equally well in any “future time in space”. There are only three characters: Colonel Elizabeth “2-D” Lochley, Random Nearly-Apostate Priest Dude, and Asmodeus. Unfortunately, through centuries of overeating and lack of exercise, Asmodeus’s powers have reduced to those of an old window A/C unit: making the room cold, making the air smell bad, and occasionally catching on fire, so it’s not as interesting as you might think.
The thing that really pissed me off, though, was the ending: