From 42b10642a87247f8525c5ba45cd2759475856555 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Mon, 25 Jan 2021 18:58:39 -0500 Subject: [PATCH] Proposal: Add `println(string $data = ''): int` What this does -------------- This function behaves similarly to this userland code ``` function println(string $data = ''): int { return printf("%s\n", $data); } ``` Similarly to `printf("%s\n", $data);`. - `println` is NOT a keyword. (e.g. functions named println can continue to be declared outside of the global namespace) - It returns the number of bytes that were successfully written to standard output. In the unlikely event that there was an error writing, this and printf return a smaller number. - This deliberately always prints the unix newline (`\n`) **instead of PHP_EOL**. I would find it very unexpected if println were to behave differently based on the web server was running it, e.g. if you moved a website's backend from/to a linux server to/from a windows server, responses generated by `println` would suddenly be different. Additionally, https://www.php-fig.org/psr/psr-2/ recommends that all php source files contain unix line endings. If those files contain inline html/text snippets mixed with php+println(), it would be inconsistent to have `\r\n` in the lines printed by println() and `\n` anywhere else. This is same choice of line ending as var_dump, debug_zval_dump, and var_export use for dumping output. Otherwise, `println("myArray=" . var_export($myArray, true));` would be a mix of multiple line ending choices. Many new languages have elected to always use only the unix newlines, e.g. https://golang.org/pkg/fmt/#Println and https://doc.rust-lang.org/std/macro.println.html Overall, editors do a much better job of detecting newline choices and displaying different newline choices than they did decades ago. My opinion is that this anything generating files targeting a specific OS's line endings should continue to use PHP_EOL or continue to base the newline choice on the OS of the user requesting the output. This newline choice differs from the implementation PR for a similar proposal made 2 years ago https://externals.io/message/104545 , for which an RFC was never written. Differently from printf's argument list, echo, and print, the argument $data is type checked based on the file's `strict_types` setting. This is consistent with handling of $data in `fwrite($stream, string $data): int` or the way format strings($format) of `printf` are checked. `println((string)$value)` should be used when strict_types=1 but you are uncertain of the type. Reasons to add this ------------------- 1. This is useful for self-contained scripts and a useful helper function to have overall. E.g. phpt tests of php itself print multiple lines for the `--EXPECT--` section, and var_dump can be overused even for known strings because `var_dump(some_function())` is shorter than `echo some_function() . "\n";` 2. Even if codebases add userland helper equivalents that do exactly this, If you are new to a codebase, or contribute to multiple codebases, it is inconvenient to use `xyz_println`, `ABCUtils::println()`, `echo X, "\n"`, etc., and remember if those different functions actually use the line endings you think they do. Additionally, the prefixing is much more verbose. 3. In tutorials or language references that teach a developer how to use php functionality, it is often preferable to use functions that append a newline when multiple snippets would be evaluated together to keep examples simple. `println("Hello $name");` would be useful to have for introducing PHP to a new developer before `echo "Hello $name\n";` (requires explaining escaping first) or `var_dump("Hello $name");` (that debug representation is rarely useful for `string(11) "Hello world"`) E.g. `var_dump` is frequently used instead of `var_export`, `echo`, or `print` in the manual even for printing strings with no control characters such as https://www.php.net/manual/en/function.json-encode.php#example-3972 TODO: Write an rfc document, gather existing counterarguments for/against naming choices and newline choices, gather examples of other languages that put a println equivalent in the standard library and their choices. --- ext/standard/basic_functions.stub.php | 1 + ext/standard/basic_functions_arginfo.h | 8 +++- ext/standard/formatted_print.c | 23 ++++++++++ .../general_functions/formatted_print1.phpt | 42 +++++++++++++++++++ .../general_functions/formatted_print2.phpt | 28 +++++++++++++ 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/general_functions/formatted_print1.phpt create mode 100644 ext/standard/tests/general_functions/formatted_print2.phpt diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 83000d5b099b7..b107cb29dbb07 100755 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -966,6 +966,7 @@ function fprintf($stream, string $format, mixed ...$values): int {} /** @param resource $stream */ function vfprintf($stream, string $format, array $values): int {} +function println(string $data = ''): int {} /* fsock.c */ /** diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index f44a32226736b..cb22f28e8c4a1 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 97edf8c87780c892984099e52ad1c6c745b919f8 */ + * Stub hash: 0909e41211d2c51032e107c7829979dff46a6944 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -1489,6 +1489,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_vfprintf, 0, 3, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_println, 0, 0, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, data, IS_STRING, 0, "\'\'") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_fsockopen, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, hostname, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, port, IS_LONG, 0, "-1") @@ -2621,6 +2625,7 @@ ZEND_FUNCTION(vprintf); ZEND_FUNCTION(vsprintf); ZEND_FUNCTION(fprintf); ZEND_FUNCTION(vfprintf); +ZEND_FUNCTION(println); ZEND_FUNCTION(fsockopen); ZEND_FUNCTION(pfsockopen); ZEND_FUNCTION(http_build_query); @@ -3261,6 +3266,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(vsprintf, arginfo_vsprintf) ZEND_FE(fprintf, arginfo_fprintf) ZEND_FE(vfprintf, arginfo_vfprintf) + ZEND_FE(println, arginfo_println) ZEND_FE(fsockopen, arginfo_fsockopen) ZEND_FE(pfsockopen, arginfo_pfsockopen) ZEND_FE(http_build_query, arginfo_http_build_query) diff --git a/ext/standard/formatted_print.c b/ext/standard/formatted_print.c index ab205feb6fa32..5dec684b9da23 100644 --- a/ext/standard/formatted_print.c +++ b/ext/standard/formatted_print.c @@ -919,3 +919,26 @@ PHP_FUNCTION(vfprintf) zend_string_efree(result); } /* }}} */ + +/* {{{ Output a string followed by a unix newline */ +PHP_FUNCTION(println) +{ + char* input = NULL; + size_t input_len = 0; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(input, input_len) + ZEND_PARSE_PARAMETERS_END(); + + if (input_len > 0) { + size_t rlen = PHPWRITE(input, input_len); + if (UNEXPECTED(rlen != input_len)) { + RETURN_LONG(rlen); + } + rlen += PHPWRITE("\n", 1); + RETURN_LONG(rlen); + } + RETURN_LONG(PHPWRITE("\n", 1)); +} +/* }}} */ diff --git a/ext/standard/tests/general_functions/formatted_print1.phpt b/ext/standard/tests/general_functions/formatted_print1.phpt new file mode 100644 index 0000000000000..d16095ae807c5 --- /dev/null +++ b/ext/standard/tests/general_functions/formatted_print1.phpt @@ -0,0 +1,42 @@ +--TEST-- +println() function with strict_types=0 +--FILE-- +getMessage()}\n"; +} +try { + println('test', 'too many'); +} catch (ArgumentCountError $e) { + echo "Caught ArgumentCountError {$e->getMessage()}\n"; +} + +?> +--EXPECT-- +println always adds a newline +the above string was 30 bytes including the newline, the below appends \n to \n + + +strict_types=0 allows string coercion the exact same way as any other function +1 +1 + +println byte lengths +[1,30,2,79,2,2,1] +Caught TypeError println(): Argument #1 ($data) must be of type string, array given +Caught ArgumentCountError println() expects at most 1 argument, 2 given \ No newline at end of file diff --git a/ext/standard/tests/general_functions/formatted_print2.phpt b/ext/standard/tests/general_functions/formatted_print2.phpt new file mode 100644 index 0000000000000..f7e2ee069c578 --- /dev/null +++ b/ext/standard/tests/general_functions/formatted_print2.phpt @@ -0,0 +1,28 @@ +--TEST-- +println() function with strict_types=1 throws for non-string +--FILE-- +getMessage()); +} +println(); // valid +try { + println(null); +} catch (TypeError $e) { + println("Caught: " . $e->getMessage()); +} + +?> +--EXPECT-- +println always adds a newline +wrote 30 bytes +Caught: println(): Argument #1 ($data) must be of type string, int given + +Caught: println(): Argument #1 ($data) must be of type string, null given