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 (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:
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");
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.
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.posted by Kaf on Aug 22, 2007 at 12:41am
-
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.posted by Chris on Aug 21, 2007 at 6:59am
-
"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.posted by Jonathan Ford on Aug 19, 2007 at 9:57pm
-
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? ;pposted by alex on Aug 29, 2007 at 1:12pm
-
#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?posted by David on Aug 24, 2007 at 2:57pm
-
Just an FYI: MAMP 1.7 includes PHP 5.2.0: http://www.mamp.info/en/releases.html (released a few days back)posted by Michel de Lange on Aug 24, 2007 at 4:10pm
-
Hey, Sorry for the late comment, I've been extremely busy... David, in php5 case does not matter; however, iirc in php5 it does.posted by Peter Goodman on Aug 24, 2007 at 7:49pm
-
Sorry about the formatting. Can I submit formatted comments?posted by Nitish Rathi on Aug 22, 2007 at 1:13pm
-
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 functionposted by Nitish Rathi on Aug 22, 2007 at 1:11pm
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/methodclass 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. -
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 thatposted by Quinton on Aug 30, 2007 at 10:42am
-
I'm not (primarily) a Java developer... I started with PHP, and have focused most of my learning on it.posted by Peter Goodman on Dec 29, 2008 at 12:40am
-
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) . ""; }posted by pcarini on Aug 30, 2007 at 6:30pm
-
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. :)posted by Nick Ohrn on Aug 30, 2007 at 11:01pm
-
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.posted by pcarini on Aug 30, 2007 at 6:31pm
-
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".posted by Chris Buckley on Sep 1, 2007 at 12:39am
-
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.posted by JasonMichael on Oct 10, 2007 at 6:44pm
-
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.posted by Stephen Clay on Sep 2, 2007 at 2:30am
-
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?posted by Peter Goodman on Dec 29, 2008 at 12:43am
-
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?posted by Luke Visinoni on Sep 17, 2007 at 10:01pm
-
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 ;) .posted by Xavier Lapointe on Sep 10, 2007 at 3:24pm
-
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!posted by Emilio López on Jul 13, 2008 at 9:16pm
-
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.posted by Peter Goodman on Feb 21, 2009 at 7:30pm
-
Yes, sorry, I manually moderate comments (as well as let akismet do some work).posted by Peter Goodman on Jun 21, 2008 at 6:36am
-
#2 can be made to work using the __call magic method.posted by John McKnight on Jun 21, 2008 at 4:20am
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"); ?> -
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");posted by John McKnight on Jun 21, 2008 at 4:27pm
-
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...posted by andrezero on Mar 18, 2009 at 3:11pm
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.
My 2 cents... (man I really need to get my blog back) -
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.posted by Peter Goodman on Mar 19, 2009 at 4:57pm
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. -
The fact that you could only find 12 things to dislike makes me love it even more.posted by Adam on May 16, 2009 at 7:36pm
Comment
