Skip to content

Commit 3fee75d

Browse files
committed
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.
1 parent 5a0e406 commit 3fee75d

File tree

5 files changed

+101
-1
lines changed

5 files changed

+101
-1
lines changed

ext/standard/basic_functions.stub.php

+1
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,7 @@ function fprintf($stream, string $format, mixed ...$values): int {}
958958
/** @param resource $stream */
959959
function vfprintf($stream, string $format, array $values): int {}
960960

961+
function println(string $data = ''): int {}
961962
/* fsock.c */
962963

963964
/**

ext/standard/basic_functions_arginfo.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 7540039937587f05584660bc1a1a8a80aa5ccbd1 */
2+
* Stub hash: 451b88d9950fe9f51752e06e0f2ee7f68a7ecf81 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
55
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
@@ -1486,6 +1486,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_vfprintf, 0, 3, IS_LONG, 0)
14861486
ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0)
14871487
ZEND_END_ARG_INFO()
14881488

1489+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_println, 0, 0, IS_LONG, 0)
1490+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, data, IS_STRING, 0, "\'\'")
1491+
ZEND_END_ARG_INFO()
1492+
14891493
ZEND_BEGIN_ARG_INFO_EX(arginfo_fsockopen, 0, 0, 1)
14901494
ZEND_ARG_TYPE_INFO(0, hostname, IS_STRING, 0)
14911495
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, port, IS_LONG, 0, "-1")
@@ -2618,6 +2622,7 @@ ZEND_FUNCTION(vprintf);
26182622
ZEND_FUNCTION(vsprintf);
26192623
ZEND_FUNCTION(fprintf);
26202624
ZEND_FUNCTION(vfprintf);
2625+
ZEND_FUNCTION(println);
26212626
ZEND_FUNCTION(fsockopen);
26222627
ZEND_FUNCTION(pfsockopen);
26232628
ZEND_FUNCTION(http_build_query);
@@ -3258,6 +3263,7 @@ static const zend_function_entry ext_functions[] = {
32583263
ZEND_FE(vsprintf, arginfo_vsprintf)
32593264
ZEND_FE(fprintf, arginfo_fprintf)
32603265
ZEND_FE(vfprintf, arginfo_vfprintf)
3266+
ZEND_FE(println, arginfo_println)
32613267
ZEND_FE(fsockopen, arginfo_fsockopen)
32623268
ZEND_FE(pfsockopen, arginfo_pfsockopen)
32633269
ZEND_FE(http_build_query, arginfo_http_build_query)

ext/standard/formatted_print.c

+23
Original file line numberDiff line numberDiff line change
@@ -919,3 +919,26 @@ PHP_FUNCTION(vfprintf)
919919
zend_string_efree(result);
920920
}
921921
/* }}} */
922+
923+
/* {{{ Output a string followed by a unix newline */
924+
PHP_FUNCTION(println)
925+
{
926+
char* input = NULL;
927+
size_t input_len = 0;
928+
929+
ZEND_PARSE_PARAMETERS_START(0, 1)
930+
Z_PARAM_OPTIONAL
931+
Z_PARAM_STRING(input, input_len)
932+
ZEND_PARSE_PARAMETERS_END();
933+
934+
if (input_len > 0) {
935+
size_t rlen = PHPWRITE(input, input_len);
936+
if (UNEXPECTED(rlen != input_len)) {
937+
RETURN_LONG(rlen);
938+
}
939+
rlen += PHPWRITE("\n", 1);
940+
RETURN_LONG(rlen);
941+
}
942+
RETURN_LONG(PHPWRITE("\n", 1));
943+
}
944+
/* }}} */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
println() function with strict_types=0
3+
--FILE--
4+
<?php
5+
6+
// Normally, you would never bother checking the number of bytes returned,
7+
// but this is a unit test of the function itself.
8+
$v1 = println('');
9+
$v2 = println('println always adds a newline');
10+
println("the above string was $v2 bytes including the newline, the below appends \\n to \\n");
11+
$v3 = println("\n");
12+
$v4 = println('strict_types=0 allows string coercion the exact same way as any other function');
13+
$v5 = println(1);
14+
$v6 = println(true);
15+
$v7 = println(); // the default argument is the empty string
16+
println('println byte lengths');
17+
println(json_encode([$v1, $v2, $v3, $v4, $v5, $v6, $v7]));
18+
try {
19+
println([]);
20+
} catch (TypeError $e) {
21+
echo "Caught TypeError {$e->getMessage()}\n";
22+
}
23+
try {
24+
println('test', 'too many');
25+
} catch (ArgumentCountError $e) {
26+
echo "Caught ArgumentCountError {$e->getMessage()}\n";
27+
}
28+
29+
?>
30+
--EXPECT--
31+
println always adds a newline
32+
the above string was 30 bytes including the newline, the below appends \n to \n
33+
34+
35+
strict_types=0 allows string coercion the exact same way as any other function
36+
1
37+
1
38+
39+
println byte lengths
40+
[1,30,2,79,2,2,1]
41+
Caught TypeError println(): Argument #1 ($data) must be of type string, array given
42+
Caught ArgumentCountError println() expects at most 1 argument, 2 given
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
println() function with strict_types=1 throws for non-string
3+
--FILE--
4+
<?php
5+
6+
declare(strict_types=1);
7+
8+
$v1 = println('println always adds a newline');
9+
println("wrote $v1 bytes");
10+
try {
11+
println(1);
12+
} catch (TypeError $e) {
13+
println("Caught: " . $e->getMessage());
14+
}
15+
println(); // valid
16+
try {
17+
println(null);
18+
} catch (TypeError $e) {
19+
println("Caught: " . $e->getMessage());
20+
}
21+
22+
?>
23+
--EXPECT--
24+
println always adds a newline
25+
wrote 30 bytes
26+
Caught: println(): Argument #1 ($data) must be of type string, int given
27+
28+
Caught: println(): Argument #1 ($data) must be of type string, null given

0 commit comments

Comments
 (0)