====== PHP RFC: Trailing Boolean Operators ====== * Version: 0.9 * Date: 2026-02-02 * Author: Len Woodward, len@artisan.build * Status: Draft * Target Version: PHP 8.6 ===== Introduction ===== This RFC proposes allowing trailing boolean operators (''&&'', ''||'', ''and'', ''or'') in parenthesized expressions, mirroring the existing support for trailing commas in arrays, function parameters, and other contexts. isActive() && $user->hasPermission('edit') && $resource->isEditable() && ) { // ... } ?> ===== Proposal ===== Allow a trailing ''&&'', ''||'', ''and'', or ''or'' at the end of a parenthesized boolean expression: isActive() && $user->hasPermission('edit') && $resource->isEditable() && ) { // ... } ?> The trailing operator is syntactically permitted and ignored. The above is semantically equivalent to: This follows the same pattern as trailing commas: the parser consumes and discards the trailing token, leaving the AST unchanged. ==== Rationale ==== === Consistency with Trailing Commas === PHP already supports trailing commas in multiple contexts: * Arrays (since PHP 5.0): ''[1, 2, 3,]'' * Function/method calls (since PHP 7.3): ''foo($a, $b,)'' * Function/method declarations (since PHP 8.0): ''function foo($a, $b,) {}'' * Closure ''use'' statements (since PHP 8.0): ''function() use ($a, $b,) {}'' * ''match'' arms (since PHP 8.0) The rationale for trailing commas applies equally to boolean operators: - **Cleaner diffs**: Adding or removing a condition only changes one line - **Easier reordering**: Conditions can be moved without adjusting punctuation - **Reduced syntax errors**: No need to remember to remove the operator when deleting the last condition === Diff Comparison === **Without trailing operators** (current behavior): if ( $user->isActive() && - $user->hasPermission('edit') + $user->hasPermission('edit') && + $resource->isEditable() ) { **With trailing operators** (proposed): if ( $user->isActive() && $user->hasPermission('edit') && + $resource->isEditable() && ) { The trailing operator style produces a one-line diff for adding a condition, compared to a two-line diff that touches an unrelated line. === Trailing-Operator Style is Established === [[https://www.php-fig.org/psr/psr-12/|PSR-12]] permits boolean operators at either the beginning or end of lines. Both styles are valid and used in practice. This RFC is not about //whether// trailing-operator style should exist — it already does. This RFC addresses the friction inherent in that style: the need to modify the final line when adding or removing conditions. === Other Languages Support This === **JavaScript** and **Ruby** already support trailing boolean operators naturally: * **JavaScript**: Due to Automatic Semicolon Insertion (ASI), a line ending with ''&&'' or ''||'' is grammatically incomplete, so no semicolon is inserted and the expression continues on the next line. [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar|MDN: Lexical Grammar]] * **Ruby**: "Ruby expressions and statements are terminated at the end of a line unless the statement is obviously incomplete—for example if the last token on a line is an operator." [[https://rubystyle.guide/|Ruby Style Guide]] PHP would not be pioneering new territory — it would be providing explicit support for a pattern that other popular languages handle implicitly through their line continuation rules. ==== Scope ==== Trailing boolean operators are permitted in: - **Parenthesized expressions**: ''($a && $b &&)'' - **Control structure conditions**: ''if'', ''elseif'', ''while'', ''do-while'', ''switch'' - **Match expressions**: ''match ($expr) { ... }'' (for consistency) Trailing boolean operators are NOT permitted in: - **Bare expressions**: ''$x = $a &&;'' (syntax error) - **Return statements**: ''return $a &&;'' (syntax error) - **Array elements**: ''[$a &&]'' (syntax error) This restriction is natural because trailing operators only make sense in contexts where the expression is already delimited by parentheses. ==== Examples ==== Basic usage in an if statement: isPaid() && $order->isShipped() && $order->isDelivered() && ) { $order->archive(); } ?> Using the OR operator: isAdmin() || $user->isEditor() || $user->isModerator() || ); ?> In a while loop: valid() && $count < $limit && ) { process($iterator->current()); $iterator->next(); $count++; } ?> With the ''and''/''or'' keywords: Edge case - nested parentheses: ===== Backward Incompatible Changes ===== This is a purely additive change. Code that was previously a syntax error becomes valid. No existing valid code changes meaning. No backward compatibility breaks are expected. ===== Proposed PHP Version(s) ===== PHP 8.6 ===== RFC Impact ===== ==== To the Ecosystem ==== IDEs, language servers, and static analyzers will need to update their parsers to recognize the new syntax. However, since this is purely additive (previously-invalid syntax becoming valid), existing tooling will not break — it will simply report false-positive syntax errors until updated. Auto-formatters may choose to add or remove trailing operators based on configuration, similar to how they handle trailing commas. ==== To Existing Extensions ==== None. The change is at the parser level only. ==== To SAPIs ==== None. ==== To Opcache ==== None. The change is at the parser level; compiled bytecode is identical with or without the trailing operator. ===== Open Issues ===== None at this time. ===== Future Scope ===== This RFC intentionally does not address: * Trailing operators in other contexts (e.g., bitwise ''|'', ''&'') * The ''xor'' operator (less commonly used in multiline expressions) These could be proposed separately if there is demand. ===== Voting Choices ===== Primary vote requiring a 2/3 majority: * Yes * No ===== Patches and Tests ===== Proof of concept implementation: //To be added// ==== Implementation Details ==== The implementation is parser-only. No changes are required to: * The lexer (tokens already exist) * The AST node types * The compiler * The VM/opcodes The trailing operator is consumed by the parser and discarded, identical to how trailing commas are handled via ''possible_comma''. === Grammar Changes === In ''Zend/zend_language_parser.y'': **Add new rule:** paren_expr: expr | expr T_BOOLEAN_AND { $$ = $1; } | expr T_BOOLEAN_OR { $$ = $1; } | expr T_LOGICAL_AND { $$ = $1; } | expr T_LOGICAL_OR { $$ = $1; } ; **Modify existing rules** to use ''paren_expr'' instead of ''expr'' in parenthesized contexts (''if'', ''while'', ''do-while'', ''switch'', ''elseif'', and general parenthesized expressions). === Files to Modify === - ''Zend/zend_language_parser.y'' — Grammar rules (~10 lines) - ''Zend/tests/operators/trailing_boolean_operators_basic.phpt'' — Basic functionality tests - ''Zend/tests/operators/trailing_boolean_operators_error.phpt'' — Error case tests ===== Implementation ===== After the RFC is implemented, this section should contain: - the version(s) it was merged into - a link to the git commit(s) - a link to the PHP manual entry for the feature ===== References ===== * [[https://www.php-fig.org/psr/psr-12/|PSR-12: Extended Coding Style]] * [[https://wiki.php.net/rfc/trailing-comma-function-calls|PHP RFC: Trailing Commas in Function Calls]] * [[https://wiki.php.net/rfc/trailing_comma_in_parameter_list|PHP RFC: Trailing Commas in Parameters]] * [[https://wiki.php.net/rfc/trailing_comma_in_closure_use_list|PHP RFC: Trailing Commas in Closure Use Lists]] * [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar|MDN: JavaScript Lexical Grammar (ASI)]] * [[https://rubystyle.guide/|Ruby Style Guide]] ===== Rejected Features ===== //To be updated based on mailing list discussion.// ===== Changelog ===== * 2026-02-02: Initial RFC draft created