PHP has lots of problems like 0 == 'a' which appears obvious (the PHP mollahs will scream "use === !!!" at you) but in fact is far from obvious, as in practice, it is difficult to distinguish between null, '', 0, 0.0, and sometimes everything else, which leads to an endless stream of very subtle and very contagious bugs. Where in your form library did the null get transformed into '' ? How come your default values don't default anymore ? What is the difference between an empty text field and an unsibmitted field ? So you dromn under a steaming pile of '===' and is_null() and array_key_exists(), whereas Python gives you "is None", and "if x in ...".
PHP gets the basics wrong, by trying to be simple, it becomes too simple, and it ends up biting your ass. Where Python will throw an exception and tell you "Fix your code !", PHP will carefully and lovingly nurture your bugs, hiding them in a secret place, saving them for the worst possible moment, when it will smash them into your face, and you won't know what hit you, since there so many foot-guns pointing at you from all directions.
The biggest problem I had with PHP though, is the broken object model. When you want to write an ORM framework which automatically generate admin interfaces and forms (kind of like Rails), you need a good object model like Python has. However, in PHP, classes are not first class, functions are not first class, you have no closures, nothing to help you. If you want to pass a bit of code, you got to put it in a class, which sucks, when you just want to pass a validator which says "lambda x: 1 <= x <10", or "lambda fields: fields.start_date <= fields.end_date".
So, in PHP,
if class BlogPost derives from class DBObject,
and you call a static method BlogPost::displayCreationForm( ), because you want a form to create an object of class BlogPost.
However, in a normal framework, BlogPost::displayCreationForm( ) is not overriden, it is only defined in the base class : DBObject::displayCreationForm(), which calls BlogPost::getFieldList() to know exactly which fields it should put in the said form.
However, since the object model doesn't work, a static method from DBObject cannot call a static method from BlogPost. Worse, if a static method from BlogPost calls a static method from DBObjectA, this method cannot call a static method from BlogPost. You have gone backwards in the class hierarchy and cannot go back.
So, displayCreationForm() will call getFieldsList() and get the fields from DBObject, not BlogPost. Since DBObject is a generic class it has no fields. Bummer.
Now you want to call :
$post = BlogPost::getFromDatabase( $id )
No, cannot do. getTable() can't be static, you're fucked, you need to do :
$tmp = (new BlogPost);
$post = $tmp->getFromDatabase( $id )
Note that the returned object can be another class than BlogPost, it can be an ImageGallery or whatever, since the information about the class is encoded in the database and it's desired that a CategoryPage has children of type ImageGallery and BlogPost and whatnot.
Result : YOU CANNOT USE STATIC METHODS. You got to instantiate dummy objects and call regular methods.
Result : my framework is littered with "$tmp = new $classname", all over the place.
It was a complete pain to write. I stopped adding features when the lack of closures became unbearable. How do I control the formatting for a specific field in the autogenerated admin ? Create yet another class ? Bleh.
In Python, it works.