Allows arrays to be type-hinted with interfaces mimicking Array behaviour

php.internals

Girgias

7 years ago
Greetings internals, I would like to have your opinion on being able to type hint arrays and objects which implement array-like interfaces (notably ArrayAccess, Countable, and Iterator) as currently if a function /method only needs one specific feature of an array and is willing to accept array-like objects. There are some rather repetitive boilerplate and the impossibility of using type hints. An example of such a function, (where $arrayLike could be custom session object) : /** ArrayAccess|array $arrayLike */ public function checkCSRF($session) { if (!is_array($session) && !($session instanceof ArrayAccess)) { throw new Exception(); } // Check token if (!isset($session['CSRF']) { throw new Exception(); } // Do some more stuff ... } As it can be seen this function/method doesn't need any of the other properties of an array such as Countability or Traversability to do its job. However, in the current state of PHP, it is impossible to accept array-like objects without writing the initial if statement. PHP does have the pseudo-type iterable which allows passing Traversable objects as well as arrays. Moreover, the count function accepts objects which implement the Countable interface. This is achieved with specials checks within the function [1]. My knowledge in C is extremely limited and basic but I see three possible implementations: First one, which seems rather impractical/impossible, is to make arrays "implement" these interfaces by possibly paving the way to object-like arrays. However, this would probably mean a major rewrite in most parts of the engine and impact performance as arrays won't be a basic data structure anymore. (Or maybe only some changes to these magic interfaces in zend_interfaces.c are needed? [2]) The second one, adding new pseudo-types like iterable. [3] This is probably one of the simplest solutions however I don't know how ArrayAccess would be named. Moreover, it seems the least "scalable" in case PHP adds a new interface where arrays can be used (something like Comparable or Sortable comes to mind) Thirdly, adding a special check during type hinting against these interfaces that allows arrays. I am not sure where this should be implemented; possibly in Zend/zend_inheritance.c [4] or within the lexer. However, as I don't know if other parts of the engine would need to be updated I've made a list of possible impacted areas of the engine which comes with this proposal, namely: - Argument type hints - Return types - Typed properties - Covariance and Contravariance Possible disadvantages: - Performance impact as every object type hint now needs to go through the special check - This could/should be implemented with object-like arrays I can't see any other disadvantages so if someone sees some if they could raise them this would be greatly appreciated. Future scope: Possibly changing signatures of some array_* functions? However, they would probably sill need to have separate code paths like count. [1] Best regards George P. Banyard [1] Count function https://github.com/php/php-src/blob/master/ext/standard/array.c#L772 [2] "Magic" interfaces https://github.com/php/php-src/blob/master/Zend/zend_interfaces.c#L579 [3] Fake engine types https://github.com/php/php-src/blob/master/Zend/zend_types.h#L435 [4] Type hint check https://github.com/php/php-src/blob/master/Zend/zend_inheritance.c#L180

Larry Garfield

7 years ago
On Sunday, February 17, 2019 9:24:05 AM CST Girgias wrote:
> Greetings internals, > > I would like to have your opinion on being able to type hint arrays and > objects which implement > array-like interfaces (notably ArrayAccess, Countable, and Iterator) as > currently if a function /method only needs one specific feature of an array > and is willing to accept array-like objects. > There are some rather repetitive boilerplate and the impossibility of using > type hints. > > An example of such a function, (where $arrayLike could be custom session > object) : > > /** ArrayAccess|array $arrayLike */ > public function checkCSRF($session) { > if (!is_array($session) && !($session instanceof ArrayAccess)) { > throw new Exception(); > } > // Check token > if (!isset($session['CSRF']) { > throw new Exception(); > } > // Do some more stuff ... > } > > As it can be seen this function/method doesn't need any of the other > properties of an array > such as Countability or Traversability to do its job. > However, in the current state of PHP, it is impossible to accept array-like > objects without > writing the initial if statement.
While I would like to see better consistency between arrays and array-ish objects in principle, in this case I think it's the wrong approach. You're using an associative array as a cheap anonymous struct. Don't do that. It's far less self-documenting, far less type safe, far more error prone, and far less performant. Just switching $session from an array-ish value to a class with public properties would make it use half as much memory. cf: https://steemit.com/php/@crell/php-use-associative-arrays-basically-never https://steemit.com/php/@crell/php-never-type-hint-on-arrays Making it easier to use anonymous-array-or-object-acting-like-anonymous-array would be a step backwards in every possible way. --Larry Garfield

Girgias

7 years ago
On Mon, 18 Feb 2019 at 17:26, Larry Garfield <larry@garfieldtech.com> wrote:
> On Sunday, February 17, 2019 9:24:05 AM CST Girgias wrote: > > Greetings internals, > > > > I would like to have your opinion on being able to type hint arrays and > > objects which implement > > array-like interfaces (notably ArrayAccess, Countable, and Iterator) as > > currently if a function /method only needs one specific feature of an > array > > and is willing to accept array-like objects. > > There are some rather repetitive boilerplate and the impossibility of > using > > type hints. > > > > An example of such a function, (where $arrayLike could be custom session > > object) : > > > > /** ArrayAccess|array $arrayLike */ > > public function checkCSRF($session) { > > if (!is_array($session) && !($session instanceof ArrayAccess)) { > > throw new Exception(); > > } > > // Check token > > if (!isset($session['CSRF']) { > > throw new Exception(); > > } > > // Do some more stuff ... > > } > > > > As it can be seen this function/method doesn't need any of the other > > properties of an array > > such as Countability or Traversability to do its job. > > However, in the current state of PHP, it is impossible to accept > array-like > > objects without > > writing the initial if statement. > > > While I would like to see better consistency between arrays and array-ish > objects in principle, in this case I think it's the wrong approach. > You're > using an associative array as a cheap anonymous struct. Don't do that. > It's > far less self-documenting, far less type safe, far more error prone, and > far > less performant. Just switching $session from an array-ish value to a > class > with public properties would make it use half as much memory. cf: > > https://steemit.com/php/@crell/php-use-associative-arrays-basically-never > https://steemit.com/php/@crell/php-never-type-hint-on-arrays > > Making it easier to use > anonymous-array-or-object-acting-like-anonymous-array > would be a step backwards in every possible way. > > --Larry Garfield
Hello Larry, First of all thanks for the feedback and link to your benchmark of Objects vs Arrays. However, I'm not exactly sure what your point is. Maybe I'm not understanding or maybe I didn't express myself clearly enough. But I don't see how what you say contradicts me. I do totally agree with you that Classes should be used most of the time, however my take from your article is that you only encourage the use of ``iterable`` instead of ``array`` which I 100% agree on. However it is impossible to type-hint an argument with Countable or ArrayAccess such that it accepts object but, key point, *ALSO* accepts arrays, even if from what I understand you want to limit its usage to the bare minimum. Also you can disagree with the fact of using an array as a "cheap" anonymous structure but, by default, PHP uses arrays like that for get, post, sessions, files, cookies, server, request, and environment variables. So if I want to use an object, which implements ArrayAccess, to represent this data because it is more structured and memory efficient but can't pass it to a function (/method/constructor) from a library because it typehints against array (and yes I saw that you discourage the use of array type-hinting but this only works for iterables not if someone accesses it like an array or wants to count it) that for example validates my POST values. However I don't expect a library to just accept an object because more likely than not it expect the raw data to be an array even if it doesn't care about the other "features" of an array. And until PHP uses objects for all superglobals I don't see an easy way around it. Now in the case I wasn't clear what I would like is that IF there is a type-hint for ArrayAccess and Countable, arrays are automatically accepted and the if statement can be removed. Now if you are saying object shouldn't implement ArrayAccess and methods should just type hint against that object that's a whole other issue, but I don't think this is what you are trying to get at. So I would love if you could clarify what you meant as you seem to have some insight/opinion which seems valuable and maybe also give your opinion on how you would like to see better consistency with arrays and array-like objects. Best regards George P. Banyard

Larry Garfield

7 years ago
On Monday, February 18, 2019 3:15:59 PM CST Girgias wrote:
> On Mon, 18 Feb 2019 at 17:26, Larry Garfield <larry@garfieldtech.com> wrote: > > On Sunday, February 17, 2019 9:24:05 AM CST Girgias wrote: > > > Greetings internals, > > > > > > I would like to have your opinion on being able to type hint arrays and > > > objects which implement > > > array-like interfaces (notably ArrayAccess, Countable, and Iterator) as > > > currently if a function /method only needs one specific feature of an > > > > array > > > > > and is willing to accept array-like objects. > > > There are some rather repetitive boilerplate and the impossibility of > > > > using > > > > > type hints. > > > > > > An example of such a function, (where $arrayLike could be custom session > > > object) : > > > > > > /** ArrayAccess|array $arrayLike */ > > > public function checkCSRF($session) { > > > > > > if (!is_array($session) && !($session instanceof ArrayAccess)) { > > > > > > throw new Exception(); > > > > > > } > > > // Check token > > > if (!isset($session['CSRF']) { > > > > > > throw new Exception(); > > > > > > } > > > // Do some more stuff ... > > > > > > } > > > > > > As it can be seen this function/method doesn't need any of the other > > > properties of an array > > > such as Countability or Traversability to do its job. > > > However, in the current state of PHP, it is impossible to accept > > > > array-like > > > > > objects without > > > writing the initial if statement. > > > > While I would like to see better consistency between arrays and array-ish > > objects in principle, in this case I think it's the wrong approach. > > You're > > using an associative array as a cheap anonymous struct. Don't do that. > > It's > > far less self-documenting, far less type safe, far more error prone, and > > far > > less performant. Just switching $session from an array-ish value to a > > class > > with public properties would make it use half as much memory. cf: > > > > https://steemit.com/php/@crell/php-use-associative-arrays-basically-never > > https://steemit.com/php/@crell/php-never-type-hint-on-arrays > > > > Making it easier to use > > anonymous-array-or-object-acting-like-anonymous-array > > would be a step backwards in every possible way. > > > > --Larry Garfield > > Hello Larry, > > First of all thanks for the feedback and link to your benchmark of Objects > vs Arrays. > > However, I'm not exactly sure what your point is. Maybe I'm not > understanding or > maybe I didn't express myself clearly enough. > But I don't see how what you say contradicts me. > > I do totally agree with you that Classes should be used most of the time, > however my take from your article is that you only encourage the use of > ``iterable`` > instead of ``array`` which I 100% agree on. > However it is impossible to type-hint an argument with Countable or > ArrayAccess > such that it accepts object but, key point, *ALSO* accepts arrays, even if > from what I > understand you want to limit its usage to the bare minimum. > > Also you can disagree with the fact of using an array as a "cheap" > anonymous structure > but, by default, PHP uses arrays like that for get, post, sessions, files, > cookies, server, > request, and environment variables. So if I want to use an object, which > implements > ArrayAccess, to represent this data because it is more structured and > memory efficient > but can't pass it to a function (/method/constructor) from a library > because it typehints > against array (and yes I saw that you discourage the use of array > type-hinting but this > only works for iterables not if someone accesses it like an array or wants > to count it) > that for example validates my POST values. > However I don't expect a library to just accept an object because more > likely than not > it expect the raw data to be an array even if it doesn't care about the > other "features" > of an array. > And until PHP uses objects for all superglobals I don't see an easy way > around it. > > Now in the case I wasn't clear what I would like is that IF there is a > type-hint for ArrayAccess > and Countable, arrays are automatically accepted and the if statement can > be removed. > > Now if you are saying object shouldn't implement ArrayAccess and methods > should just type hint > against that object that's a whole other issue, but I don't think this is > what you are trying to get at. > > So I would love if you could clarify what you meant as you seem to have > some insight/opinion which > seems valuable and maybe also give your opinion on how you would like to > see better consistency > with arrays and array-like objects. > > > Best regards > > George P. Banyard
Yes, a lot of existing code uses arrays as anonymous structs. Such code is, IMO, "doing it wrong". That includes PHP default behavior in many cases. The super-globals are one of the exceptions, as since by definition you don't know what the keys could be in advance you need some sort of generic accessor. superglobals anyway on the grounds that globals are evil (fact check: true). There is often an object wrapping around the session that has get() and set() object be kind-of-array-ish-for-some-part-of-array-ish-ness is a long-standing problem, no question. iterable was a good addition. I think there was discussion of a countable type hint at one point, but I don't think it made it That leaves ArrayAccessible (aka, you can put [] after it and something useful happens), which is what you describe. I would not be against adding such a instead of an object is Just Plain Wrong(tm). Even a bare public property class is superior in every possible way, before we even get into questions of useful accessor methods. be. (Eg, parsing JSON, GET or POST parameters, etc.) 2) You're being sloppy and not thinking through your API. struct into an object that has appropriate accessors that can then handle error cases, missing values, etc. Not doing so results in things like, say, particular anonymous-struct was missing a certain key, which I didn't know was possible, but it broke the whole application. (Seriously, that was my entire struct, even if you're using __get() or ArrayAccerss to do it. (SimpleXML is an example of that approach, for better or worse.) where you have to care, but those are edge-casey. ArrayAccessible is a code smell, and the solution is to properly structure your data and type hint on a defined class instead. Including an ArrayAccessible pseudo-type would be just a matter of completeness, not something I would advise anyone actually use.

Rowan Collins

7 years ago
On Tue, 19 Feb 2019 at 02:44, Larry Garfield <larry@garfieldtech.com> wrote:
> Yes, a lot of existing code uses arrays as anonymous structs. Such code > is, > IMO, "doing it wrong". That includes PHP default behavior in many cases. > The > super-globals are one of the exceptions, as since by definition you don't > know > what the keys could be in advance you need some sort of generic accessor. > > superglobals anyway on the grounds that globals are evil (fact check: > true). > There is often an object wrapping around the session that has get() and > set() > object be kind-of-array-ish-for-some-part-of-array-ish-ness is a > long-standing > problem, no question. iterable was a good addition. I think there was > discussion of a countable type hint at one point, but I don't think it > made it > That leaves ArrayAccessible (aka, you can put [] after it and something > useful > happens), which is what you describe. I would not be against adding such > a > instead of an object is Just Plain Wrong(tm). Even a bare public property > class is superior in every possible way, before we even get into questions > of > useful accessor methods. > > be. (Eg, parsing JSON, GET or POST parameters, etc.) > 2) You're being sloppy and not thinking through your API. > struct into an object that has appropriate accessors that can then handle > error cases, missing values, etc. Not doing so results in things like, > say, > particular anonymous-struct was missing a certain key, which I didn't know > was > possible, but it broke the whole application. (Seriously, that was my > entire > struct, even if you're using __get() or ArrayAccerss to do it. (SimpleXML > is > an example of that approach, for better or worse.) > where you have to care, but those are edge-casey. > > ArrayAccessible is a code smell, and the solution is to properly structure > your data and type hint on a defined class instead. Including an > ArrayAccessible pseudo-type would be just a matter of completeness, not > something I would advise anyone actually use.
Hi Larry, The above post seems to have been mangled somewhere and lost some of its text. It's mostly readable anyway (and I think I agree with what's there) but if you have a copy of the original anywhere, you might want to re-post it. Regards,
-- Rowan Collins [IMSoP]

Larry Garfield

7 years ago
On Tuesday, February 19, 2019 3:38:16 AM CST Rowan Collins wrote:
> On Tue, 19 Feb 2019 at 02:44, Larry Garfield <larry@garfieldtech.com> wrote: > > Yes, a lot of existing code uses arrays as anonymous structs. Such code > > is, > > IMO, "doing it wrong". That includes PHP default behavior in many cases. > > The > > super-globals are one of the exceptions, as since by definition you don't > > know > > what the keys could be in advance you need some sort of generic accessor. > > > > superglobals anyway on the grounds that globals are evil (fact check: > > true). > > There is often an object wrapping around the session that has get() and > > set() > > object be kind-of-array-ish-for-some-part-of-array-ish-ness is a > > long-standing > > problem, no question. iterable was a good addition. I think there was > > discussion of a countable type hint at one point, but I don't think it > > made it > > That leaves ArrayAccessible (aka, you can put [] after it and something > > useful > > happens), which is what you describe. I would not be against adding such > > a > > instead of an object is Just Plain Wrong(tm). Even a bare public property > > class is superior in every possible way, before we even get into questions > > of > > useful accessor methods. > > > > be. (Eg, parsing JSON, GET or POST parameters, etc.) > > 2) You're being sloppy and not thinking through your API. > > struct into an object that has appropriate accessors that can then handle > > error cases, missing values, etc. Not doing so results in things like, > > say, > > particular anonymous-struct was missing a certain key, which I didn't know > > was > > possible, but it broke the whole application. (Seriously, that was my > > entire > > struct, even if you're using __get() or ArrayAccerss to do it. (SimpleXML > > is > > an example of that approach, for better or worse.) > > where you have to care, but those are edge-casey. > > > > ArrayAccessible is a code smell, and the solution is to properly structure > > your data and type hint on a defined class instead. Including an > > ArrayAccessible pseudo-type would be just a matter of completeness, not > > something I would advise anyone actually use. > > Hi Larry, > > The above post seems to have been mangled somewhere and lost some of its > text. It's mostly readable anyway (and I think I agree with what's there) > but if you have a copy of the original anywhere, you might want to re-post > it. > > Regards,
<expletive deleted> No, I don't. That's the second time KMail has eaten half a message on me. I am now officially in the market for a new local mail client, because that's JUST NOT OK, KMAIL! The gist is that if you're dealing with unknown foreign data (JSON, GET/POST, etc.) you should be wrapping the raw structure into an object that provides a better interface, default value handling, error handling, etc. If you know what the keys are in advance, make it an explicit class, period. So while I would be OK with an ArrayAccessible type hint for completeness, I would view using it as a code smell. Insert debate about union types here, as it's bound to come up at this point. Excuse me while I go find a new Linux mail client that isn't a buggy data- losing <censored>. --Larry Garfield

Girgias

7 years ago
On Tue, 19 Feb 2019 at 17:33, Larry Garfield <larry@garfieldtech.com> wrote:
> On Tuesday, February 19, 2019 3:38:16 AM CST Rowan Collins wrote: > > On Tue, 19 Feb 2019 at 02:44, Larry Garfield <larry@garfieldtech.com> > wrote: > > > Yes, a lot of existing code uses arrays as anonymous structs. Such > code > > > is, > > > IMO, "doing it wrong". That includes PHP default behavior in many > cases. > > > The > > > super-globals are one of the exceptions, as since by definition you > don't > > > know > > > what the keys could be in advance you need some sort of generic > accessor. > > > > > > superglobals anyway on the grounds that globals are evil (fact check: > > > true). > > > There is often an object wrapping around the session that has get() and > > > set() > > > object be kind-of-array-ish-for-some-part-of-array-ish-ness is a > > > long-standing > > > problem, no question. iterable was a good addition. I think there was > > > discussion of a countable type hint at one point, but I don't think it > > > made it > > > That leaves ArrayAccessible (aka, you can put [] after it and something > > > useful > > > happens), which is what you describe. I would not be against adding > such > > > a > > > instead of an object is Just Plain Wrong(tm). Even a bare public > property > > > class is superior in every possible way, before we even get into > questions > > > of > > > useful accessor methods. > > > > > > be. (Eg, parsing JSON, GET or POST parameters, etc.) > > > 2) You're being sloppy and not thinking through your API. > > > struct into an object that has appropriate accessors that can then > handle > > > error cases, missing values, etc. Not doing so results in things like, > > > say, > > > particular anonymous-struct was missing a certain key, which I didn't > know > > > was > > > possible, but it broke the whole application. (Seriously, that was my > > > entire > > > struct, even if you're using __get() or ArrayAccerss to do it. > (SimpleXML > > > is > > > an example of that approach, for better or worse.) > > > where you have to care, but those are edge-casey. > > > > > > ArrayAccessible is a code smell, and the solution is to properly > structure > > > your data and type hint on a defined class instead. Including an > > > ArrayAccessible pseudo-type would be just a matter of completeness, not > > > something I would advise anyone actually use. > > > > Hi Larry, > > > > The above post seems to have been mangled somewhere and lost some of its > > text. It's mostly readable anyway (and I think I agree with what's there) > > but if you have a copy of the original anywhere, you might want to > re-post > > it. > > > > Regards, > > <expletive deleted> No, I don't. That's the second time KMail has eaten > half > a message on me. I am now officially in the market for a new local mail > client, because that's JUST NOT OK, KMAIL! > > The gist is that if you're dealing with unknown foreign data (JSON, > GET/POST, > etc.) you should be wrapping the raw structure into an object that > provides a > better interface, default value handling, error handling, etc. If you > know > what the keys are in advance, make it an explicit class, period. > > So while I would be OK with an ArrayAccessible type hint for completeness, > I > would view using it as a code smell. > > Insert debate about union types here, as it's bound to come up at this > point. > > Excuse me while I go find a new Linux mail client that isn't a buggy data- > losing <censored>. > > --Larry Garfield
In all honesty the idea/proposal was more for completeness than anything else. I do think I can agree with you that ArrayAccess is *probably* a code smell. But my initial email was more to see if this would get any support and secondly to see how to implement this. Because as said using pseudo types are (from my understanding) easy to implement but could possibly limit the scope of new Array like interfaces (not sure if it is practical at all but as an idea Sortable and/or Comparable, even tho comparable probably needs a whole pseudo type on its own to accepts strings, integers floats and what's not) whereas some other possible (from my limited understanding) could be more bullet proof to future change. But I suppose one question is if PHP want's to continue to encourage usage of the ArrayAccess (and other array like interfaces) for objects. I could well see an argument for a ``countable`` pseudo type if decided ArrayAccess as an interface "should" be scrapped as you say leads to code smell. Anyway thanks again for the feedback. Best regards George P. Banyard