PHP: a fractal of bad design
Preface
I’m cranky. I complain about a lot of things. There’s a lot in the world of technology I don’t like, and that’s really to be expected—programming is a hilariously young discipline, and none of us have the slightest clue what we’re doing. Combine with Sturgeon’s Law, and I have a lifetime’s worth of stuff to gripe about.
This is not the same. PHP is not merely awkward to use, or ill-suited for what I want, or suboptimal, or against my religion. I can tell you all manner of good things about languages I avoid, and all manner of bad things about languages I enjoy. Go on, ask! It makes for interesting conversation.
PHP is the lone exception. Virtually every feature in PHP is broken somehow. The language, the framework, the ecosystem, are all just bad. And I can’t even point out any single damning thing, because the damage is so systemic. Every time I try to compile a list of PHP gripes, I get stuck in this depth-first search discovering more and more appalling trivia. (Hence, fractal.)
PHP is an embarrassment, a blight upon my craft. It’s so broken, but so lauded by every empowered amateur who’s yet to learn anything else, as to be maddening. It has paltry few redeeming qualities and I would prefer to forget it exists at all.
But I’ve got to get this out of my system. So here goes, one last try.
An analogy
I just blurted this out to Mel to explain my frustration and she insisted that I reproduce it here.
I can’t even say what’s wrong with PHP, because— okay. Imagine you have uh, a toolbox. A set of tools. Looks okay, standard stuff in there.
You pull out a screwdriver, and you see it’s one of those weird tri-headed things. Okay, well, that’s not very useful to you, but you guess it comes in handy sometimes.
You pull out the hammer, but to your dismay, it has the claw part on both sides. Still serviceable though, I mean, you can hit nails with the middle of the head holding it sideways.
You pull out the pliers, but they don’t have those serrated surfaces; it’s flat and smooth. That’s less useful, but it still turns bolts well enough, so whatever.
And on you go. Everything in the box is kind of weird and quirky, but maybe not enough to make it completely worthless. And there’s no clear problem with the set as a whole; it still has all the tools.
Now imagine you meet millions of carpenters using this toolbox who tell you “well hey what’s the problem with these tools? They’re all I’ve ever used and they work fine!” And the carpenters show you the houses they’ve built, where every room is a pentagon and the roof is upside-down. And you knock on the front door and it just collapses inwards and they all yell at you for breaking their door.
That’s what’s wrong with PHP.
Stance
I assert that the following qualities are important for making a language productive and useful, and PHP violates them with wild abandon. If you can’t agree that these are crucial, well, I can’t imagine how we’ll ever agree on much.
- A language must be predictable. It’s a medium for expressing human ideas and having a computer execute them, so it’s critical that a human’s understanding of a program actually be correct.
- A language must be consistent. Similar things should look similar, different things different. Knowing part of the language should aid in learning and understanding the rest.
- A language must be concise. New languages exist to reduce the boilerplate inherent in old languages. (We could all write machine code.) A language must thus strive to avoid introducing new boilerplate of its own.
- A language must be reliable. Languages are tools for solving problems; they should minimize any new problems they introduce. Any “gotchas” are massive distractions.
- A language must be debuggable. When something goes wrong, the programmer has to fix it, and we need all the help we can get.
My position is thus:
- PHP is full of surprises:
mysql_real_escape_string
,E_ALL
- PHP is inconsistent:
strpos
,str_rot13
- PHP requires boilerplate: error-checking around C API calls,
===
- PHP is flaky:
==
,foreach ($foo as &$bar)
- PHP is opaque: no stack traces by default or for fatals, complex error reporting
I can’t provide a paragraph of commentary for every single issue explaining why it falls into these categories, or this would be endless. I trust the reader to, like, think.
Don’t comment with these things
I’ve been in PHP arguments a lot. I hear a lot of very generic counter-arguments that are really only designed to halt the conversation immediately. Don’t pull these on me, please. :(
-
Do not tell me that “good developers can write good code in any language”, or bad developers blah blah. That doesn’t mean anything. A good carpenter can drive in a nail with either a rock or a hammer, but how many carpenters do you see bashing stuff with rocks? Part of what makes a good developer is the ability to choose the tools that work best.
-
Do not tell me that it’s the developer’s responsibility to memorize a thousand strange exceptions and surprising behaviors. Yes, this is necessary in any system, because computers suck. That doesn’t mean there’s no upper limit for how much zaniness is acceptable in a system. PHP is nothing but exceptions, and it is not okay when wrestling the language takes more effort than actually writing your program. My tools should not create net positive work for me to do.
-
Do not tell me “that’s how the C API works”. What on Earth is the point of using a high-level language if all it provides are some string helpers and a ton of verbatim C wrappers? Just write C! Here, there’s even a CGI library for it.
-
Do not tell me “that’s what you get for doing weird things”. If two features exist, someday, someone will find a reason to use them together. And again, this isn’t C; there’s no spec, there’s no need for “undefined behavior”.
-
Do not tell me that Facebook and Wikipedia are built in PHP. I’m aware! They could also be written in Brainfuck, but as long as there are smart enough people wrangling the things, they can overcome problems with the platform. For all we know, development time could be halved or doubled if these products were written in some other language; this data point alone means nothing.
-
Ideally, don’t tell me anything! This is my one big shot; if this list doesn’t hurt your opinion of PHP, nothing ever will, so stop arguing with some dude on the Internet and go make a cool website in record time to prove me wrong :)
Side observation: I loooove Python. I will also happily talk your ear off complaining about it, if you really want me to. I don’t claim it’s perfect; I’ve just weighed its benefits against its problems and concluded it’s the best fit for things I want to do.
And I have never met a PHP developer who can do the same with PHP. But I’ve bumped into plenty who are quick to apologize for anything and everything PHP does. That mindset is terrifying.
PHP
Core language
CPAN has been called the “standard library of Perl”. That doesn’t say much about Perl’s standard library, but it makes the point that a solid core can build great things.
Philosophy
-
PHP was originally designed explicitly for non-programmers (and, reading between the lines, non-programs); it has not well escaped its roots. A choice quote from the PHP 2.0 documentation, regarding
+
and friends doing type conversion:Once you start having separate operators for each type you start making the language much more complex. ie. you can’t use ‘==’ for stings [sic], you now would use ‘eq’. I don’t see the point, especially for something like PHP where most of the scripts will be rather simple and in most cases written by non-programmers who want a language with a basic logical syntax that doesn’t have too high a learning curve.
- PHP is built to keep chugging along at all costs. When faced with either doing something nonsensical or aborting with an error, it will do something nonsensical. Anything is better than nothing.
- There’s no clear design philosophy. Early PHP was inspired by Perl; the huge stdlib with “out” params is from C; the OO parts are designed like C++ and Java.
- PHP takes vast amounts of inspiration from other languages, yet still manages to be incomprehensible to anyone who knows those languages.
(int)
looks like C, butint
doesn’t exist. Namespaces use\
. The new array syntax results in[key => value]
, unique among every language with hash literals. - Weak typing (i.e., silent automatic conversion between strings/numbers/et al) is so complex that whatever minor programmer effort is saved is by no means worth it.
- Little new functionality is implemented as new syntax; most of it is done with functions or things that look like functions. Except for class support, which deserved a slew of new operators and keywords.
- Some of the problems listed on this page do have first-party solutions—if you’re willing to pay Zend for fixes to their open-source programming language.
-
There is a whole lot of action at a distance. Consider this code, taken from the PHP docs somewhere.
@fopen('example.com/not-existing-file', 'r');
What will it do?
- If PHP was compiled with
--disable-url-fopen-wrapper
, it won’t work. (Docs don’t say what “won’t work” means; returns null, throws exception?) Note that this flag was removed in PHP 5.2.5. - If
allow_url_fopen
is disabled in php.ini, this still won’t work. (How? No idea.) - Because of the
@
, the warning about the non-existent file won’t be printed. - But it will be printed if
scream.enabled
is set in php.ini. - Or if
scream.enabled
is set manually withini_set
. - But not if the right
error_reporting
level isn’t set. - If it is printed, exactly where it goes depends on
display_errors
, again in php.ini. Orini_set
.
I can’t tell how this innocuous function call will behave without consulting compile-time flags, server-wide configuration, and configuration done in my program. And this is all built in behavior.
- If PHP was compiled with
- The language is full of global and implicit state.
mbstring
uses a global character set.func_get_arg
and friends look like regular functions, but operate on the currently-executing function. Error/exception handling have global defaults.register_tick_function
sets a global function to run every tick—what?! - There is no threading support whatsoever. (Not surprising, given the above.) Combined with the lack of built-in
fork
(mentioned below), this makes parallel programming extremely difficult. - Parts of PHP are practically designed to produce buggy code.
json_decode
returns null for invalid input, even though null is also a perfectly valid object for JSON to decode to—this function is completely unreliable unless you also calljson_last_error
every time you use it.array_search
,strpos
, and similar functions return0
if they find the needle at position zero, but false if they don’t find it at all.
Let me expand on that last part a bit.
In C, functions like
strpos
return-1
if the item isn’t found. If you don’t check for that case and try to use that as an index, you’ll hit junk memory and your program will blow up. (Probably. It’s C. Who the fuck knows. I’m sure there are tools for this, at least.)In, say, Python, the equivalent
.index
methods will raise an exception if the item isn’t found. If you don’t check for that case, your program will blow up.In PHP, these functions return false. If you use
FALSE
as an index, or do much of anything with it except compare with===
, PHP will silently convert it to0
for you. Your program will not blow up; it will, instead, do the wrong thing with no warning, unless you remember to include the right boilerplate around every place you usestrpos
and certain other functions.This is bad! Programming languages are tools; they’re supposed to work with me. Here, PHP has actively created a subtle trap for me to fall into, and I have to be vigilant even with such mundane things as string operations and equality comparison. PHP is a minefield.
I have heard a great many stories about the PHP interpreter and its developers from a great many places. These are from people who have worked on the PHP core, debugged PHP core, interacted with core developers. Not a single tale has been a compliment.
So I have to fit this in here, because it bears repeating: PHP is a community of amateurs. Very few people designing it, working on it, or writing code in it seem to know what they’re doing. (Oh, dear reader, you are of course a rare exception!) Those who do grow a clue tend to drift away to other platforms, reducing the average competence of the whole. This, right here, is the biggest problem with PHP: it is absolutely the blind leading the blind.
Okay, back to facts.
Operators
==
is useless.- It’s not transitive.
"foo" == TRUE
, and"foo" == 0
… but, of course,TRUE != 0
. ==
converts to numbers when possible (123 == "123foo"
… although"123" != "123foo"
), which means it converts to floats when possible. So large hex strings (like, say, password hashes) may occasionally compare true when they’re not. Even JavaScript doesn’t do this.- For the same reason,
"6" == " 6"
,"4.2" == "4.20"
, and"133" == "0133"
. But note that133 != 0133
, because0133
is octal. But"0x10" == "16"
and"1e3" == "1000"
! ===
compares values and type… except with objects, where===
is only true if both operands are actually the same object! For objects,==
compares both value (of every attribute) and type, which is what===
does for every other type. What.
- It’s not transitive.
- Comparison isn’t much better.
- It’s not even consistent:
NULL < -1
, andNULL == 0
. Sorting is thus nondeterministic; it depends on the order in which the sort algorithm happens to compare elements. - The comparison operators try to sort arrays, two different ways: first by length, then by elements. If they have the same number of elements but different sets of keys, though, they are uncomparable.
- Objects compare as greater than anything else… except other objects, which they are neither less than nor greater than.
- For a more type-safe
==
, we have===
. For a more type-safe<
, we have… nothing."123" < "0124"
, always, no matter what you do. Casting doesn’t help, either.
- It’s not even consistent:
- Despite the craziness above, and the explicit rejection of Perl’s pairs of string and numeric operators, PHP does not overload
+
.+
is always addition, and.
is always concatenation. - The
[]
indexing operator can also be spelled{}
. []
can be used on any variable, not just strings and arrays. It returns null and issues no warning.[]
cannot slice; it only retrieves individual elements.foo()[0]
is a syntax error. (Fixed in PHP 5.4.)-
Unlike (literally!) every other language with a similar operator,
?:
is left associative. So this:$arg = 'T'; $vehicle = ( ( $arg == 'B' ) ? 'bus' : ( $arg == 'A' ) ? 'airplane' : ( $arg == 'T' ) ? 'train' : ( $arg == 'C' ) ? 'car' : ( $arg == 'H' ) ? 'horse' : 'feet' ); echo $vehicle;
prints
horse
.
Variables
- There is no way to declare a variable. Variables that don’t exist are created with a null value when first used.
- Global variables need a
global
declaration before they can be used. This is a natural consequence of the above, so it would be perfectly reasonable, except that globals can’t even be read without an explicit declaration—PHP will quietly create a local with the same name, instead. I’m not aware of another language with similar scoping issues. - There are no references. What PHP calls references are really aliases; there’s nothing that’s a step back, like Perl’s references, and there’s no pass-by-object identity like in Python.
- “Referenceness” infects a variable unlike anything else in the language. PHP is dynamically-typed, so variables generally have no type… except references, which adorn function definitions, variable syntax, and assignment. Once a variable is made a reference (which can happen anywhere), it’s stuck as a reference. There’s no obvious way to detect this and un-referencing requires nuking the variable entirely.
- Okay, I lied. There are “SPL types” which also infect variables:
$x = new SplBool(true); $x = "foo";
will fail. This is like static typing, you see. - A reference can be taken to a key that doesn’t exist within an undefined variable (which becomes an array). Using a non-existent array normally issues a notice, but this does not.
- Constants are defined by a function call taking a string; before that, they don’t exist. (This may actually be a copy of Perl’s
use constant
behavior.) - Variable names are case-sensitive. Function and class names are not. This includes method names, which makes camelCase a strange choice for naming.
Constructs
array()
and a few dozen similar constructs are not functions.array
on its own means nothing,$func = "array"; $func();
doesn’t work.- Array unpacking can be done with the
list($a, $b) = ...
operation.list()
is function-like syntax just likearray
. I don’t know why this wasn’t given real dedicated syntax, or why the name is so obviously confusing. (int)
is obviously designed to look like C, but it’s a single token; there’s nothing calledint
in the language. Try it: not only doesvar_dump(int)
not work, it throws a parse error because the argument looks like the cast operator.(integer)
is a synonym for(int)
. There’s also(bool)
/(boolean)
and(float)
/(double)
/(real)
.- There’s an
(array)
operator for casting to array and an(object)
for casting to object. That sounds nuts, but there’s almost a use: you can use(array)
to have a function argument that’s either a single item or a list, and treat it identically. Except you can’t do that reliably, because if someone passes a single object, casting it to an array will actually produce an array containing that object’s attributes. (Casting to object performs the reverse operation.) include()
and friends are basically C’s#include
: they dump another source file into yours. There is no module system, even for PHP code.- There’s no such thing as a nested or locally-scoped function or class. They’re only global. Including a file dumps its variables into the current function’s scope (and gives the file access to your variables), but dumps functions and classes into global scope.
- Appending to an array is done with
$foo[] = $bar
. echo
is a statement-y kind of thing, not a function.empty($var)
is so extremely not-a-function that anything but a variable, e.g.empty($var || $var2)
, is a parse error. Why on Earth does the parser need to know aboutempty
?- There’s redundant syntax for blocks:
if (...): ... endif;
, etc.
Error handling
- PHP’s one unique operator is
@
(actually borrowed from DOS), which silences errors. - PHP errors don’t provide stack traces. You have to install a handler to generate them. (But you can’t for fatal errors—see below.)
- PHP parse errors generally just spew the parse state and nothing more, making a forgotten quote terrible to debug.
- PHP’s parser refers to e.g.
::
internally asT_PAAMAYIM_NEKUDOTAYIM
, and the<<
operator asT_SL
. I say “internally”, but as above, this is what’s shown to the programmer when::
or<<
appears in the wrong place. - Most error handling is in the form of printing a line to a server log nobody reads and carrying on.
E_STRICT
is a thing, but it doesn’t seem to actually prevent much and there’s no documentation on what it actually does.E_ALL
includes all error categories—exceptE_STRICT
. (Fixed in 5.4.)-
Weirdly inconsistent about what’s allowed and what isn’t. I don’t know how
E_STRICT
applies here, but these things are okay:- Trying to access a non-existent object property, i.e.,
$foo->x
. (warning) - Using a variable as a function name, or variable name, or class name. (silent)
- Trying to use an undefined constant. (notice)
- Trying to access a property of something that isn’t an object. (notice)
- Trying to use a variable name that doesn’t exist. (notice)
2 < "foo"
(silent)foreach (2 as $foo);
(warning)
And these things are not:
- Trying to access a non-existent class constant, i.e.,
$foo::x
. (fatal error) - Using a constant string as a function name, or variable name, or class name. (parse error)
- Trying to call an undefined function. (fatal error)
- Leaving off a semicolon on the last statement in a block or file. (parse error)
- Using
list
and various other quasi-builtins as method names. (parse error) - Subscripting the return value of a function, i.e.,
foo()[0]
. (parse error; okay in 5.4, see above)
There are a good few examples of other weird parse errors elsewhere in this list.
- Trying to access a non-existent object property, i.e.,
- The
__toString
method can’t throw exceptions. If you try, PHP will… er, throw an exception. (Actually a fatal error, which would be passable, except…) - PHP errors and PHP exceptions are completely different beasts. They don’t seem to interact at all.
- PHP errors (internal ones, and calls to
trigger_error
) cannot be caught withtry
/catch
. - Likewise, exceptions do not trigger error handlers installed by
set_error_handler
. - Instead, there’s a separate
set_exception_handler
which handles uncaught exceptions, because wrapping your program’s entry point in atry
block is impossible in themod_php
model. - Fatal errors (e.g.,
new ClassDoesntExist()
) can’t be caught by anything. A lot of fairly innocuous things throw fatal errors, forcibly ending your program for questionable reasons. Shutdown functions still run, but they can’t get a stack trace (they run at top-level), and they can’t easily tell if the program exited due to an error or running to completion.
- PHP errors (internal ones, and calls to
- There is no
finally
construct, making wrapper code (set handler, run code, unset handler; monkeypatch, run a test, unmonkeypatch) tedious and difficult to write. Despite that OO and exceptions were largely copied from Java, this is deliberate, becausefinally
“doesn’t make much sense in the context of PHP”. Huh?
Functions
- Function calls are apparently rather expensive.
- Some built-in functions interact with reference-returning functions in, er, a strange way.
- As mentioned elsewhere, a lot of things that look like functions or look like they should be functions are actually language constructs, so nothing that works with functions will work with them.
-
Function arguments can have “type hints”, which are basically just static typing. But you can’t require that an argument be an
int
orstring
orobject
or other “core” type, even though every builtin function uses this kind of typing, probably becauseint
is not a thing in PHP. (See above about(int)
.) You also can’t use the special pseudo-type decorations used heavily by builtin functions:mixed
,number
, orcallback
. (callable
is allowed as of PHP 5.4.)-
As a result, this:
function foo(string $s) {} foo("hello world");
produces the error:
PHP Catchable fatal error: Argument 1 passed to foo() must be an instance of string, string given, called in...
- You may notice that the “type hint” given doesn’t actually have to exist; there is no
string
class in this program. If you try to useReflectionParameter::getClass()
to examine the type hint dynamically, then it will balk that the class doesn’t exist, making it impossible to actually retrieve the class name. - A function’s return value can’t be hinted.
-
- Passing the current function’s arguments to another function (dispatch, not uncommon) is done by
call_user_func_array('other_function', func_get_args())
. Butfunc_get_args
throws a fatal error at runtime, complaining that it can’t be a function parameter. How and why is this even a type of error? (Fixed in PHP 5.3.) - Closures require explicitly naming every variable to be closed-over. Why can’t the interpreter figure this out? Kind of hamstrings the whole feature. (Okay, it’s because using a variable ever, at all, creates it unless explicitly told otherwise.)
- Closed-over variables are “passed” by the same semantics as other function arguments. That is, arrays and strings etc. will be “passed” to the closure by value. Unless you use
&
. - Because closed-over variables are effectively automatically-passed arguments and there are no nested scopes, a closure can’t refer to private methods, even if it’s defined inside a class. (Possibly fixed in 5.4? Unclear.)
- No named arguments to functions. Actually explicitly rejected by the devs because it “makes for messier code”.
- Function arguments with defaults can appear before function arguments without, even though the documentation points out that this is both weird and useless. (So why allow it?)
- Extra arguments to a function are ignored (except with builtin functions, which raise an error). Missing arguments are assumed null.
- “Variadic” functions require faffing about with
func_num_args
,func_get_arg
, andfunc_get_args
. There’s no syntax for such a thing.
OO
- The procedural parts of PHP are designed like C, but the objectional (ho ho) parts are designed like Java. I cannot overemphasize how jarring this is. The class system is designed around the lower-level Java language which is naturally and deliberately more limited than PHP’s contemporaries, and I am baffled.
- I’ve yet to find a global function that even has a capital letter in its name, yet important built-in classes use camelCase method names and have
getFoo
Java-style accessors. - Perl, Python, and Ruby all have some concept of “property” access via code; PHP has only the clunky
__get
and friends. (The documentation inexplicably refers to such special methods as “overloading”.) - Classes have something like variable declaration (
var
andconst
) for class attributes, whereas the procedural part of the language does not. - Despite the heavy influence from C++/Java, where objects are fairly opaque, PHP often treats objects like fancy hashes—for example, the default behavior of
foreach ($obj as $key => $value)
is to iterate over every accessible attribute of the object.
- I’ve yet to find a global function that even has a capital letter in its name, yet important built-in classes use camelCase method names and have
- Classes are not objects. Any metaprogramming has to refer to them by string name, just like functions.
- Built-in types are not objects and (unlike Perl) can in no way be made to look like objects.
instanceof
is an operator, despite that classes were a late addition and most of the language is built on functions and function-ish syntax. Java influence? Classes not first-class? (I don’t know if they are.)- But there is an
is_a
function. With an optional argument specifying whether to allow the object to actually be a string naming a class. get_class
is a function; there’s notypeof
operator. Likewiseis_subclass_of
.- This doesn’t work on builtin types, though (again,
int
is not a thing). For that, you needis_int
etc. - Also the right-hand side has to be a variable or literal string; it can’t be an expression. That causes… a parse error.
- But there is an
clone
is an operator?!- Object attributes are
$obj->foo
, but class attributes areClass::$foo
. ($obj::$foo
will try to stringify$obj
and use it as a class name.) Class attributes can’t be accessed via objects; the namespaces are completely separate, making class attributes completely useless for polymorphism. Class methods, of course, are exempt from this rule and can be called like any other method. (I am told C++ also does this. C++ is not a good example of fine OO.) - Also, an instance method can still be called statically (
Class::method()
). If done so from another method, this is treated like a regular method call on the current$this
. I think. new
,private
,public
,protected
,static
, etc. Trying to win over Java developers? I’m aware this is more personal taste, but I don’t know why this stuff is necessary in a dynamic language—in C++ most of it’s about compilation and compile-time name resolution.- PHP has first-class support for “abstract classes”, which are classes that cannot be instantiated. Code in similar languages achieves this by throwing an exception in the constructor.
- Subclasses cannot override private methods. Subclass overrides of public methods can’t even see, let alone call, the superclass’s private methods. Problematic for, say, test mocks.
- Methods cannot be named e.g. “list”, because
list()
is special syntax (not a function) and the parser gets confused. There’s no reason this should be ambiguous, and monkeypatching the class works fine. ($foo->list()
is not a syntax error.) - If an exception is thrown while evaluating a constructor’s arguments (e.g.,
new Foo(bar())
andbar()
throws), the constructor won’t be called, but the destructor will be. (This is fixed in PHP 5.3.) - Exceptions in
__autoload
and destructors cause fatal errors. (Fixed in PHP 5.3.6. So now a destructor might throw an exception literally anywhere, since it’s called the moment the refcount drops the zero. Hmm.) - There are no constructors or destructors.
__construct
is an initializer, like Python’s__init__
. There is no method you can call on a class to allocate memory and create an object. - There is no default initializer. Calling
parent::__construct()
if the superclass doesn’t define its own__construct
is a fatal error. - OO brings with it an iterator interface that parts of the language (e.g.,
for...as
) respect, but nothing built-in (like arrays) actually implements the interface. If you want an array iterator, you have to wrap it in anArrayIterator
. There are no built-in ways to chain or slice or otherwise work with iterators as first-class objects. - Interfaces like
Iterator
reserve a good few unprefixed method names. If you want your class to be iterable (without the default behavior of iterating all of its attributes), but want to use a common method name likekey
ornext
orcurrent
, well, too bad. - Classes can overload how they convert to strings and how they act when called, but not how they convert to numbers or any other builtin type.
- Strings, numbers, and arrays all have a string conversion; the language relies heavily on this. Functions and classes are strings. Yet trying to convert a built-in or user-defined object (even a Closure) to a string causes an error if it doesn’t define
__toString
. Evenecho
becomes potentially error-prone. - There is no overloading for equality or ordering.
- Static variables inside instance methods are global; they share the same value across all instances of the class.
Standard library
Perl is “some assembly required”. Python is “batteries included”. PHP is “kitchen sink, but it’s from Canada and both faucets are labeled C”.
General
- There is no module system. You can compile PHP extensions, but which ones are loaded is specified by php.ini, and your options are for an extension to exist (and inject its contents into your global namespace) or not.
- As namespaces are a recent feature, the standard library isn’t broken up at all. There are thousands of functions in the global namespace.
- Chunks of the library are wildly inconsistent from one another.
- Underscore versus not:
strpos
/str_rot13
,php_uname
/phpversion
,base64_encode
/urlencode
,gettype
/get_class
- “to” versus 2:
ascii2ebcdic
,bin2hex
,deg2rad
,strtolower
,strtotime
- Object+verb versus verb+object:
base64_decode
,str_shuffle
,var_dump
versuscreate_function
,recode_string
- Argument order:
array_filter($input, $callback)
versusarray_map($callback, $input)
,strpos($haystack, $needle)
versusarray_search($needle, $haystack)
- Prefix confusion:
usleep
versusmicrotime
- Case insensitive functions vary on where the
i
goes in the name. - About half the array functions actually start with
array_
. The others do not. htmlentities
andhtml_entity_decode
are inverses of each other, with completely different naming conventions.
- Underscore versus not:
- Kitchen sink. The libary includes:
- Bindings to ImageMagick, bindings to GraphicsMagick (which is a fork of ImageMagick), and a handful of functions for inspecting EXIF data (which ImageMagick can already do).
- Functions for parsing bbcode, a very specific kind of markup used by a handful of particular forum packages.
- Way too many XML packages.
DOM
(OO),DOM XML
(not),libxml
,SimpleXML
, “XML Parser”,XMLReader
/XMLWriter
, and half a dozen more acronyms I can’t identify. There’s surely some kind of difference between these things and you are free to go figure out what that is. - Bindings for two particular credit card processors, SPPLUS and MCVE. What?
- Three ways to access a MySQL database:
mysql
,mysqli
, and thePDO
abstraction thing.
C influence
This deserves its own bullet point, because it’s so absurd yet permeates the language. PHP is a high-level, dynamically-typed programming language. Yet a massive portion of the standard library is still very thin wrappers around C APIs, with the following results:
- “Out” parameters, even though PHP can return ad-hoc hashes or multiple arguments with little effort.
- At least a dozen functions for getting the last error from a particular subsystem (see below), even though PHP has had exceptions for eight years.
- Warts like
mysql_real_escape_string
, even though it has the same arguments as the brokenmysql_escape_string
, just because it’s part of the MySQL C API. - Global behavior for non-global functionality (like MySQL). Using multiple MySQL connections apparently requires passing a connection handle on every function call.
- The wrappers are really, really, really thin. For example, calling
dba_nextkey
without callingdba_firstkey
will segfault. - There’s a set of
ctype_*
functions (e.g.ctype_alnum
) that map to the C character-class detection functions of similar names, rather than, say,isupper
.
Genericism
There is none. If a function might need to do two slightly different things, PHP just has two functions.
How do you sort backwards? In Perl, you might do sort { $b <=> $a }
. In Python, you might do .sort(reverse=True)
. In PHP, there’s a separate function called rsort()
.
- Functions that look up a C error:
curl_error
,json_last_error
,openssl_error_string
,imap_errors
,mysql_error
,xml_get_error_code
,bzerror
,date_get_last_errors
, others? - Functions that sort:
array_multisort
,arsort
,asort
,ksort
,krsort
,natsort
,natcasesort
,sort
,rsort
,uasort
,uksort
,usort
- Functions that find text:
ereg
,eregi
,mb_ereg
,mb_eregi
,preg_match
,strstr
,strchr
,stristr
,strrchr
,strpos
,stripos
,strrpos
,strripos
,mb_strpos
,mb_strrpos
, plus the variations that do replacements - There are a lot of aliases as well, which certainly doesn’t help matters:
strstr
/strchr
,is_int
/is_integer
/is_long
,is_float
/is_double
,pos
/current
,sizeof
/count
,chop
/rtrim
,implode
/join
,die
/exit
,trigger_error
/user_error
,diskfreespace
/disk_free_space
… scandir
returns a list of files within a given directory. Rather than (potentially usefully) return them in directory order, the function returns the files already sorted. And there’s an optional argument to get them in reverse alphabetical order. There were not, apparently, enough sort functions. (PHP 5.4 adds a third value for the sort-direction argument that will disable sorting.)str_split
breaks a string into chunks of equal length.chunk_split
breaks a string into chunks of equal length, then joins them together with a delimiter.- Reading archives requires a separate set of functions depending on the format. There are six separate groups of such functions, all with different APIs, for bzip2, LZF, phar, rar, zip, and gzip/zlib.
- Because calling a function with an array as its arguments is so awkward (
call_user_func_array
), there are some pairings likeprintf
/vprintf
andsprintf
/vsprintf
. These do the same things, but one function takes arguments and the other takes an array of arguments.
Text
preg_replace
with the/e
(eval) flag will do a string replace of the matches into the replacement string, then eval it.strtok
is apparently designed after the equivalent C function, which is already a bad idea for various reasons. Nevermind that PHP can easily return an array (whereas this is awkward in C), or that the very hackstrtok(3)
uses (modifying the string in-place) isn’t used here.parse_str
parses a <">