> On Sep 2, 2024, at 11:10 PM, Hammed Ajao <hamieg...@gmail.com> wrote: > > That's one of the reasons, but arrays are not strict enough (you can add > anything to any array, no distinction between a list and map), and you can't > really specify the shape of an array.
What do you mean by "shape of an array" in this context? I am trying to understand how an object differs in "shape" and I am coming up with nothing. > And/or do you want the compiler to validate when you use a name that has not > been declared, i.e. when you type "Content_Type" or "Content Type" for <key> > you want an error message? > > The cool thing is that the compiler already does this with named parameters > or the splat operator applied to an array (https://3v4l.org/tholi#v8.3.11). That is quite interesting, I did not know that was possible. Unfortunately though, the validation is done at the point of call not at the point of array instantiation which can result in spooky action at a distance if use in more than trivial ways: https://3v4l.org/ZJXYX#v8.3.11 > Not at all, I just prefer custom and strict data structures with a clear > intention. +1 > I mean it is more work having to define a custom attribute and reflecting > It's target each time you want literal string names. The compiler already > does all I need, I just want to extend that support to literal strings. > > I'm proposing using a class every time you need a strict data structure with > the keys known ahead of time, trust me i have no issue with more work when > it's warranted, your version is just a lot of unnecessary code that doesn't > actually achieve that. Okay, I hear where you are coming from. > I'm proposing two things: > 1. Properties keys as literal strings: > ``` > class A { public array "key"; } > ``` > Since php also allows declaring properties in functions (promoted > properties), we have to decide what to do with: > ``` > class A { public function __construct(public array "key"); } > ``` > Which means we would run into cases where it's valid to pass a literal string > as a named parameter > ``` > $a = new A("key": 1); > ``` > I decided to add that to my proposal since there's no point restricting it to > only constructors, which brings us to: > 2. Literal strings as named parameters > Since I decided to include functions, we now have to decide what to allow and > what not, for example, can you now define params as strings too: > ``` > function func(string "param", array "withDefault" = []) {} > ``` > or is it only allowed in variadic functions and func_get_args() (so syntactic > sugar for what we have now with func(...[])) That is very helpful to understand why you came to what you are proposing. For me though, extending it in that way would potentially infect other much more common use-cases so I would prefer to have a constrained data type that allowed this rather than an open-ended one. And one that did not perpetuate the requirement to use quotes for what are effectively identifiers that do not follow the syntactical rules of identifiers. > Say I have a response class: > ``` > class Response { > public function __construct(private MyHeaders $headers) {} > } > ``` > With MyHeaders being a class, nothing is stopping me from passing in: > ``` > class MyCustomHeaders extend MyHeaders { > public function __construct() { > parent::__construct(); > //unset one of the keys or set more > } > } > ``` > That isn't possible with the enum and I would have to store MyHeaders as an > array in the Response class. Fair point on the enum OTOH as for storing as an array in your Response class, I don't see how that is huge issue. I do see how the array does not have a reflectable identity, but stuff it in a single property Response class and then together they do. But I do get that you want to minimize that boilerplate. > > MyHeaders::create("Content-Type": "text/html; charset=utf-8", > "X-Custom-Header": "value") > > But it's clear, each key does what it intends, no additional processing will > be required to transform/validate the keys. It is only "clear" if you can get past all the quotes obscuring what is a key and what it a value. When I first saw your proposal I did not notice the colons and thought you had written the following. If you read back at my original reply you'll recognize that I misunderstood your example: MyHeaders::create("Content-Type", "text/html; charset=utf-8", "X-Custom-Header", "value") IOW, I find it too easy to misread and would not want to have to read and/or maintain code that did not have a more easily visible distinction between what goes on the left of the colon and what goes on the right, remembering that reading and maintenance comprise far more time in the life of working code than the initial development. > MyHeaders::create(Content-Type: "text/html; charset=utf-8", X-Custom-Header: > "value") > > That works for me, but the literal has clearer intent and doesn't rely on > transformation. Clearer in your view, but not in mine. Although I do concur that is requires a transformation I do not find that to be problematic as the rules would need to be determinative and would be easier to recognize key vs. value. Especially when reading code written by those darn developers who do not write each key:value pair on a new line of their own. ;-) It would be interesting to know who else on the list thinks it is clear enough with the quote vs. those who would prefer a transform to avoid having to use quotes? > Are you sure what you are proposing would _not_ end up having to use > reflection under the hood and be equally less performant? > > Yep, it would be mostly handled in the parser/compiler, and internally, named > params are already represented as a php array so we wouldn't even have to > change much. In your example which that I copied below for reference, how will `MyHeaders::create(...)` and `new self(...$headers)` work without reflection? class MyHeaders { public function __construct( public string "Content-Type" = "application/json", public string "Cache-Control" = "no-cache, no-store, must-revalidate", public string "Pragma" = "no-cache", public string "Expires" = "0", public string "X-Frame-Options" = "SAMEORIGIN", public string "X-XSS-Protection" = "1; mode=block", public string "X-Content-Type-Options" = "nosniff", public string "Referrer-Policy" = "strict-origin-when-cross-origin", public string "Access-Control-Allow-Origin" = "*", public string "X-Custom-Header" = "value", ) {} public static function create(string ...$headers): self { return new self(...$headers); // Throws an error if an unknown named parameter is passed } public function dispatch(): void { foreach ((array) $this as $name => $value) { header("$name: $value"); } } } $headers = new MyHeaders("Content-Type": "application/json", "X-Custom-Header": "value"); // or $headers = MyHeaders::create("Content-Type": "text/html; charset=utf-8", "X-Custom-Header": "value"); $headers->dispatch(); > For example, what if PHP had two new datatypes DataSet and DataPair (or some > other bikeshed names) that allowed for defining a set of name-valued pairs, > that could be optionally be close-end or open-ended, that could be converted > to arrays or stdClass objects, that could be validated by the compiler for > close-ended sets, that could be used to specify named arguments for > functions, that do not need to use quotes for names, and probably other very > flexible aspects? Wouldn't that be better than shoehorning features onto > existing classes to handle your use-case? (DataSet and DataPair could be > classes under the hood, just like Enums are.) > > I would love more data types but that doesn't solve this issue. I specifically crafted the idea of `DataSet` and `DataPair` to solve your issues, at least as I understand them. Consider the following hypothetical example: <?php closed dataset MyHeaders { public function __construct( public datapair Content-Type = "application/json", public datapair Cache-Control = "no-cache, no-store, must-revalidate", public datapair Pragma = "no-cache", public datapair int Expires = 0, public datapair X-Frame-Options = "SAMEORIGIN", public datapair X-XSS-Protection = "1; mode=block", public datapair X-Content-Type-Options = "nosniff", public datapair Referrer-Policy = "strict-origin-when-cross-origin", public datapair Access-Control-Allow-Origin = "*", public datapair X-Custom-Header = "value", ){} public static function create(datapair ...$headers): self { return new self(...$headers); // Throws an error if an unknown named parameter is passed } public function dispatch(): void { foreach ((array) $this as $name => $value) { header("$name: $value->current"); } } } // Giving each header its own line so they are not so damn hard to read... $headers = new MyHeaders( Content-Type: "application/json", X-Custom-Header: "value", ); // or $headers = MyHeaders::create( Content-Type: "text/html; charset=utf-8", X-Custom-Header: "value", ); $headers->dispatch(); As I am proposing, the above defines a `closed` dataset meaning only the properties declared are valid. If declared as open-ended, keys assigned to an instance would not need to be validated in a constructor. `datapair` properties and variables would be value objects with have a `->current` property and an `->original` property where the default value would gets assigned to `->original` and `->current`, but `->current can be overwritten (or `->value` and `->default` if `default` could be used in this context.) Anything declared to be a `datapair` type could be declared to have names that are not valid identifiers and end with whitespace. A declared `datapair` inner type would default to `string` but could be declared otherwise if needed, see how I declared `Expires` as `int`. However, it would be good if they could always just be `string` and never anything else. By their nature, any dataset properties or variables would need to be accessed with braces, e.g. `$this->{Content-Type}` and `${Content-Type}` or if without quotes is not possible in the parser then by `$this->{'Content-Type'}` and `${'Content-Type'}` Given the above — assuming it were accepted and added as a feature to PHP — how would this not address your use-case(s)? Note I have no idea if the above would even be considered, but I am proposing here to understand what alternatives may be to address your desired use-case(s). > Good call, I should probably separate both proposals. +1 -Mike