Re: [PHP-DEV] PHP True Async RFC - Stage 2
> > Again, that's the *how*, not the *why*. We only need to "get rid of" the parentheses if there's some reason to type them in the first place. > I’ve already understood that. You mean it’s not the reason, but one of the possible solutions. But it’s one of the solutions that, from my point of view, causes less surprise. Because the alternatives like `spawn fn` and `spawn {}` seem more radical to me compared to the current design. In that case, perhaps `spawn_fn` or `spawn closure` would be better options. > > spawn ( fn() => do_something( fetch_something($input) ) )(); Now this option, I’d say, looks realistic. > spawn => do_something( fetch_something($input) ) ); Looks good. But then, of course, it turns out that spawn plays two roles — including defining a closure in the language. That specific point is what makes me doubt it. It's like the language has TWO keywords for creating a closure. Essentially, no matter how you approach it, some compromise will have to be made. * Either accept potential confusion between a variable and a `function`, * Or agree that a closure will have two forms, and everyone will have to learn them. If we go with the second option, then we can use both forms: ```php spawn use {}; spawn => code; ``` > > Again, though, this could easily be added later when a need becomes > visible, as long as we don't do something weird now that closes the door > on it. > I tend to agree. If there's doubt, it's better to postpone it. > spawn => $input |> fetch_something(...) |> do_something(...); It can be made even cleaner by adding a parallelism operator. That way, there’s no need to write `spawn` at all. > > I suggest we leave this sub-thread here; there's plenty of other things to discuss. :) > Ok!
Re: [PHP-DEV] Potential RFC: mb_rawurlencode() ?
Hi Am 2025-03-18 18:48, schrieb Paul M. Jones: $iriPath = '/heads/' . rawurlencode($val) . '/tails/'); assert($iriPath === '/heads/fü bar/tails/'; // false From my reading of RFC 3987 that result is incorrect. The space is neither listed as `iunreserved`, not as `sub-delims`, thus isn't a valid `ipchar`. Thus the space needs to be encoded as %20 for IRIs as well. The same mistake applies to the reference userland implementation below. Best regards Tim Düsterhus
Re: [PHP-DEV] [RFC] [Discussion] Never parameters
Hi Am 2025-03-20 21:04, schrieb Juris Evertovskis: I'm not opposed to having a bottom type, but the `try`/`tryFrom` issue feels caused by another shortcoming. In my view`from` and `tryFrom` are factory methods that construct an instance by being called on the class itself. As such they should not be subject to LSP at all, just like __construct is not. Indeed. I said the same in the PR on GitHub: https://github.com/php/php-src/pull/18016#issuecomment-2715887293. Static methods within an interface are not useful. The `never` type would however still be useful for non-static methods in interfaces and to round off the type hierarchy. Best regards Tim Düsterhus
Re: [PHP-DEV] Potential RFC: mb_rawurlencode() ?
Hi Am 2025-03-20 17:46, schrieb Paul M. Jones: ```php function mb_rawurlencode(string $string, string $encode): string {} ``` Ah yes, you're right -- probably `?string $encode = null` to match with mb_substr(). I am not sure if that signature makes sense and if the proposed functionality fits into mbstring for that reason. IRIs are defined as UTF-8, any other encoding results in invalid output / results that are not interoperable. As one example paragraph from RFC 3987: Conversions from URIs to IRIs MUST NOT use any character encoding other than UTF-8 in steps 3 and 4, even if it might be possible to guess from the context that another character encoding than UTF-8 was used in the URI. The correct solution to me is to build a proper thought-through API as part of the proposed new Uri namespace and not adding new standalone functions without a clear vision. Best regards Tim Düsterhus
Re: [PHP-DEV] [RFC] [Discussion] Never parameters
On 21/03/2025 11:50, Tim Düsterhus wrote: Am 2025-03-20 21:27, schrieb Matt Fonda: If an interface adds a method but makes no promises about what parameters it accepts, then why is it part of the interface in the first place--why add a method that can't be used? It would more cleanly allow for userland / PHPDoc-based generics, while still providing some engine-enforced type safety. Consider this example (not sure if I got the syntax completely right): /** @template T */ interface Comparable { /** @param T $other */ public function compareTo(never $other): int; } /** @implements Comparable */ final class Number implements Comparable { public function compareTo(Number $other): int { return $this <=> $other; } } I think I agree with Matt on this: the interface isn't making any usable promises about that method. In this example, Comparable is a kind of "abstract interface" - in order to actually make use of it, you need to specialise it. Declaring that a class implements a template interface is like inheriting an abstract method: either you fill in the type parameter ("class Foo implements Comparable { ... }"), or the class is also a template ("class Foo implements Comparable { ... }") I don't think the language should pretend to support something that it doesn't - if the contract is actually enforced by a third-party tool reading docblocks, put the contract in a docblock: /** * @template T * @method compareTo(T $other): int; */ interface Comparable { } /** @implements Comparable */ final class Number implements Comparable { public function compareTo(Number $other): int { return $this <=> $other; } } -- Rowan Tommins [IMSoP]
Re: [PHP-DEV] PHP True Async RFC - Stage 2
> > You already explicitly await all fibers spawned in the generateReport function, you get all the data you need, any extra spawned fibers should not interest you for the purpose of the logic of generateReport. > In this specific code, it only awaits the tasks it has launched itself. So, if another function mistakenly starts a coroutine in the current `Scope`, that coroutine will be cancelled when the scope is destroyed. On the other hand, code that does `await $scope` assumes that the programmer *intends* to wait for everything and understands the implications. This usually means that the functions being called are part of the same module and are designed with this in mind. As for library functions — **library functions MUST understand what they are doing**. If a library function creates resources indiscriminately and doesn’t clean them up, the language cannot be held responsible. If library functions don’t manage resource ownership properly, the language cannot take responsibility for that either. > > This is because again, the main use case listed of making sure all fibers are done after a request is a footgun is a non-business-logic requirement, > an exercise in functional purity that also reduces caching and concurrency opportunities, as mentioned before. > In this RFC, there is no such primary use case. There is the `await $scope` construct, but it can no longer be used as the default. There used to be a `await currentScope()` construct, which was a footgun — but it will no longer exist. I also removed `globalScope`, because in 99% of cases it’s an anti-pattern and can be easily replaced with code that creates a coroutine in a separate `Scope`. Through writing examples, this became clear. > > A (somewhat bikesheeding, but this has been the vast majority of the posts on this thread anyway) note is that await could also be made to accept an iterable of futures, avoiding the use of Async\all combinators. > I considered this option — it looks nice with an array — but for some reason, it's not implemented in any language. And here's why. When you allow await to work with an array, it leads to significant complications. Because once you support arrays, you naturally want to add more variants, like: - await first - await any - await ignore and so on. And with additions like await until or await limit, it becomes a very large and complex statement. After exploring different options, I also came to the conclusion that using a function which returns a `Future` from parameters is more flexible and better than having a multitude of options. The only unresolved question is until, because it could be convenient. But it doesn’t exist in any other language.
Re: [PHP-DEV] PHP True Async RFC - Stage 2
> > As I write more code examples, I'm starting to get annoyed by the verbosity > of the `spawn in $scope` construct—especially in situations where all spawns > need to happen within the same context. > > At the same time, in 80% of cases, it turns out that explicitly defining > `$scope` is the only correct approach to avoid shooting yourself in the foot. > > So, it turns out that the `spawn in $scope` construct is used far more > frequently than a plain `spawn`. > > with async > > ```php > function generateReport(): void > { > try { > > $scope = Scope::inherit(); > > async $scope { > [$employees, $salaries, $workHours] = await Async\all([ > spawn fetchEmployees(), > spawn fetchSalaries(), > spawn fetchWorkHours() > ]); > > foreach ($employees as $id => $employee) { > $salary = $salaries[$id] ?? 'N/A'; > $hours = $workHours[$id] ?? 'N/A'; > echo "{$employee['name']}: salary = $salary, hours = > $hours\n"; > } > } > > } catch (Exception $e) { > echo "Failed to generate report: ", $e->getMessage(), "\n"; > } > } > > ``` > > async syntax > > ```php > async { > > } > ``` > I can see how you think that syntactic sugar is understandably needed for spawn in scope, but again, you’re still writing code that makes no sense: why do you care about fetchEmployees (a possible library function) not spawning any fiber? You already explicitly await all fibers spawned in the generateReport function, you get all the data you need, any extra spawned fibers should not interest you for the purpose of the logic of generateReport. This is because again, the main use case listed of making sure all fibers are done after a request is a footgun is a non-business-logic requirement, an exercise in functional purity that also reduces caching and concurrency opportunities, as mentioned before. A (somewhat bikesheeding, but this has been the vast majority of the posts on this thread anyway) note is that await could also be made to accept an iterable of futures, avoiding the use of Async\all combinators. Regards, Daniil Gentili.
Re: [PHP-DEV] [RFC] [Discussion] Never parameters
On Thu, Mar 20, 2025, at 6:02 PM, Daniel Scherzer wrote: > On Thu, Mar 20, 2025 at 4:00 PM Larry Garfield wrote: >> I have a use case for this in Serde, so would be in favor. >> >> We should not block this kind of improvement on the hope of generics. Worst >> case, we have this plus generics so you have options, how terrible. >> > > Would you mind sharing details of your Serde use case? It seems that > the BackedEnum example might not have been the best (since it is for > static methods) and so perhaps a userland case where this would be used > would help. > > --Daniel Simplified example to show the thing we care about: I have an interface Formatter, like: interface Formatter { public function serializeInitialize(ClassSettings $classDef, Field $rootField): mixed; public function serializeInt(mixed $runningValue, Field $field, ?int $next): mixed; public function serializeFloat(mixed $runningValue, Field $field, ?float $next): mixed; // And other methods for other types } The $runningValue is of a type known concretely to a given implementation, but not at the interface level. It's returned from serializeIntialize(), and then passed along to every method, recursively, as it writes out an object. So for instance, the JSON formatter looks like this: class JsonSerializer implements Formatter { public function serializeInitialize(ClassSettings $classDef, Field $rootField): array { return ['root' => []]; } /** * @param array $runningValue * @return array */ public function serializeInt(mixed $runningValue, Field $field, ?int $next): array { $runningValue[$field->serializedName] = $next; return $runningValue; } } Because JsonFormatter works by building up an array and passing it to json_encode(), eventually. So $runningValue is guaranteed to always be an array. I can narrow the return value to an array, but not the parameter. The JsonStreamFormatter, however, has a stream object that it passes around (which wraps a file handle internally): class JsonStreamFormatter implements Formatter { public function serializeInitialize(ClassSettings $classDef, Field $rootField): FormatterStream { return FormatterStream::new(fopen('php://temp/', 'wb')); } /** * @param FormatterStream $runningValue */ public function serializeInt(mixed $runningValue, Field $field, ?int $next): FormatterStream { $runningValue->write((string)$next); return $runningValue; } } Again, I can narrow the return value but not the param. To be clear, generics would absolutely be better in this case. I'm just not holding my breath. Associated Types would probably work in this case, too, since it's always relevant when creating a new concrete object, not when parameterizing a common object. If we got that, I'd probably use that instead. Changing the interface to use `never` instead of `mixed` would have the weakest guarantees of the three, since it doesn't force me to use the *same* widened type on serializeInt(), serializeFloat(), serializeString(), etc., even though it would always be the same. But it would allow me to communicate more type information than I can now. How compelling this use case is, I leave as an exercise for the reader. --Larry Garfield
Re: [PHP-DEV] [VOTE] Optional Interfaces
On 2025-03-20 18:09, Gina P. Banyard wrote: And another user [2] was basically suggesting my previous solution of adding support for type classes/runtime implementation of interfaces. Hey, There are two ideas -- the `$object implements Iface` that you suggested and the `SomeClass implements Iface` that was suggested on reddit. I see the instance-specific one as something that a consumer can do in their project if they know that the `$object` actually fits where needed. That would be a cool feature per se. But it seems to me it would take a lot of bridging code to provide such "dynamically implementing" objects if you're the package author. I'll address the `SomeClass implements Iface` expression vs `class SomeClass implements ?Iface` hereinafter. The `?OptionalInterface` would always be within the class definition and in a real-life project with PSR-4 you could check the interface existence by inspecting the file tree or click-throughing the name in an IDE. It's not obvious to me how the runtime implementation expressions could be in such a discoverable spot. The expression could be anywhere in the project. And it will likely be inside conditionals. It would surely be hard to discover manually. I'm not a developer of stan tooling, but I expect it would complicate automated static analysis as well. BR, Juris
[PHP-DEV] VCS Account Request: daniels
Contributing to php-src with features and bug fixes, https://github.com/DanielEScherzer
Re: [PHP-DEV] VCS Account Request: daniels
Hi internals. On Fri, Mar 21, 2025 at 3:11 PM Daniel Scherzer wrote: > Contributing to php-src with features and bug fixes, > https://github.com/DanielEScherzer Since there wasn't much space in the form field to fill out the details, I've been contributing to various parts of the code base, mostly around attributes, the generated stubs, and the reflection extension, as well as trying to clean up some of the test organization. I am the author of two RFCs ([1], [2]), the first passed and the second is still under discussion. If granted access I would also volunteer to take on maintaining the reflection extension, which per `EXTENSIONS` in php-src does not currently have a primary maintainer. I was encouraged by cmb69 to apply for an account 6 months ago[3], and have also privately gotten encouragement from Ilija to apply (the form said that this should be noted in the request but there didn't seem to be space). Let me know if there are any questions I can answer, --Daniel Scherzer [1] https://wiki.php.net/rfc/attributes-on-constants [2] https://wiki.php.net/rfc/never-parameters-v2 [3] https://github.com/php/php-src/pull/15977#issuecomment-2366983138
Re: [PHP-DEV] Re: Constructor property promotion for final properties
Hi Am 2025-03-20 21:12, schrieb Daniel Scherzer: I recently found out that constructor property promotion cannot be used for final properties. I propose that it become allowed. Thoughts? Would this need an RFC, or is this minor enough to be acceptable with just a mailing list discussion? Given the lack of engagement, I want to make it clear: unless somebody objects, maintainers have agreed to merge a PR implementing the feature in 2 weeks. If you object, please speak up. Can you clarify if the following would result in constructor property promotion or not: class Foo { public function __construct( final string $bar, ) { } } Best regards Tim Düsterhus
Re: [PHP-DEV] [RFC] [Discussion] Never parameters
On Thursday, 20 March 2025 at 16:57, Larry Garfield wrote: > On Thu, Mar 20, 2025, at 11:24 AM, Gina P. Banyard wrote: > > > As the person that had the initial discussion in R11 with Jordan [1] > > never as a parameter type for an interface actually is not the solution > > for "poor man generics". > > Matthew Fonda [2] already replied to the thread pointing out the remark > > Nikita made in the discussion of the previous RFC. > > But importantly, going from mixed parameter type to a generic parameter > > type is allowed and not a BC change, > > however, going from a never parameter type to a generic parameter type > > is a BC break. > > > To clarify, you're saying this: > > [...] > > Am I following that? Because just from writing that I am not sure I agree, > which means I may be misunderstanding. :-) I am saying: interface I { pubic function foo(never $a); } can ***not*** be "upgraded" to interface I { pubic function foo(A $a); } whereas it is possible to go from interface I { pubic function foo(mixed $a); } to interface I { pubic function foo(A $a); } The implementing classes are completely irrelevant in this context. Best regards, Gina P. Banyard
Re: [PHP-DEV] Re: Constructor property promotion for final properties
On Fri, Mar 21, 2025 at 5:20 PM Daniel Scherzer wrote: > On Fri, Mar 21, 2025 at 4:07 AM Tim Düsterhus wrote: > >> Can you clarify if the following would result in constructor property >> promotion or not: >> >> class Foo { >> public function __construct( >> final string $bar, >> ) { } >> } >> >> Best regards >> Tim Düsterhus >> > > Yes, that would result in constructor property promotion. I'll need to > retarget the original PR for master, but at > https://github.com/php/php-src/pull/17861 you can see in > `Zend/tests/property_hooks/final_prop_promoted_2.phpt` a very similar test > case. > > I see. Good catch Tim. Daniel, you don't have this exact test case there. Initially, in 8.0, only a visibility keyword would trigger the property promotion logic. Later, in 8.1, also the readonly keyword would trigger this and make the property public, without the need to mention the visibility: https://3v4l.org/Co0gl Now we want the same for the final keyword, to trigger the property promotion without having a visibility keyword, because like readonly, it would not be applicable to a parameter, but just to a property? -- Alex
[PHP-DEV] Re: [VOTE] Add get_error_handler(), get_exception_handler() functions
Hi, The voting has ended. The RFC was accepted unanimously with 28 (Yes) to 0 (No) votes. Thank you for your participation. Kind regards, Arnaud On Wed, Mar 5, 2025 at 10:50 AM Arnaud Le Blanc wrote: > > Hi, > > I just started the vote on the "Add get_error_handler(), > get_exception_handler() functions" RFC: > https://wiki.php.net/rfc/get-error-exception-handler > > The vote will end in two weeks, on March 20th, 2025. > > Kind Regards, > Arnaud
Re: [PHP-DEV] Re: Constructor property promotion for final properties
On Fri, Mar 21, 2025 at 4:07 AM Tim Düsterhus wrote: > Can you clarify if the following would result in constructor property > promotion or not: > > class Foo { > public function __construct( > final string $bar, > ) { } > } > > Best regards > Tim Düsterhus > Yes, that would result in constructor property promotion. I'll need to retarget the original PR for master, but at https://github.com/php/php-src/pull/17861 you can see in `Zend/tests/property_hooks/final_prop_promoted_2.phpt` a very similar test case. --Daniel
[PHP-DEV] Re: PHP True Async RFC - Stage 2
Good day, everyone. As I write more code examples, I'm starting to get annoyed by the verbosity of the `spawn in $scope` construct—especially in situations where all spawns need to happen within the same context. At the same time, in 80% of cases, it turns out that explicitly defining `$scope` is the only correct approach to avoid shooting yourself in the foot. So, it turns out that the `spawn in $scope` construct is used far more frequently than a plain `spawn`. I remembered an example that Larry came up with and decided to use it as syntactic sugar. There’s some doubt because the actual gain in characters is minimal. This block doesn't change the logic in any way. The convenience lies in the fact that within the block, it’s clear which `$scope` is currently active. However, this is more about visual organization than logical structure. Here's what I ended up with: ### Async blocks Consider the following code: ```php function generateReport(): void { $scope = Scope::inherit(); try { [$employees, $salaries, $workHours] = await Async\all([ spawn in $scope fetchEmployees(), spawn in $scope fetchSalaries(), spawn in $scope fetchWorkHours() ]); foreach ($employees as $id => $employee) { $salary = $salaries[$id] ?? 'N/A'; $hours = $workHours[$id] ?? 'N/A'; echo "{$employee['name']}: salary = $salary, hours = $hours\n"; } } catch (Exception $e) { echo "Failed to generate report: ", $e->getMessage(), "\n"; } } ``` with async ```php function generateReport(): void { try { $scope = Scope::inherit(); async $scope { [$employees, $salaries, $workHours] = await Async\all([ spawn fetchEmployees(), spawn fetchSalaries(), spawn fetchWorkHours() ]); foreach ($employees as $id => $employee) { $salary = $salaries[$id] ?? 'N/A'; $hours = $workHours[$id] ?? 'N/A'; echo "{$employee['name']}: salary = $salary, hours = $hours\n"; } } } catch (Exception $e) { echo "Failed to generate report: ", $e->getMessage(), "\n"; } } ``` async syntax ```php async { } ```
Re: [PHP-DEV] Consensus on argument validation for built-in functions
Hi Am 2025-03-20 17:00, schrieb Gina P. Banyard: And again, ValueErrors are only ever added when it *easy* to check if the condition is satisfied, and it very clearly points to a programming error. I still find it baffling that telling someone that the code they wrote, even if it is decades old, is incorrect is somehow bad. Because the alternative is letting users have bugs in their software that they can ignore. I agree with that (and Kamil, who said the same thing). Passing an undocumented value that does *something* is a clear programmer error that would also break when adding new values, which is generally considered a backwards compatible change. Pointing out this error as an actual error before it causes a silent behavioral change is a good thing. The `round()` function would be a good example. PHP 8.4 both added a validation of the `$mode` parameter if an integer value is given and also added new rounding modes (just as an enum in the gold release, though). Before PHP 8.4 it was possible to use `round($value, 5)` by accident and it would be interpreted as `PHP_ROUND_HALF_UP`. Now if we would've added a new `const PHP_ROUND_WHATEVER = 5;` constant this code would silently have changed its behavior. As a user I certainly would be interested in finding out about this mistake. A clear ValueError is easy to fix in a backwards compatible way, but incorrectly rounded values can remain undetected for so long that it becomes impossible to fix them, since the stored data might already be poisoned, including all backups. Best regards Tim Düsterhus
Re: [PHP-DEV] Re: Constructor property promotion for final properties
On Fri, Mar 21, 2025 at 9:45 AM Alexandru Pătrănescu wrote: > > On Fri, Mar 21, 2025 at 5:20 PM Daniel Scherzer < > daniel.e.scher...@gmail.com> wrote: > >> On Fri, Mar 21, 2025 at 4:07 AM Tim Düsterhus wrote: >> >>> Can you clarify if the following would result in constructor property >>> promotion or not: >>> >>> class Foo { >>> public function __construct( >>> final string $bar, >>> ) { } >>> } >>> >>> Best regards >>> Tim Düsterhus >>> >> >> Yes, that would result in constructor property promotion. I'll need to >> retarget the original PR for master, but at >> https://github.com/php/php-src/pull/17861 you can see in >> `Zend/tests/property_hooks/final_prop_promoted_2.phpt` a very similar test >> case. >> >> > I see. Good catch Tim. > Daniel, you don't have this exact test case there. > > Initially, in 8.0, only a visibility keyword would trigger the property > promotion logic. > Later, in 8.1, also the readonly keyword would trigger this and make the > property public, without the need to mention the visibility: > https://3v4l.org/Co0gl > > Now we want the same for the final keyword, to trigger the property > promotion without having a visibility keyword, > because like readonly, it would not be applicable to a parameter, but just > to a property? > > -- > Alex > Sorry, I completely missed that part. Yes, that should trigger promotion, and if it doesn't then that is a bug in my implementation. When I retarget the PR to master I'll also add a test case for promotion from just using `final`. --Daniel