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:

class TestLambda {
	public $test;
	
	public function __construct() {
		$this->test = create_function('$str', 'echo $str;');
	}
}
$l = new TestLambda;

// if functions were objects this would work; unfortunately that is not the case.
$l->test("hello world");

// instead this ugliness needs to be done:
call_user_func_array(array($l, $l->test), array("hello world"));

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...

class Foo {
	public function bar() {
		return $this;
	}
	public function baz() {
		return $this;
	}
}
As is obvious, bar() and baz() can be chained indefinitely. PHP can't do the following even though a class constructor implicitly returns the instance of an object.
(new Foo)->bar()->baz()->...

4. Magic function __toString() doesn't work as expected

Note: This is a solved bug on php 5.2; however, I am stuck using php 5.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.

class Foo {
	public function __toString() {
		return "instance of class Foo";
	}
}

$foo = new Foo;

echo $foo; // output: instance of class Foo

$str = (string)$foo;
echo $str; // output: Object id #1 WTF!?

5. Static Methods and Scope Resolution

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:

class StaticTest {
	static public function moo() {
		echo __CLASS__; // also redundant given the get_class() function, but whatever.
		echo get_class();
	}
}

class ChildStaticTest extends StaticTest {
	// ...
}

ChildStaticTest::moo();

// output: StaticTest StaticTest < -- wrong class names!

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:

try {
	throw new Exception;
} catch(Exception $e) {
	echo 'caught';
} finally { // whoops, parse error here, missing feature
	echo 'clean up';
}

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:

function return_empty_string() {
	return '';
}

empty(return_empty_string()); // PHP Fatal error:  Can't use function return value in write context

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:

strtoupper($string = "hello world");
echo $string; // output: hello world

Notice that $string is available in this scope and hasn't been affected by strtoupper() in any way. Unfortunately the following is not possible:

function test() {
	return implode("", func_get_args()); // func_get_args can't be passed as an argument
}
test("hello", "world");

Does this restriction make any sense?

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!

function test_with_array() {
	return array('a', 'b', 'c');
}

function test_with_array_object() {
	return new ArrayObject(array('a', 'b', 'c'));
}

// both fail with an unexpected parse error
echo test_with_array()[0];
echo test_with_array_object()[0];

// works
echo test_with_array_object()->offsetGet(0); // output: a

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.

  • PHP
  • Rants
  • by Peter Goodman on Aug 19, 2007 @ 1:23pm

Comments

"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.

#12 really bugs me!

    by Anonymous on Aug 20, 2007 @ 1:06pm

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.

    by Chris on Aug 20, 2007 @ 11:59pm

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.

    by Kaf on Aug 21, 2007 @ 5:41pm

I can add a few more to the list of things you should dislike about PHP:

Can't implement multiple interfaces which both declare the same function

interface Runnable {

function run();

}

interface Task {

function run();

}

// won't work

class Job implements Runnable, Task {

function run() {

echo 'run';

}

}

Can't initialize a field by calling a function/method

class Fields {

private $array1 = array('f', 'o', 'o');

private $string1 = 'foo';

// the following lines just won't work

private $foo1 = $this->createFoo();

private $foo2 = Fields::staticCreateFoo();

private function createFoo() {

return 'foo';

}

private static function staticCreateFoo() {

return 'foo';

}

}

Dynamic static method call doesn't work as expected

// will create a new instance of ClassName;

$name = "ClassName";

$instance = new $name();

// will run method().

public function method() {

//some code

}

$methodName = "method";

$methodName();

// won't work

class ClassName {

public static function staticClassMethod() {

//some code

}

}

$name = "ClassName";

$name::staticClassMethod();

T_PAAMAYIM_NEKUDOTAYIM

Turns out it’s Hebrew for '::'. Thank you, PHP, for teaching me a Hebrew word.

    by Nitish Rathi on Aug 22, 2007 @ 6:11am

Sorry about the formatting. Can I submit formatted comments?

    by Nitish Rathi on Aug 22, 2007 @ 6:13am

#1 - Does it matter if its mysqli or MySQLi? You can write both and still get working code. This would be a problem if calling $Mysqli->something wouldnt work... or, did I miss something?

    by David on Aug 24, 2007 @ 7:57am

Just an FYI: MAMP 1.7 includes PHP 5.2.0: http://www.mamp.info/en/releases.html (released a few days back)

    by Michel de Lange on Aug 24, 2007 @ 9:10am

Hey,

Sorry for the late comment, I've been extremely busy...

David, in php5 case does not matter; however, iirc in php5 it does.

    by Peter Goodman on Aug 24, 2007 @ 12:49pm

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

    by alex on Aug 29, 2007 @ 6:12am

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

    by Quinton on Aug 30, 2007 @ 3:42am

Quinton,

I'm not a java developer... Where did you get that idea? I have embraced PHP for what it is for over five years now. PHP has some cool features but in many cases it is fundamentally broken.

I feel like you have missed the point of this post altogether. This post was not meant to show how bad PHP was but simply to show that PHP isn't all good. I think it would be a good exercise for you to step back and take an objective look at the languages that you work with.

    by Peter Goodman on Aug 30, 2007 @ 7:26am

In re: "4. Magic function __toString() doesn't work as expected"

I've always used print_r instead of __toString, it's always been enough to see what's going on. For debug (into html), this one-liner makes my life easier:

function dump_pre($mixed) { echo "" . print_r($mixed, true) . ""; }

    by pcarini on Aug 30, 2007 @ 11:30am

I apologize, I didn't expect the text to be removed entirely.. in the above dump_pre function, those are html pre tags in the quotes.

    by pcarini on Aug 30, 2007 @ 11:31am

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".

Functions can be by passed by reference via the callback psuedotype and call_user_func. You might want to move this to the "cool" things list, because it's real handy.

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?

    by Peter Goodman on Sep 1, 2007 @ 10:50pm

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 ;) .

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?

Sorry about the spam, forgot one small thing in my code :) It's all gone now.

    by Peter Goodman on Sep 17, 2007 @ 3:32pm

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.

#2 can be made to work using the __call magic method.

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");

?>

Yes, sorry, I manually moderate comments (as well as let akismet do some work).

    by Peter Goodman on Jun 20, 2008 @ 11:36pm

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");

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!

Add a Comment