12 Things You Should Dislike About PHP

In my last post I outlined my 15 favorite features of PHP that many people tend not to know about. To contrast my previous post I will detail several things I dislike most in PHP.

Note: the intention is not to start a flame war between PHP and any other language and I expect that the comments will reflect this.

1. Naming and Return Value Conventions

Naming Conventions

The first part of this--naming conventions--is obvious to anyone who has used PHP. PHP lacks a specific naming convention. This can easily be demonstrated with many of the string functions. For example, why is the function to find the first position of one string in another called strpos() when the function to repeat a string is called str_repeat()?

Another clear example of this is with PHP's built in classes. Why is the SQLite class called "SQLiteDatabase" whereas the MySQLi class is called "mysqli"? Anothing minor example with PHP's magic functions: __toString() and __set_state().

This might seem like excessive nitpicking; I think it is a serious issue that affects expectations. When programming, productivity is increased when ones expectations about a function or class name are fulfilled given the prior experience of using similar functions or classes.

Return Value Conventions

This was something that was annoying me the other day: parse_str(). The parse string function takes in a URL encoded string and then extracts the variables within into the current scope. As an option, it can take a reference to an array and put the variables into that by indexing them associatively.

Why would this function ever extract these variables? In fact why does it even take an array by reference? I knew of this function but hadn't read the documentation because I assumed it worked in a similar way to parse_url(). It doesn't. It doesn't even return an array.

This might seem like more of a rant than anything, but then lets get back to expectations. Consider that GET variables can have periods, spaces, hyphens, and more in them. PHP doesn't support any of these characters in their variable names so how is the function acting like extract() useful for these cases? Wouldn't it be more intuitive if it acted more like the similarly named parse_url()?

2. Functions

PHP's functions are not objects. More to the point, PHP has no first class functions. Functions cannot be passed by reference and they cannot be self-calling (this is different than a recursive function, which calls itself from within, whereas self-calling is from the outside).

Functions also don't seem to obey their scope in a predictable way. For example, functions can be nested indefinitely in PHP but that doesn't mean that they act like sub functions! Instead it means that any sub functions become accessible in the global scope once the parent function is called. This does have a practical application where different implementations of functions is needed for different PHP versions; however, that is what the if() statement is for. Simply put: nesting functions does not encapsulate them.

PHP has make-believe lambdas using create_function(). Once created, these functions are available in the global namespace and cannot be self-calling. For example: create_function(...)(); does not work because create_function returns the string name of the function it just created.

Consider the implication of returning a string function name from create_function() when it comes to meta-programming in PHP:

3. Method chaining doesn't work as expected

In my previous post I championed the use of method chaining--not necessarily with the best example--but showed how to do it nonetheless. Unfortunately there is a critical element of method chaining missing from PHP. It doesn't affect the methods that return $this but the constructor and class instantiation itself. The following code is from my previous post...

5. Static Methods and Scope Resolution (i.e. No Late Static Binding)

This was a problem that bit the Zend team in the ass early on in their development of the ORM in the Zend Framework. The problem happens when there is a static public method in a parent class that is called by an extending class. One would expect that the function would know it is being called from within the child class (as is the case with instance methods); however, that is not the case. Consider the following example:

As a bug in PHP this has many annoying implications. It means that--for example--in the Zend Framework they could not do ModelName::find() for quick access to model functions without a monstrous hack involving debug_backtrace(). They ended up changing their finder syntax.

6. __autoload()

I mentioned this function as being a useful in my previous post. I also said not to use it and instead use spl_autoload_register(). To reiterate the point: __autoload() belongs to the global namespace. This means if two or more scripts share files and define an __autoload() function of their own then there will be a huge problem. This problem also sheds light on the fact that PHP does not support redefining; however, that is a limitation and not a problem of PHP.

7. Magic Quotes and Register Globals

To be honest I don't even want to talk about these. It's been rehashed so many times that I think most PHPers generally get the point.

8. Cleaning up after Exceptions

PHP5 introduces Exceptions. This is one of my favorite improvements present in PHP5. Unfortunately there is one gotcha that I hacked a solution for in a previous post. Exceptions can be thrown and caught. As with all classes in PHP5, destructors are generally called when an object leaves scope. Unfortunately, an uncaught exception will never be destroyed! Why is this an issue? PHP has no finally clause for exceptions! The following is not possible in PHP:

That means any dependable clean up / tear down action must happen in the exception destructor, which won't be called unless the exception is caught! (Note: a hack around this is to explicitly call the __destruct() method of an exception from within its __toString() method.)

Note: if you didn't catch it above, PHP DOES NOT have a finally statement. This point is to show that because of this, destructors are the only way to clean up after an exception and even they are not 100% dependable.

9. Return values in write context

This is a huge WTF. There might be some logic behind it but for everyday programming it can be incredibly annoying. The following code will cause a fatal error:

10. Safe Mode

No. Just No. Safe mode is not safe. Please read the talks by Ilia Alshanetsky. Every hosting company should stop using PHP in safe mode and simply use open_basedir.

11. func_get_args()

This one has always been confusing to me and I will show why with some example code. PHP's func_get_args() cannot be passed as an argument to a function because of scoping issues. Consider that the following in PHP is possible:

12. Return values again

PHP acts inconsistently with return values of functions. If a function were to return a string then any string functions could easily be applied to it. If a function returns an object then any of the objects instance methods can be called on it. However, if a function returns an array, then array item syntax (using square brackets) cannot be applied directly to it!

Although the tone of this post was negative it must be taken with a grain of salt. As a language PHP is constantly improving and I'm eagerly looking forward to PHP6. In its current state I think PHP is an amazing language--despite the above flaws. One might say its biggest flaw is that its too easy to learn. That argument puts the blame in the wrong hands. Many PHP programmers are unaware of security issues and best practices and as a result the language as a whole is tarnished.

This post was meant to contrast my previous one. Looking at what I think are PHP's unknown heroes and what I see as its flaws is not an accurate way of weighting strengths against weaknesses. There is much more to PHP (such as its libraries, community, etc) than what's on these two lists and I encourage people to discover those things for themselves.

Comments

Actually many people don't have security knowledge and they think it cames attached to the lang. I know some langs have better solutions in terms of security but. believe me I have seen code in java(struts, spring etc etc), .net and php with ugly... terrible security flaws. PHP is nice and your right with the 12 points you have in this article and cant wait for the version 6.

just read through both of your PHP articles - they were a really nice read. I'm still learning the language (we all are I suppose) and posts like this really help me think more critically of the code I write.

"Note: This might be a solved bug; however, I am stuck using php5.1 on MAMP and this is an annoyance for me.
The __toString() method provides a nifty alternative to the cryptic "Object id #1". One would expect that coercing an instance of an object into a string would thus call __toString()--it doesn't. "
Yep, I think that bug's been solved :-). At least, it seems to work for me with my projects (PHP 5.2.1).
Thanks for this very helpful article! There's no point going on about how amazing any language is (*cough* Ruby *cough*) without looking at the flaws and problems with it.

The inconsistent built-in function naming gets my goat too. I have to look up strip_slashes every time.
You didn't mention magic quotes!
You're right- PHP is too easy to learn- it seems to have pretty much every function conceivable built in... is anyone else waiting for the buildme_aselfmoderatingforum() function in php 8? ;p

Ur a java developer. All ur cases above dont hold water because ur trying to apply native java syntax and language constructs into php
it will NEVER work becoz php is not java
So i suggest u either stick with java or embrace php for what it is. you might just write a "12 things u should dislike about java" whereby u bitch at how java falls short of php's cool features
i was a java developer. been there. done that

I found #4 to be quite interesting, and very perplexing. That really doesn't make any sense at all!
In response to pearini, two comments above, I would really use __toString for debugging purposes so much. Especially if I was dealing with elements that could be viewed in a browser, you could use __toString to do a simple echo of the object and have it show up the way you want.
For example, a box object's __toString method could echo out some divs and content inside them. :)

In #11, whilst it is true that func_get_args() can't be passed as a function argument, the first half of that point is surely unrelated. It wasn't $string that was used in the strtoupper function, it was the return from evaluating '$string = "hello world"', and the return from a '$a = $b' assignment ('also '$a .= $b etc) is always the final value of $a, in this case "hello world".

In what language can you get a reference to an array result by putting brackets after the function name? Is this in Java or something? C++?
I would never expect anything other than a parse error to put brackets immediately after anything other than a variable name.

You're right, it is cool. One dislike about php I have that I mentioned is that there is no actual way to reference or return a function. call_user_func is roughly equivalent to doing: $func = 'a'; $func('hello world'); As shown, $func is unfortunately not a reference to the function "a" itself but rather is a string representation of it. When you say passes a function by reference, do you mean in object context when passing array($this, 'func_name') as the first parameter?

I agree with everything here. Great article! It seems to me that by far the largest problem with PHP is inconsistancy. I can't tell you how many times I've done:
$foo = FooBar::getSomething()[5]
And been saddened by the results.
Also, what's with all the SPAM? Don't you moderate that crap?

That was a good review! It's clear there's a huge problem of naming convention. I'm glad that you mentionned it. I thought you were to talk about libraries, but I didn't realise the problem started directly from the language itself.
However, I'm not use to some specific cases that you mentionned. For other ones (that I often use), like "try and catch" statement or class, they must improve it.
I hope PHP dev will hear your thought ;) .

Talking about #9, that is an empty() limitation.
If you read http://php.net/empty , you will see what I mean:
"Note: empty() only checks variables as anything else will result in a parse error. In other words, the following will not work: empty(trim($name))."
Other than that, great post!

Emilio, case in point, if usage of empty() without a variable parameter causes a parse error then it means that empty() is a special case within the parser, which is terrible. empty() should be no different from every other function in the PHP standard library.

Part of that code got clipped. Not sure what happened but it should have looked like this.
class TestLambda {
private $test;
public function __construct() {
$this->test = create_function('$str', 'echo $str;');
}
// (John McKnight) This magic method checks for the existence of a function that may have been created using create_function.
// Normal methods are unaffected by this code so they should run without issue also.
function __call($name, $args) {
if (!isset($this->$name)) {
echo "Undefined function '{$name}'";
}
$function = $this->$name;
if (function_exists($function)) {
call_user_func_array($function, $args);
}
}
}
$l = new TestLambda;
// (John McKnight) - But now it works.
$l->test("hello world");

I would add the fact that PHP throws notices and warnings and errors and exceptions on different situations... apparently it is up to any given php contributor to decide what to throw and when...

This leaves you with mixed error handling strategies, registering error handlers here, and then another one there, and then your own try {...} catch(){...} blocks for your own code... If I was to decide, php would have a strict mode where every error would be thrown as an exception (possibly involving a couple of different Exception classes).

Then maybe some php.ini conf directive could make it ignore some types of Exceptions so it could run the typical php hacky code.

The try {} finally {} block would be nifty too.

And I could do away with the unrecoverable FATAL errors too. They don't make much sense in a scripting/interpreted environment. Well, they could take an extra check or too within php, possibly a performance cost, sure... well, this way, the extra check or two is in the user space and the performance cost is certainly bigger.

I think that PHP should keep the idea of unrecoverable error for parse errors, and in this case, they should try to continue parsing a file to find as many parse errors as they can.

I agree that for most other things, using exceptions as the main method for errors would be nice; however, I would add one thing, and this is somewhat inspired by the way Java lays things out (PHP would also current, or might actually, support this).

Most errors would actually extend Error, which implements Throwable, whereas most user-errors and other recoverable errors would extend Exception, which itself also extends Throwable. This distinction between Error and Exception would allow for PHP to internally throw errors, which usually are not caught (as it would mean catching all Throwables).

This would allow for current things to work as usual, as most people wouldn't catch Errors.