Repository: php/php-src (2025-07-22T16:54:33Z)

Issue:
Deprecate semicolons after case in switch statement
### Description

Hello everyone :wave: 

Did you know that...

> It's possible to use a semicolon instead of a colon after a case like:

```php
<?php
switch($beer)
{
    case 'tuborg';
    case 'carlsberg';
    case 'heineken';
        echo 'Good choice';
    break;
    default;
        echo 'Please make a new selection...';
    break;
}
?>
```

This has been working since at least PHP 4: https://php-legacy-docs.zend.com/manual/php4/en/control-structures.switch

I had never seen syntax like this until I accidentally wrote it myself. The resulting code confused everyone.

IMO, this syntax doesn't have benefits and can lead to mistakes. Therefore, I propose to deprecate it and remove it from future versions.


Discussion:
This syntax can be used when using plain PHP templates, e.g. https://3v4l.org/7mKS8. While I would not recommend to use switch statements in such templates, some code might still rely on that, so I think dropping support for semicolons after case requires an RFC.


There has not been any recent activity in this feature request. It will automatically be closed in 14 days if no further action is taken. Please see https://github.com/probot/stale#is-closing-stale-issues-really-a-good-idea to understand why we auto-close stale feature requests.
Don't close it.
Is anyone going to pursue the RFC process?
I'm interested in making an RFC, if someone can assist with the implementation.
The implementation *might* be as simple as removing this line:

https://github.com/php/php-src/blob/96d1cd00b7ee686143e0f66319a4ea5e324b723b/Zend/zend_language_parser.y#L716

Anyhow, I suggest that you write to mailing list to gauge some opinions.
There has not been any recent activity in this feature request. It will automatically be closed in 14 days if no further action is taken. Please see https://github.com/probot/stale#is-closing-stale-issues-really-a-good-idea to understand why we auto-close stale feature requests.
Don't close
Just cross-linking that this was added to the general 8.5 deprecations RFC (https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_semicolon_after_case_in_switch_statement) and the discussion thread was at https://externals.io/message/126000



Patch:
diff --git a/NEWS b/NEWS
index fa8db03972bdb..03711eb0a4691 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,8 @@ PHP                                                                        NEWS
     been deprecated. (Girgias)
   . The $exclude_disabled parameter of the get_defined_functions() function has
     been deprecated, as it no longer has any effect since PHP 8.0. (Girgias)
+  . Terminating case statements with a semicolon instead of a colon has
+    been deprecated. (theodorejb)
 
 - DOM:
   . Fixed bug GH-18877 (\Dom\HTMLDocument querySelectorAll selecting only the
diff --git a/UPGRADING b/UPGRADING
index fc36f2d86e44c..4328caf7a7abc 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -322,6 +322,9 @@ PHP 8.5 UPGRADE NOTES
   . The $exclude_disabled parameter of the get_defined_functions() function has
     been deprecated, as it no longer has any effect since PHP 8.0.
     RFC: https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_exclude_disabled_parameter_of_get_defined_functions
+  . Terminating case statements with a semicolon instead of a colon has
+    been deprecated.
+    RFC: https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_semicolon_after_case_in_switch_statement
 
 - FileInfo:
   . The finfo_close() function has been deprecated.
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index eb2286b932959..5527f55a25c29 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -6313,6 +6313,11 @@ static void zend_compile_switch(zend_ast *ast) /* {{{ */
 			continue;
 		}
 
+		if (case_ast->attr == ZEND_ALT_CASE_SYNTAX) {
+			CG(zend_lineno) = case_ast->lineno;
+			zend_error(E_DEPRECATED, "Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead");
+		}
+
 		zend_compile_expr(&cond_node, cond_ast);
 
 		if (expr_node.op_type == IS_CONST
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index a083babcd8109..0decb7e848f95 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -1127,6 +1127,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
 	((ZEND_TYPE_FULL_MASK((arg_info)->type) & _ZEND_IS_TENTATIVE_BIT) != 0)
 
 #define ZEND_DIM_IS					(1 << 0) /* isset fetch needed for null coalesce. Set in zend_compile.c for ZEND_AST_DIM nested within ZEND_AST_COALESCE. */
+#define ZEND_ALT_CASE_SYNTAX		(1 << 1) /* deprecated switch case terminated by semicolon */
 
 /* Attributes for ${} encaps var in strings (ZEND_AST_DIM or ZEND_AST_VAR node) */
 /* ZEND_AST_VAR nodes can have any of the ZEND_ENCAPS_VAR_* flags */
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index 12d93716817a7..3f2817b26ec44 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -713,15 +713,14 @@ switch_case_list:
 
 case_list:
 		%empty { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); }
-	|	case_list T_CASE expr case_separator inner_statement_list
+	|	case_list T_CASE expr ':' inner_statement_list
 			{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, $3, $5)); }
-	|	case_list T_DEFAULT case_separator inner_statement_list
+	|	case_list T_CASE expr ';' inner_statement_list
+			{ $$ = zend_ast_list_add($1, zend_ast_create_ex(ZEND_AST_SWITCH_CASE, ZEND_ALT_CASE_SYNTAX, $3, $5)); }
+	|	case_list T_DEFAULT ':' inner_statement_list
 			{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $4)); }
-;
-
-case_separator:
-		':'
-	|	';'
+	|	case_list T_DEFAULT ';' inner_statement_list
+			{ $$ = zend_ast_list_add($1, zend_ast_create_ex(ZEND_AST_SWITCH_CASE, ZEND_ALT_CASE_SYNTAX, NULL, $4)); }
 ;
 
 


Test patch:
diff --git a/ext/opcache/tests/issue0057.phpt b/ext/opcache/tests/issue0057.phpt
index 20c2f2a10558a..ac73859b0d36e 100644
--- a/ext/opcache/tests/issue0057.phpt
+++ b/ext/opcache/tests/issue0057.phpt
@@ -15,9 +15,9 @@ class ZException extends Exception {
 function dummy($query) {
     try {
         switch ($query) {
-            case 1;
+            case 1:
             break;
-            case 2;
+            case 2:
             break;
         default:
             throw new Exception('exception');
diff --git a/tests/lang/033.phpt b/tests/lang/033.phpt
index e6254d687b07c..41424e40489d6 100644
--- a/tests/lang/033.phpt
+++ b/tests/lang/033.phpt
@@ -37,7 +37,8 @@ switch ($a):
         break;
 endswitch;
 ?>
---EXPECT--
+--EXPECTF--
+Deprecated: Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead in %s
 If: 11
 While: 12346789
 For: 0123401234
diff --git a/tests/lang/bug26696.phpt b/tests/lang/bug26696.phpt
index 1d10297b12c3d..de0dc90ca0d42 100644
--- a/tests/lang/bug26696.phpt
+++ b/tests/lang/bug26696.phpt
@@ -15,7 +15,7 @@ for ($i = 0; $i < $len; $i++) {
 
 $str = '*';
 switch ($str[0]) {
-    case '*';
+    case '*':
         echo "OK\n";
         break;
     default: