RSS Feed

Peter Goodman's blog about PHP, Parsing Theory, C++, Functional Programming, Applications,

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


Comment