In what context are you doing if("0")? People always have these obscure type comparison complaints, not just for PHP, and all I can think is why would you write that?
I think the biggest problem is that the main way to use it is as a HTTP response handler, whereas other languages are more general purpose.
So when a developer wants to develop themselves outside of a HTTP response handler, they need to switch languages.
After over two decades of working in PHP, I'm now working in Java. PHP is basically Java-lite. I am absolutely loving the compile-time safety of Java, but I dearly miss PHP's maps and arrays. In Java, the amount of verbosity for defining a map/list and operating on it is overwhelming.
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).
yes I say this now very often, that PHP has morphed into runtime Java. Quite nice in some ways.
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.
Have you tried Kotlin? It's a less clunky Java. The syntax is IMHO Ruby-level charming (for an OO-first lang), but with types that are quite a bit stronger than Java. Java interop is quite smooth.
Maps should be a first class type in every language.
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 I remember it correctly, the "Java-lite" part comes rather late. PHP was more close to Perl and/or other old-days scripting language, it allows you to quickly launch a web page. Just
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.
Just had a bug the other day where we were array_filter and then feeding the results into json_encode. If you feed a sparse array into that, you get an object, not an array, which can then cause JavaScript Problems.
I wasn't aware of either of those behaviors going into that debugging session.
To note, it is surprisingly refreshing to completely forgo instanciable classes on a modern codebase.
Phpstan deals well with type definitions, arrays are powerful enough to contain whatever needed, and functions can be stored and passed around easily enough.
PHP has quite a lot of oddities such as how loose comparisons (`==`) are made, numeric-strings, and type coercion. But the two oddities mentioned in the article are not that "odd" with a bit of context.
- 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.
> This example exposes the "uninitialized" state that a property can be in, which is NOT the same as NULL. This distinction frustratingly comes up when you try to do a null check on these properties:
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.
I think anyone would agree with the “you’re doing something wrong” part, but if you declare that a property is typed and non-nullable, it would be nice if a constructor that didn’t populate its non-nullable properties would cause an exception to be thrown or something, as the constructor didn’t construct a valid object by its own specification. The wrongdoing was in the constructor so the constructor should be where that happens, not the innocent caller later who trusts the type definition.
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).
I’d love to see a post like this for JS that actually talks about things people run into. Usually when people make a post like this in js, its about archaic things nobody actually uses.
> This lax behaviour for property definitions makes writing code around them harder. Especially when you take into account that any object can have properties dynamically added to them:
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.
Arrays in PHP are mostly confusing if you are used to them in other languages.
Instead of $array[0] - the first element is accessed via array_first().
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.
I like the arrays, they make it feel like a bit of a low level language and there are some weird tricks one can do with them. As for class property behaviour, I kind of get nervous if my properties or the constructor don't enforce default values I can then fit into downstream logic.
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.
> 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).
I have not seen "PHP" listed as a language used in any Hacker News job posting in a very long time. Everything is Javascript/Typescript now, with the occasional Rust, C/C++, Golang, Python, Ruby. I never see PHP ever.
54 comments
[ 5.2 ms ] story [ 68.4 ms ] threadOf 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.
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).
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 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.
But then people started to actually use it to build big sites, `echo($_GET['user'])` alone is not enough, it has to be:
So people started to add module/components as well as ways to load and use those components, to enable them to write code like: That's when it got it's Java look.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.
Phpstan deals well with type definitions, arrays are powerful enough to contain whatever needed, and functions can be stored and passed around easily enough.
- 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.
[^1]: https://www.php.net/manual/en/class.splfixedarray.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).
Surprisingly enough, I was more productive in PHP than I was in perl. Perhaps perl is even stranger than PHP.
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.
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.
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.
https://psysh.org/
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.
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
and have it be equivalent to and then be able to typehint it like and honestly, why not go all the way and allow type definitions/aliases, something like That'd be great.But apparently they deprecated and then changed(?!) the associativity in the past few years, which if anything just makes things even more confusing.
https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/
> 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).