Modern PHP is great. Many powerful language features, excellent performance, great community and package ecosystem, and decent enough safety with modern static analysis tools.
I'm not too sure I agree with the author's complaints here. When using something like array_filter, you're typically mapping from collection to collection (i.e. you don't care about the first element--you care about the whole thing) and so this problem is really a non-issue. The next follow up step would usually be foreach, or another operation like array_map, in which case it's a non-issue.
If you really do need the first element, you can use array_first. And if you really do need a fixed-sized collection, you can use SplFixedArray.
The point on properties is valid to an extent, but IMO not really an issue you commonly run into in the real world (regardless of language, your constructors should generally return an object in a usable state).
I have this thing I want to do in C. Now my C is very weak so my plan was to do a prototype in python, keeping in mind the C ecosystem then sit down with my old copy of "The C Programming Language" and struggle through it. Doing it with no dicts was rough.
I am normally the sort to avoid adding any libraries I don't have to but if anyone has any hints to simple hash maps in C I am all ears.
If for some reason you absolutely need to use c, consider if you really need a hashmap. If your collection is relatively small, just use an array or linked list with linear search. That's a pattern I've seen used in several c codebases, I think because of the difficulty of using map types.
If linear search would be too slow, then a binary search tree is relatively easy to implement, and gives you log(n) lookup time (as long as it's balanced). Or if you build it up once and don't modify it very much afterwards, you can use a sorted array, with a binary search.
If you really need a hashmap, there are some implementations, but I've also seen a few c projects that just implement their own hashmaps.
0) use Lua with LuaJIT. It has very good C interop and native hashmaps. Downside - Lua has its own rough edges and LuaJIT is frozen at Lua 5.1 with some extensions for 5,2
1) SDL3 has a hashmap implementation with its properties API, and a general purpose hashmap that they're probably going to make public at some point maybe? Downside - overkill if you don't actually want to use the rest of the library.
2) https://github.com/tidwall/hashmap.c this seems to work fine and is the simplest hashmap implementation for C that I've seen. Downside - you still have to write a lot of boilerplate.
<p><?php echo(htmlspecialchars($_GET['user'])); ?></p>
and you get a hello page with a parameter specifiable via `?user=` query.But then people started to actually use it to build big sites, `echo($_GET['user'])` alone is not enough, it has to be:
<?php
$user = "guest";
if (!empty($_GET['user'])) { // Have to remember to do this check everytime when handling $_GET/$_POST etc
... safety check etc etc
$user = htmlspecialchars($_GET['user'], ...more parameters...);
}
...
So people started to add module/components as well as ways to load and use those components, to enable them to write code like: <?php
use My\Beautiful\InputFilter;
use My\Beautiful\InputFilters\Integer;
function get_page_num_from_query(InputFilter $f, array $source, string $name): bool {
return $f->is_valid(new Integer(0, 100), $source, $name);
} // Or something like that, hasn't write a single line since PHP5.
That's when it got it's Java look.Nice Java burn! But now days all you have to say to burn Java is "Lawnmower".
To get the first element there also is reset().
I love PHP though.
That probably needs an array_second too, doesn't it? Maybe array_second_from_last as well?
I wasn't aware of either of those behaviors going into that debugging session.
Most type checking happens at runtime (this might not be true for interfaces at some level, but I can’t say for 100% certain - I just know I tend to see interface related errors earlier during code execution…). It’s perfectly valid syntax to declare a private method as returning an integer and then for the body of the method to return a string (explicitly cast as a string even). As long as you never call that method at runtime, no exceptions will be thrown.
With a half decent IDE or LSP, these sorts of runtime exceptions can be easily avoided but technically they still exist and if you don’t know about that, it can be argued to be confusing. PHP has made a lot of trade-offs to largely maintain backwards compatibility and many of them live in decisions that happen at runtime.
Modern PHP tooling can provide type safety in a very similar way to Typescript if you’re willing to put in the effort while also still technically offering you an escape hatch to do whatever the heck you want and duck type to your hearts content.
I always thought that DSLs were the one thing Ruby did better than the competition, but Kotlin's combination of receiver lambdas plus syntactic sugar for calling higher-order functions make it an even better language to write DSLs in.
And the code I'm looking at now with Kotlin is so similar to code i liked reading when I was in a committed relationship with Ruby.
In PHP though the STDlib is not very well thought out, it may be fun(needle,haystack) or fun(haystack,needle) and you just have to remember
empty() will do weird stuff like a string with the value "0" is also empty, so not great for parsing things.
A lot of footguns and the best way to avoid that is with a decent linter that lets you be picky
another thing I mention is avoiding the array_ stuff because of aformentioned reasons, it's easier to remember/reason about boring loops unfortunately.
I heard this a long time ago about perl. CPAN is great.
Well ... perl entered the fossilized era. I think people do not really observe things correctly. I am noticing the same with ruby right now - everyone sees that ruby is in decline, very strongly so, in the last 3 years. Yet you have blog posts such as "ruby is not dying - it is aging like fine wine". And these are all NOT BASED ON FACTUAL ANALYSIS. I still think ruby is a great language, but if people are not realistic in their assessment of a situation, what does this tell us about people's evaluation in general? People seem to shy away from criticism. You can see this on reddit too, where moderators ban and censor willy-nilly, or even on github, where you can also quickly get eliminated for not conforming to xyz. It's as if some people are very afraid of strong opinions. I don't understand why - an opinion that is objectively false, can be shown to be false.
> Ruby is dying
How exactly do you define these “objective” criteria for such sensationalism?
What actual criticism of PHP is anyone shying away from?
PHP got bashed for such a long time, while simply nothing steps up to do what it does better. Something that, for example, is available on every webhost you can just throw files at, where all (meaningful) config and state can be in those files.
Why do I bring up containers? Because part of why PHP was so dope in this way was the way you can just define 1 file per endpoint and drop it in public_html, and have no server setup to do. Running say, Rails or ASP.NET or a Java site back then meant doing… a lot more, to your server.
But with Docker, you can just steal a good Dockerfile template from someone else, and it’s just like 3-4 simple files for you to manage for a simple Sinatra (Ruby) or node.js version of the “one-off PHP file” things.
Sure it's full of wtfs and as consistent at French on a good day.
But as a business language, it's as twisted as the business needs its code to be, I found it to be a great match. And there is literally nothing you can't get Laravel to do. It's that good.
Now working with other stacks, most colleagues' features are "wow wow wow slow down this is a big deal" ... In PHP/Laravel, it ain't.
PHP is fast enough, powerful enough and comfortingly weird enough. It reminds you that computers weren't that serious.
Yesterday, Cursor coughed up https://fdedictionary.com for me in PHP. You know what, it is like a dozen files and works awesome, no daemon needed, basically just a web server, some PHP files, and one SQLite file.
Today, Claude Code, the major hotness, made changes to a (different) toy app with less complexity, in node.js and you know what, it has ~49,000 files. And uses a 3rd party SaaS to host the db, and another 3rd party SaaS to do auth, and yet another 3rd party SaaS to import a repo and do deployments and hosting.
Simplicity has a place for some things. I love PHP+SQLite for tiny apps, and LLMs/agents are awesome at it.
I forgot, ChatGPT one-shotted the PHP app and Cursor put the finishing touches on. What a world. Long live PHP, is the point. Arrays and all.
Doing so now raises a deprecation warning, unless you add #[AllowDynamicProperties], and PHP 9 will convert it to an error. I'm told this will simplify internals and unlock optimizations.
Arrays are still fairly awful, but generics may become a reality sooner rather than later, and on that could be built Vec and Dict types, à la Hack. PHP is going to be stuck with arrays as they are now for forever, but they'll at least become optional for new code.
The one thing I really wish PHP would add is structurally typed objects. I really miss it when moving back and forth between PHP and TypeScript.
They could call them anonymous objects if they want to (that would be a more culturally correct analogue to anonymous classes).
Like, I wish it was possible to do
{
string $mystring = $myvar,
}
and have it be equivalent to new class($myvar) {
public function __construct(
readonly public string $mystring,
) {}
}
and then be able to typehint it like function ({ string $mystring } $myobj) {
echo $myobj->mystring;
}
and honestly, why not go all the way and allow type definitions/aliases, something like type myobj_type = { string $mystring };
That'd be great. /** @psalm-type MyobjType = object{mystring: string} */
/**
* @param MyobjType $myobj
*/
function (object $myobj): void
Here are some documentation and examples:- For Psalm, see https://psalm.dev/docs/annotating_code/type_syntax/utility_t...
- For PHPstan, see https://phpstan.org/writing-php-code/phpdoc-types
It may work in your IDE (autocompletion, etc.) but there is no standard on this side. Some IDE have their own parsers, others use one of the LSPs for PHP.
- PHP has `SplFixedArray`[^1] that work similar to the standard arrays you expect from other languages. SPL extension is always available in PHP 5.3+, it is not even possible to compile PHP without it anymore. There is no specific type for list-arrays and associative arrays, but there is an `array_is_list` function to quickly check it.
- For typed properties, if a property is not typed, it is effectively considered `mixed $var = null`. If the property is typed, and has no default value, then it is considered uninitialized, and not allowed to access.
Having them as key-value means, that you can easily just remove some items in the middle, during iteration etc. No automatic shifting happening.
The thing which bites me is when some internal functions actually reindex the array. array_filter does not, but for example array_reverse, array_slice etc. do (preserve_keys always defaults to false). And for array_merge too, but there it's no array_merge(preserve_keys: false), but instead the + operator. (Why is this operator overloaded?!)
On the topic of the uninitialized state, as co-author of that RFC:
I agree with the author that nullable properties should have been auto-initialized to NULL. I haven't ever seen any benefit of an uninitialized state for these. Some co-authors of that RFC disagreed and wished for consistency with the other typed properties. The good thing probably is, that we still could opt to change this with a relatively minor BC break.
For non-nullable properties, I do think there is value. Not every value is actually available/ready in a constructor. Sure you can assign dummy values to properties. But it's requiring you to then manually guard/assert that the property is actually initialized. If you happen to access a non-nullable typed property without isset(), then your code is likely broken anyway and I'm grateful for the Error exception thrown.
Also, PHP has this peculiar feature of ReflectionClass::newInstanceWithoutConstructor(). This is forcibly having an object in an uninitialized state. Whether that feature should exist or not is a good question, but in practice it's helpful for object hydration for example. This was one further motivation to introduce the uninitialized state.
The author of the post suggests checking at the constructor boundary. But this doesn't inhibit objects leaking / not finishing the initialization properly. (class Foo { public stdClass $object; function __construct() { global $foo; $foo = $this; } } new Foo; $foo->object ... is now still existing? PHP doesn't have mechanisms to invalidate objects at a distance. That would be the alternative, but also spooky.) Some choices need to be made, and all choices will have some rough edges.
Side note: I personally never use is_null(), but nearly always isset(). This nicely checks for the uninitialized state too. Static analysis tells me anyway, when I access a variable or property name which can never exist.
This can lead to some unexpected behaviors. For example, I've already been bitten by `array_merge()` whose result is different if its parameters are arrays with numeric indexes.
array_merge(["4 " => "four"], ["5 " => "five"])
// ["4 " => "four", "5 " => "five"]
array_merge(["4" => "four"], ["5" => "five"])
// [0 => "four", 1 => "five"]That's exactly what I've been complaining in my post above. If there were no automatic reindexing, then this wouldn't be a problem either.
I haven't run into any of the quirks. I keep things simple, and it just works. The only problem I ran into was the realization that PHP is the wrong tool for long running tasks. After some rogue requests/users running my whole service to ground, I had to move the long running endpoints to node.
Paid my share of dealing with those problems with PHP 5 and 6 (after coming from PHP 4). I think it became a more sane ecosystem around very late 7.x to 8.
I won't touch PHP ever again, but I'm glad (no irony) that they finally were able to pull it off. There were some good ideas there, then they quickly became victims of their own success.
Nowadays, there's places (Amazon) where PHP is just forbidden at a company-wide level (not joking) because of their early, long-standing reputation of being a mess. Or places where they just gave up and re-implemented their own PHP (Meta). I don't see that changing any time soon.
Hard to take this seriously when there was no PHP 6.
If you were there, you'd remember the UTF8/Unicode fiasco (how long it took, how many people was both relying and struggling with it, and how they needed to cover it up after even some hosting providers attempted a beta upgrade and had to roll it back).
There was a PHP 6 (I'm including the non-updates in PHP 5 "waiting" for it), they just had to rewrite history as of PR damage control back then. That's what the article you linked describes, pretty much.
This might as well be PHP's slogan, tbh.
I've worked with PHP since... good lord, 200...2? And it's wild that this is still the case.
> Not a warning—a FATAL error occurs if you try to access an uninitialized property. This comes up a lot in cases where you try to deserialize data into a PHP object. If a field's data isn't present you might not initialize the property at all.
I don't think that is an issue, except in interpretet type-unsafe languages it is harder to anticipate when writing code whether that value is NULL or undefined/uninitialized. E.g. it is basically the same in C#, but here the compiler warns you that the value is not initialized and forbids some actions (like reading the value of it).
$ php -r 'var_dump("01234" == "1234");' bool(true)
$ php -r 'var_dump("09223372036854775808" == "9223372036854775808");' bool(false)
Php has had a strict equals operator for decades. You not using it is not a language fault.
For the second point: I doubt you'll fine any language where you can just do an equals comparison on floats and it works as expected. That's the nature of floating point numbers.
Phpstan deals well with type definitions, arrays are powerful enough to contain whatever needed, and functions can be stored and passed around easily enough.
As a person who also codes in PHP at work, I still dislike the language syntax part. JS/TS also has terrible parts, but IMHO, compared to PHP, I don't face them as much and they are much easier to avoid when you have enough knowledge and stick to good parts.
As the author mentioned, in my experience PHPs arrays are quite annoying to work with, and you can't do anything about them. This C-like procedural syntax for array and string manipulations makes discoverability harder. Some functions (in_array, implode, shuffle, trim, stripos, lcfirst, etc) still have inconsistent namings despite many of them have a standard prefix in their name, such as array_ or str_.
I mean OK, PHP is still widely used, has new features and is still and far from dying (mostly for reasons other than syntax), but please don't pretend that it hasn't some issues with basic stuff which are still there even in 8.x version.
Just to not write a full article, you can read here: https://waspdev.com/articles/2025-06-12/my-honest-opinion-ab...
If interested, I also listed some annoyances of JS: https://waspdev.com/articles/2025-04-16/what-i-dislike-in-ja...
Maybe you’ll find it helpful
But apparently they deprecated and then changed(?!) the associativity in the past few years, which if anything just makes things even more confusing.
Once again showing how out of date peoples hate for PHP is
!""
into the browser console just to be sure, during code reviews :D "0" == false : true
"" == false : true
" " == false : true
"1" == false : false
!"0" : false
!"" : true
!" " : false
!"1" : false
In PHP: "0" == false : true
"" == false : true
" " == false : true
"1" == false : false
!"0" : true
!"" : true
!" " : false
!"1" : false
Honestly the only way to remain sane in either, but especially if you use both, is to always use === and never use boolean logic (!) when a string could be involved.!someValue is useful only for:
- booleans, including optional booleans (which is why every bool flag should default to false)
- undefined, null (falsy), or object/function (truthy)
It's nice for the second variant to also cover falsy NaN or things like this, for example for forms.
I guess that's where
!!""===false
comes from.But it's this exact case that keeps tripping me up.
What about empty arrays?
Per my original comment, now I'd have to look up if
![]
is false in PHP, or just
empty([]) === true.
So yea I agree, and extend your case to PHP "arrays" (in JS,
!![] === true
is true if ($input) {}
It'll work through every test case you try, and then someone enters a 0 into the field and it's also unexpectedly considered empty.Of couse it can be argued that PHP can be used outside this one way as well, but even e.g. https://symfony.com/doc/current/mercure.html is using golang.
I've heard that PHP has improved a lot since then, but I don't see how you could really fix all the inconsistencies, global state, and "oddities" without a lot of breaking changes and really making it into a different language.
It's been debunked so many times over the years, I'm afraid I don't have the energy or desire to do it again when it's really not needed if you're far out of the php ecosystem that it really won't make a difference. Suffice to say the PHP it is talking about is nothing at all like modern PHP.
One of the best things with PHP is PsySH, or "Tinker" as the laravelists call it. It's not a REPL in the Common Lisp sense, but it is quite nice for an interactive programming shell. I've spent countless hours solving problems very, very quickly in it, and alongside Picolisp pil + and Elixir iex it's one of the earliest tools I install on a new system.
The thing I miss the most is a nice concurrency story. It has become better but it's still a bit of a mess, often it's nicest to just implement workers as PHP and then implement control somewhere else, e.g. Elixir, or grab one of the application servers that are nowadays a thing in PHP.
If you're accessing an uninitialized property or checking if a property is uninitialized, you're probably already doing something wrong.
The point of class properties with no default value is that you're supposed to set them either in the constructor, immediately after creating an instance, or via some other method that guarantees they'll have a value by the time you need to read them (such as deserialization with validation).
If you want your properties to have a default "unset" value that you can trivially check for, that's what null is for. The author doesn't make it clear whether they are aware that you can declare a nullable string and give it the default value of null, but I hope they are.
Maybe it’s too impossible to do that, but the behavior described seems like it puts you right back in the world of completely dynamic anything-goes (PHP’s legacy, basically).
I thought part of the point of types was to give the caller confidence that simply accessing a typed property is guaranteed to return a certain type (null being a type that may be included).
Good third party libraries generally don't leave uninitialized properties laying around for you to trip on, and I find the ability to set non-nullable properties after the constructor to be quite convenient for things like Symfony entities.
Surprisingly enough, I was more productive in PHP than I was in perl. Perhaps perl is even stranger than PHP.
I’ve been doing so Perl maintenance lately and I miss PHP. Perl is a lot weirder than PHP. If I didn’t know C or had dabbled in Perl before I would be completely confused. There is More Than One Way to do it (the Perl rallying cry) causes a lot of confusion. The one nice thing about Perl is that it doesn’t really change anymore, and you can see where it positively influenced others.