Skip to content

Commit 1c09c0c

Browse files
CrellGirgiasarnaud-lbTimWolla
authored
RFC: Pipe operator (#17118)
Co-authored-by: Gina Peter Banyard <[email protected]> Co-authored-by: Arnaud Le Blanc <[email protected]> Co-authored-by: Tim Düsterhus <[email protected]>
1 parent 2036c71 commit 1c09c0c

32 files changed

+760
-1
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ PHP NEWS
5454
released on bailout). (DanielEScherzer and ilutov)
5555
. Fixed AST printing for immediately invoked Closure. (Dmitrii Derepko)
5656
. Properly handle __debugInfo() returning an array reference. (nielsdos)
57+
. Added the pipe (|>) operator. (crell)
5758

5859
- Curl:
5960
. Added curl_multi_get_handles(). (timwolla)

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ PHP 8.5 UPGRADE NOTES
144144
RFC: https://wiki.php.net/rfc/attributes-on-constants
145145
. The #[\Deprecated] attribute can now be used on constants.
146146
RFC: https://wiki.php.net/rfc/attributes-on-constants
147+
. Added the pipe (|>) operator.
148+
RFC: https://wiki.php.net/rfc/pipe-operator-v3
147149

148150
- Curl:
149151
. Added support for share handles that are persisted across multiple PHP
@@ -476,6 +478,7 @@ PHP 8.5 UPGRADE NOTES
476478

477479
- Tokenizer:
478480
. T_VOID_CAST.
481+
. T_PIPE.
479482

480483
========================================
481484
11. Changes to INI File Handling

Zend/tests/pipe_operator/ast.phpt

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
--TEST--
2+
A pipe operator displays as a pipe operator when outputting syntax, with correct parens.
3+
--FILE--
4+
<?php
5+
6+
print "Concat, which binds higher\n";
7+
8+
try {
9+
assert(false && foo() . bar() |> baz() . quux());
10+
} catch (AssertionError $e) {
11+
echo $e->getMessage(), PHP_EOL;
12+
}
13+
14+
try {
15+
assert(false && (foo() . bar()) |> baz() . quux());
16+
} catch (AssertionError $e) {
17+
echo $e->getMessage(), PHP_EOL;
18+
}
19+
20+
try {
21+
assert(false && foo() . (bar() |> baz()) . quux());
22+
} catch (AssertionError $e) {
23+
echo $e->getMessage(), PHP_EOL;
24+
}
25+
26+
try {
27+
assert(false && foo() . bar() |> (baz() . quux()));
28+
} catch (AssertionError $e) {
29+
echo $e->getMessage(), PHP_EOL;
30+
}
31+
32+
try {
33+
assert(false && (foo() . bar() |> baz()) . quux());
34+
} catch (AssertionError $e) {
35+
echo $e->getMessage(), PHP_EOL;
36+
}
37+
38+
try {
39+
assert(false && foo() . (bar() |> baz() . quux()));
40+
} catch (AssertionError $e) {
41+
echo $e->getMessage(), PHP_EOL;
42+
}
43+
44+
print "<, which binds lower\n";
45+
46+
try {
47+
assert(false && foo() < bar() |> baz());
48+
} catch (AssertionError $e) {
49+
echo $e->getMessage(), PHP_EOL;
50+
}
51+
52+
try {
53+
assert(false && (foo() < bar()) |> baz());
54+
} catch (AssertionError $e) {
55+
echo $e->getMessage(), PHP_EOL;
56+
}
57+
58+
try {
59+
assert(false && foo() < (bar() |> baz()));
60+
} catch (AssertionError $e) {
61+
echo $e->getMessage(), PHP_EOL;
62+
}
63+
64+
try {
65+
assert(false && foo() |> bar() < baz());
66+
} catch (AssertionError $e) {
67+
echo $e->getMessage(), PHP_EOL;
68+
}
69+
70+
try {
71+
assert(false && (foo() |> bar()) < baz());
72+
} catch (AssertionError $e) {
73+
echo $e->getMessage(), PHP_EOL;
74+
}
75+
76+
try {
77+
assert(false && foo() |> (bar() < baz()));
78+
} catch (AssertionError $e) {
79+
echo $e->getMessage(), PHP_EOL;
80+
}
81+
82+
83+
84+
print "misc examples\n";
85+
86+
try {
87+
assert(false && foo() |> (bar() |> baz(...)));
88+
} catch (AssertionError $e) {
89+
echo $e->getMessage(), PHP_EOL;
90+
}
91+
92+
?>
93+
--EXPECT--
94+
Concat, which binds higher
95+
assert(false && foo() . bar() |> baz() . quux())
96+
assert(false && foo() . bar() |> baz() . quux())
97+
assert(false && foo() . (bar() |> baz()) . quux())
98+
assert(false && foo() . bar() |> baz() . quux())
99+
assert(false && (foo() . bar() |> baz()) . quux())
100+
assert(false && foo() . (bar() |> baz() . quux()))
101+
<, which binds lower
102+
assert(false && foo() < bar() |> baz())
103+
assert(false && (foo() < bar()) |> baz())
104+
assert(false && foo() < bar() |> baz())
105+
assert(false && foo() |> bar() < baz())
106+
assert(false && foo() |> bar() < baz())
107+
assert(false && foo() |> (bar() < baz()))
108+
misc examples
109+
assert(false && foo() |> (bar() |> baz(...)))
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Pipe operator rejects by-reference functions.
3+
--FILE--
4+
<?php
5+
6+
function _modify(int &$a): string {
7+
$a += 1;
8+
return "foo";
9+
}
10+
11+
function _append(array &$a): string {
12+
$a['bar'] = 'beep';
13+
}
14+
15+
// Simple variables
16+
try {
17+
$a = 5;
18+
$res1 = $a |> _modify(...);
19+
var_dump($res1);
20+
} catch (\Error $e) {
21+
echo $e->getMessage(), PHP_EOL;
22+
}
23+
24+
// Complex variables.
25+
try {
26+
$a = ['foo' => 'beep'];
27+
$res2 = $a |> _append(...);
28+
var_dump($res2);
29+
} catch (\Error $e) {
30+
echo $e->getMessage(), PHP_EOL;
31+
}
32+
33+
34+
?>
35+
--EXPECTF--
36+
_modify(): Argument #1 ($a) could not be passed by reference
37+
_append(): Argument #1 ($a) could not be passed by reference
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Pipe operator accepts prefer-by-reference functions.
3+
--FILE--
4+
<?php
5+
6+
$a = ['hello', 'world'];
7+
8+
try {
9+
$r = $a |> array_multisort(...);
10+
var_dump($r);
11+
} catch (\Error $e) {
12+
echo $e->getMessage(), PHP_EOL;
13+
}
14+
15+
?>
16+
--EXPECT--
17+
bool(true)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Functions are executed in the expected order
3+
--FILE--
4+
<?php
5+
6+
function foo() { echo __FUNCTION__, PHP_EOL; return 1; }
7+
function bar() { echo __FUNCTION__, PHP_EOL; return false; }
8+
function baz($in) { echo __FUNCTION__, PHP_EOL; return $in; }
9+
function quux($in) { echo __FUNCTION__, PHP_EOL; return $in; }
10+
11+
$result = foo()
12+
|> (bar() ? baz(...) : quux(...))
13+
|> var_dump(...);
14+
15+
?>
16+
--EXPECT--
17+
foo
18+
bar
19+
quux
20+
int(1)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Pipe operator chains
3+
--FILE--
4+
<?php
5+
6+
function _test1(int $a): int {
7+
return $a + 1;
8+
}
9+
10+
function _test2(int $a): int {
11+
return $a * 2;
12+
}
13+
14+
$res1 = 5 |> '_test1' |> '_test2';
15+
16+
var_dump($res1);
17+
?>
18+
--EXPECT--
19+
int(12)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
A pipe interrupted by an exception, to demonstrate correct order of execution.
3+
--FILE--
4+
<?php
5+
6+
function foo() { echo __FUNCTION__, PHP_EOL; return 1; }
7+
function bar() { echo __FUNCTION__, PHP_EOL; return false; }
8+
function baz($in) { echo __FUNCTION__, PHP_EOL; return $in; }
9+
function quux($in) { echo __FUNCTION__, PHP_EOL; throw new \Exception('Oops'); }
10+
11+
try {
12+
$result = foo()
13+
|> (bar() ? baz(...) : quux(...))
14+
|> var_dump(...);
15+
}
16+
catch (Throwable $e) {
17+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
18+
}
19+
20+
try {
21+
$result = foo()
22+
|> (throw new Exception('Break'))
23+
|> (bar() ? baz(...) : quux(...))
24+
|> var_dump(...);
25+
}
26+
catch (Throwable $e) {
27+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
28+
}
29+
30+
?>
31+
--EXPECTF--
32+
foo
33+
bar
34+
quux
35+
Exception: Oops
36+
foo
37+
Exception: Break
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Pipe operator throws normally on missing function
3+
--FILE--
4+
<?php
5+
6+
try {
7+
$res1 = 5 |> '_test';
8+
}
9+
catch (Throwable $e) {
10+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
11+
}
12+
13+
?>
14+
--EXPECT--
15+
Error: Call to undefined function _test()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Generators
3+
--FILE--
4+
<?php
5+
6+
function producer(): \Generator {
7+
yield 1;
8+
yield 2;
9+
yield 3;
10+
}
11+
12+
function map_incr(iterable $it): \Generator {
13+
foreach ($it as $val) {
14+
yield $val +1;
15+
}
16+
}
17+
18+
$result = producer() |> map_incr(...) |> iterator_to_array(...);
19+
20+
var_dump($result);
21+
?>
22+
--EXPECT--
23+
array(3) {
24+
[0]=>
25+
int(2)
26+
[1]=>
27+
int(3)
28+
[2]=>
29+
int(4)
30+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
--TEST--
2+
Pipe operator handles all callable styles
3+
--FILE--
4+
<?php
5+
6+
function times3(int $x): int
7+
{
8+
return $x * 3;
9+
}
10+
11+
function times5(int $x): int
12+
{
13+
return $x * 5;
14+
}
15+
16+
class Test
17+
{
18+
public function times7(int $x): int
19+
{
20+
return $x * 7;
21+
}
22+
23+
public function times11(int $x): int
24+
{
25+
return $x * 11;
26+
}
27+
}
28+
29+
class StaticTest
30+
{
31+
public static function times13(int $x): int
32+
{
33+
return $x * 13;
34+
}
35+
36+
public static function times17(int $x): int
37+
{
38+
return $x * 17;
39+
}
40+
}
41+
42+
function times19(int $x): int
43+
{
44+
return $x * 19;
45+
}
46+
47+
class Times23
48+
{
49+
function __invoke(int $x): int
50+
{
51+
return $x * 23;
52+
}
53+
}
54+
55+
$times29 = function(int $x): int {
56+
return $x * 29;
57+
};
58+
59+
function times2(int $x): int {
60+
return $x * 2;
61+
};
62+
63+
$test = new Test();
64+
65+
$res1 = 1
66+
|> times3(...)
67+
|> 'times5'
68+
|> $test->times7(...)
69+
|> [$test, 'times11']
70+
|> StaticTest::times13(...)
71+
|> [StaticTest::class, 'times17']
72+
|> new Times23()
73+
|> $times29
74+
|> fn($x) => times2($x)
75+
;
76+
77+
var_dump($res1);
78+
?>
79+
--EXPECT--
80+
int(340510170)

0 commit comments

Comments
 (0)