Skip to content

Categories:

PHP: The Ultimate Suck

fboender

I've been a PHP developer for too long and it has turned my brain into jelly. PHP is just such a convoluted mess, it rots the brain. I've seen other long time PHP programmers with the same syndrome. I'm not sure we'll ever recover, but there might be a cure. For those who aren't yet convinced of the level of suckitude of PHP, I've compiled a short List Of Stupidity found in PHP.

Core PHP

Procedural v.s. Object Oriented

PHP was procedural. Lately, they've started adding Object Oriented things to the core language. We now have a half-procedural, half-Object Oriented PHP. This is a complete mess. It is a paradigm shift with a sincere lack of confidence on the PHP side. Either go for it, or stay with what worked. I'm not talking about allowing PHP users to develop in an Object Oriented methodology. This is fine, and was even done reasonably well. We can now write PHP code Object Oriented, and it works well. I'm talking about the core language here: SPL, the MySQLi library, etc. SPL feels like a half-baked attempt at going in some general direction with PHP, but is just lacks momentum, focus and a clear direction. What is it trying to solve?

What is SPL trying to be? SPL is a collection of interfaces and classes that are meant to solve standard problems.. What kind of problems? It just doesn't make any sense. It implements all kinds of iterators, but it doesn't integrate them into the core language at all. What's wrong with foreach? Is SPL something for the future of PHP? If so, then keep it under wraps until that future is here. Don't expose it before it's done.

SPL's data structures are cute, but why should I use them? Why use SPLStack, if I've already got arrays in PHP? The ArrayIterator.. what is its purpose? I can create an ArrayObject from an array, and then iterate over it by requesting an ArrayIterator from it and calling the next() method on that. I can also NOT do that, and just call foreach() which is much cleaner. The whole of SPL seems to serve no other purpose than to make PHP appear more like Java.

The really bad thing about SPL is that they once again managed to screw it up by adding procedural functions to an otherwise entirely Object Oriented interface: SPL functions. PHP is really showing it's identity crisis. It's trying to be a kind of PERL and do everything magically. It's trying to be Java, by doing things Object Oriented and over-engineered. It's trying to do things like C, the procedural way. And like everything that tries to be like everything, it fails at all of them.

solution: Remove SPL and all things Object-Oriented (in the Core) or focus on SPL and Object-Orientation and integrate it into the core language.

Aliases

The PHP library is filled with all kinds of function aliases. Why? There is no point, other than to cause confusion and unreadable code. I mean, is_int() and is_integer()? join() and implode(). Really. Get that crap out of there.

solution: Remove all aliases.

Shebang

Just about every script under the UNIX operating system can be made executable by changing the permissions and adding a simple line at the top of the script that tells the shell what interpreter it should use to run the file. This line is called a shebang, and it works like this:

[todsah@jib]~$ cat file.php
#!/usr/bin/php
<?php
echo("hoi\n");
?>
[todsah@jib]~$ chmod 755 ./file.php
[todsah@jib]~$ ./file.php
hoi

The shell 'magically' understands how the script should be run, and it thus starts the PHP interpreter to interpret the script. This is basically the same as running this manually:

[todsah@jib]~$ /usr/bin/php ./file.php
hoi

PHP didn't used to have this ability, but the PHP development team hacked it in, and now it does. Completely in the style of the rest of PHP, it was implemented shoddy and without much forefought. You see, when we create a file main.php, and include the file file.php in that file, here's what happens:

[todsah@jib]~$ cat main.php
#!/usr/bin/php
<?php
include('file.php');
?>
[todsah@jib]~$ chmod 755 ./main.php
[todsah@jib]~$ ./main.php
#!/usr/bin/php
hoi

You see? The PHP interpreter understands the shebang, but it only understands it in files directly ran from the shell, and not when you include/require it from another file. This makes it impossible to do cool stuff such as making PHP files self-executable so that when ran from the commandline it will run a bunch of self-tests, but if you include it, it will just skip over the test and only get included. Awesome work PHP.

Datatypes

Associative arrays

PHP's associative arrays, sometimes better known as hashmaps, allow for both indexed access as well as hashed access in the same array. This gives rise to strange, mixed arrays such as:

<?php
$a = array(
);
$a[0] = 'abc';
$a['somekey'] = 'def';
$a[3] = 'ghi';
$a['foo'] = 'jkl';

var_dump($a);
?>

output:

array(4) {
  [0]=>
  string(3) "abc"
  ["somekey"]=>
  string(3) "def"
  [3]=>
  string(3) "ghi"
  ["foo"]=>
  string(3) "jkl"
}

Datastructures are the most important part of any programming language. Allowing such mixing as we see above is foolish, as it makes it easy to create a complete mess of arrays and causes very strange behaviour:

<?php
$a = array();
$a[0] = 'abc';
$a[1] = 'def';
$a[2] = 'ghi';

$data = 'xyz123';
for ($i = 0; $i < strlen($data); $i++) {
        $a[ $data[$i] ] = $i;
}
var_dump($a);
?>

output:
array(7) {
  [0]=>
  string(3) "abc"
  [1]=>
  int(3)
  [2]=>
  int(4)
  ["x"]=>
  int(0)
  ["y"]=>
  int(1)
  ["z"]=>
  int(2)
  [3]=>
  int(5)
}

PHP made it's associative arrays too powerful. Now, they become a mess.

solution: Put the PHP authors through a computer science study. Next, seperate current array functionality into normal tuples (array), sets and hashmaps. Also, do not cast string hashmap keys to int if possible. int(0) and str(0) are not the same.

Error reporting

Error level

Currently PHP allows developers to set the level of errors they want to display or ignore complete. This is bad practice. Developers will switch off errors and warnings because they're lazy, especially novice programmers. This often happens when some third-party library (especially everything in PEAR) starts displaying warnings or errors, and the developer turns off error reporting to get rid of them. There is no easy way to exclude the displaying of errors and warning from certain origins, so developers quickly resort to turning them off globally.

solution: Remove error reporting levels alltogether. Always display and log every warning and error. This will break every package in PEAR, which doesn't matter because PEAR is a flaming heap of shit anyway.

Error severity

PHP is not conservative enough about classifying its warnings and errors:

  • An undefined variable is currently a notice.
  • An undefined property is currently a notice.
  • Using foreach() on something which isn't an array or class instance is currently a warning.
  • Etc…

This is remendously stupid, and as such has given rise to a wide variety of insecurities in PHP applications. This is PHP's fault, it should protect the developer from itself, and the users from the developers. An undefined variable should always be a critical error. All of the examples above are programmer errors, and the programmer should be made aware of this.

solution: Everything should be a fatal error, except for Deprecation warnings.

Error destination

PHP can send errors to too many destinations. They can be displayed in the output, or not. They can go the Apache error log, or not. They can go to a seperate PHP error log, or not. Repeated errors can be ignored or not. Errors can be sent to the standard out (STDOUT) or standard error (STDERR) or to nowhere at all. As you can see, this quickly becomes an enormous mess. But it gets worse:

There are too many things that can influence how errors are handled. The global php.ini can change the behaviour. Then the Apache configuration can include PHP configuration changes. Some things can be changed in htaccess configurations. Then there can be multiple php.ini's depending on how you're running PHP: Commandline, CGI or mod_php. Finally, PHP scripts can at any time change their error logging behaviour.

All this makes such a mess of PHP error reporting, it's no wonder most developers simply shut the entire thing off.

solution: Always report all errors and warnings in the output and log them to standard out. They will appear in the output and in the Apache error logs.

Exceptions v.s. Native errors

Why are some things exceptions, and other things native PHP warnings/errors? SPL throws exceptions, the rest all just .. log errors. They're not even really errors.

Error messages

Some error messages in PHP are so cryptic, you actually need to speak hebrew to understand them. For instance, if you make an error in the scope resolution operator (::) in PHP, you get this:

Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in Command line code on line 1

PHP's developers are completely aware of this error message, it's confusing nature and that it causes programmers writing in PHP unnecessary head-aches, yet they refuse to remove it from PHP. Now, there's nothing wrong with some humor in your programming language, but it should never come at the expense of your user base.

This is a perfect embodiment of everything that's wrong with PHP as a language, and the team of developers creating the PHP language.

Databases

Database access layer

There is no native general database access layer in PHP. They took the lazy approach, and simply dumped every C connector library they could find into the PHP core without writing any kind of wrapper around them. There is nothing wrong with directly accessible native C connector libraries for databases, but they should only be used if you need very very raw speed. Instead, they are the default way of connecting to databases in PHP. Every database connector uses a different approach to connecting, querying, cursors, error reporting, etc.

Since this is such as mess, people have decided to do something about it. Now we have about four PEAR packages which try to solve the problem, two of which seem to have been deprecated and the rest being buggy, slow, incomprehensable, over-engineered and PEARy in general. We have DBx. Oh, wait, that's been moved to PECL and isn't included with PHP by default anymore, in effect deprecating it. We have ODBC, but hold on, it's not really ODBC: "Note: With the exception of iODBC, there is no ODBC involved when connecting to the above databases. The functions that you use to speak natively to them just happen to share the same names and syntax as the ODBC functions". The latest thing seems to be PDO which, for a change, looks like somebody really thought about. Unfortunatelly, it's only available in PHP 5.1. For PHP 5.0 you need to compile a PECL extension. But we can live with that. PDO works reasonable however, like so many things in PHP, it's error reporting is a complete dragon to work with.

solution: Provide one single database access layer (PDO). Remove all the others. Find a way to discourage users from directly using the native connectors. Fix the error reporting for all connectors.

MySQL(i) connectors

Now, the native MySQL connectors are a complete mess. It seems to be the only connector which allows you to define how it should connect from the php.ini. There are too many ways to escape query parameters. It does not support prepared statements. For this we have MySQLi (ironically stands for MySQL Improved), which is an even bigger mess. MySQLi is not backwards compatible with the old MySQL connectors, meaning you'll have to complete rewrite every portion of your application that uses the old connector. The MySQLi connector is half Object-Oriented, half procedural style. Why this is, nobody knows. All we know is it creates a mess in the API. When looking at the API documentation, it seems we've got a procedural style method for each corresponding Ojbect-Oriented style method. However, they behave in completely different ways. Or at least the error methods do, which is the only thing I tried before deciding that MySQLi is not worth my time. Another funny thing about the MySQLi connector is that you have to manually determine how query parameters in prepared statements have to be escaped. Another future security nightmare for PHP applications.

solution: Deprecate the old MySQL extension immediately. Remove the procedural versions of MySQLi. Implement real prepared statement parameter escaping. Fix error reporting. Remove all mentionings of the old mysql connector from all documentation. Pretend it never existed.

PEAR

Package versions

PEAR has too many deprecated packages. I'm not sure what's going on here, but many of the same packages have two, three sometimes even four different implementations. For instance, there are two Auth_PrefManager packages: Auth_PrefManager and Auth_PrefManager2. In full-on PHP style, it's not version 2 you should be using, because that's been deprecated. It's version 1 you should use because, get this, PrefManager2 was an experimental package! How did an experimental package get into PEAR and, much more importantly, what's it still doing in there?

This is a very common occurence in the PEAR package repository. Take the database packages for instance. First we note that there are two (or more?) completely different Object Interface packages in PEAR. Next, there's a Database abstraction layer DB. This was created before PDO even existed, which is good, but it never really worked. It was always extremely buggy, throwing errors and warnings all over the place. It's been deprecated (but of course, it's still listed in the PEAR repository). It hints that we should use MDB2. Apparently, MDB v1 failed miserably, like every other PEAR package. I'm not too confident that MDB2 is going to make it too, for some reason. MDB2 seems to replicate a lot of the behaviour of native PHP extensions, various PECL extensions and a whole slew of other PEAR packages. So what do I use? I just have no idea. And more importantly, if I decide on choice X, how am I going to be sure that it won't be deprecated in two weeks, like all those other PHP and PEAR packages?

Now, there's nothing wrong with choice, of course. There isn't even anything wrong with duplicated efforts. We see this in every language, and it's always okay. But for some reason that I can't quite put my finger on, it fails miserably in PHP and PEAR. PEAR tries to be the authoritive end-point for useful collections of PHP libraries, but it just fails, hard.

There just doesn't seem to be any kind of consensus in the PHP world about which direction they should be going. Should they focus in PDO? Should such database abstraction libraries be packaged in PEAR or on PHP or in PECL or in the user-contributed notes in the documentation? They just don't know, they have no direction whatsoever. As such, they move in all directions at the same time, and PHP developers stick with using the mysql C connector because it's the only thing that appears to be stable.

solution: Deprecate PEAR entirely. Let evolution sort things out without any kind of central control. PEAR should either be a clean repository of recommended packages, or should be an easy-to-install-PHP-libraries distribution net where everybody can upload their crap.

Quality Assurence

For some reason the people who do Quality Assurence (and I use that term as lightly as I possibly can) for PEAR have it in their head that as long as your if statements have a space between the if and the opening parenthesis, they've done their job. Wrong. Quality Assurence in PEAR is a complete atrocity. Old cruft isn't cleaned up, there's no advice about duplicated efforts at all. They allow all kinds of idiotic packages in (a LinkedList implementation in PHP.. please).

solution: Deprecate PEAR entirely. Let evolution sort things out without any kind of central control. PEAR should either be a clean repository of recommended packages, or should be an easy-to-install-PHP-libraries distribution net where everybody can upload their crap.

Errors

Too many packages in PEAR have errors and warnings. There's not much too say about this. PEAR packages are all unusable because of this. They can't be trusted.

Now, this isn't really the PEAR package authors fault. PHP's error reporting facilities are such a pain in the ass that it's incredibly hard to write code that doesn't trigger any errors. In fact, depending on what you're doing, it's impossible.

solution: Deprecate PEAR entirely, or put true quality assurence on the packages (and fix PHP's error reporting mechanism). Everything should run on PHP 5.0+ without any errors, warnings, notices, strange hacks, etc.

Core library

Regular expressions

There are two libraries for regular expressions. There should be only one.

solution: Remove one of the two regular expression packages. Preferably ereg. Rename preg to regexp – nobody gives a shit whether it came from PERL or Extended POSIX.

isset()

isset() sounds like it checks whether a variable is defined in the current (or even global) scope. PHP's manual says: Determine whether a variable is set. . Naturally, that's not really what it does. Rather, isset() checks if a variable has a value other than NULL, without giving an error if the variable isn't set. You'd think the following returns True:

$foo = NULL;
if(isset($foo)) {
  print('\$foo is set');
}
# No output

Of course, this is not a terrible disaster. But many people use isset() to check for array keys ($a = array(a ⇒ NULL); isset($a[a]) == False). Now what happens when you select fields from a database, retrieve the results with db_fetch_assoc(), and one of the values is NULL? Do you know? I can't guess what happens in PHP, so I'd have to experiment. Or just always use array_key_exists(). Another complete failure of PHP at being a predictable language.

Language constructs v.s. functions

PHP is easily confused when it comes to what is a function and what is a function. You see, some things in PHP appear as a function, but they really aren't. They're language constructs. The difference between this is a long and boring story which shouldn't really have to matter to programmers writing code in PHP. It is enough to know that the PHP developers are sufficiently saddistic to make it basically impossible for PHP programmers to see the difference, nor to create proper errors for their 'misuse'.

Here are some nice effects of language constructs that appear to be functions, but really aren't:

var_dump(empty(trim($name)));
Fatal error: Can't use function return value in write context in file.php on line 1

An obvious error, I'd say. Fortunatelly the PHP developers know that what you really want to do, and understand the confusion completely. Instead of just making empty() a function, they've opted to document their own mental retardedness here: http://nl3.php.net/manual/en/function.empty.php

var_dump(empty(''));
Parse error: syntax error, unexpected T_CONSTANT_ENCAPSED_STRING, expecting T_STRING or T_VARIABLE or '$' in file.php on line 1

Another fun thing to try:

$foo = 'bar';
var_dump(function_exists('empty'));
// output: false
var_dump(empty($foo));
// output: true

PHP's identity crisis becomes apparent again as function_exists fails to detect PHP's own functions because they're not functions but language constructs although they look and act (almost) completely like functions. Kudos for PHP.

Error: Success!

Some functions work just brilliantly:

Testing XMLRPC server is operating...: Couldn't contact the XMLRPC server. (PHP reported: 'file_get_contents(http://ems2.dev.local/svcmon/RPC/server.php) [function.file-get-contents]: failed to open stream: Success')

The URL works just fine. PHP simply reports an error about the thing it tried was succesful.

API confusion

PHP has a highly confusing API. Without even looking at the mixing of objects, functions and language constructs, even its simple function parameters are often mixed up and confusing.

pathinfo

The pathinfo() function can take option parameters which are constants in the form of PATHINFO_DIRNAME, etc. One would naturally assume that you could then also do:

$pathparts = pathinfo($path, PATHINFO_DIRNAME);
print($pathparts[PATHINFO_DIRNAME]);

But this doesn't work, as the indexes of the array that pathinfo() returns are strings (dirname) instead of integers that correspond to the constants. It's clear that this function was written by a programming noob that doesn't understand the use of constants, nor how to build a consistent API.

Documentation

User contributed notes

The user contributed notes in PHP's documentation are an attrocity, and they need to go. We all know and appreciate the "No documentation is better than bad documentation" principle, and PHP's documentation would do wise to keep the buggy, incorrect, asinine, slow, sloppy, worthless craptasticly FUBARred code PHP programmers seem to love out of the official documentation, or at the very least filter out any example code and "Hey guys look, I made my own version of X!" kind of code.

The user contributed notes should only ever refer to incorrect or missing information in the documentation itself. User contributed examples should go to ActiveState-like code repositories or, preferably, the waste-bin in case of PHP.

Conclusion

There's more, but I can't go on anymore. I can't even muster enough brain-goo to write up a conclusion. Do I really need to? Goodbye cruel PHP world, it's been a torture!

Posted in Software Bashing.

Tagged with , .


4 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. averstegen says

    Poep, harde poep!

  2. cvanpelt says

    TL;DR: PHP sucks.

  3. ivo says

    Ha ha, you're using PHP as a programming language. Stop that.

  4. fboender says

    I try, I try, but there are still some applications at my job that are written in PHP. Rewriting them to, say, Python is going to take about three years of fulltime development (seeing that the application is about 9 years old).