Repository: prettier/plugin-php
Pull request #2433 (2025-07-08 09:41:54)

feat: add support for PHP 8.3 typed class constants
Updated version of #2396

cc @mreiden


Patch:
diff --git a/package.json b/package.json
index bfc85b2..1ef1e88 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
   ],
   "dependencies": {
     "linguist-languages": "^8.0.0",
-    "php-parser": "^3.1.5"
+    "php-parser": "^3.2.4"
   },
   "devDependencies": {
     "@babel/preset-env": "^7.27.2",
diff --git a/src/options.mjs b/src/options.mjs
index 93fca45..b431fdf 100644
--- a/src/options.mjs
+++ b/src/options.mjs
@@ -23,6 +23,7 @@ export default {
       { value: "8.0" },
       { value: "8.1" },
       { value: "8.2" },
+      { value: "8.3" },
     ],
   },
   trailingCommaPHP: {
diff --git a/src/printer.mjs b/src/printer.mjs
index cec72d0..69300de 100644
--- a/src/printer.mjs
+++ b/src/printer.mjs
@@ -2308,6 +2308,7 @@ function printNode(path, options, print) {
         node.final ? "final " : "",
         node.visibility ? [node.visibility, " "] : "",
         "const",
+        node.type ? [node.nullable ? " ?" : " ", print("type")] : "",
         firstVariable ? [" ", firstVariable] : "",
         indent(printed.slice(1).map((p) => [",", hardline, p])),
       ]);
diff --git a/yarn.lock b/yarn.lock
index 2811e91..915ac54 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4701,10 +4701,10 @@ path-type@^4.0.0:
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
-php-parser@^3.1.5:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/php-parser/-/php-parser-3.2.1.tgz#960916dc03e4979a59435f9dd9eb03f2e86d8126"
-  integrity sha512-WT5AMqe39ZdqAxp9Yp7uR6e3clBWlT1dxHHs49GmnDx2d+975NEiLSVy2tRGLdSC9tgdQOLiN1Yz54g1d2cZDQ==
+php-parser@^3.2.4:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/php-parser/-/php-parser-3.2.4.tgz#56f97438a46c54ce0e78a8357782402f9530f892"
+  integrity sha512-X1HIaSHCb9aAReEqb+U/AAB3evE1iKD6tAY/lw9+wT4koCkWhg22m4LNtFGwd3Qv4PYIL2+zl+oXh00ALKya0Q==
 
 picocolors@^1.0.0:
   version "1.0.0"


Test patch:
diff --git a/tests/class/__snapshots__/jsfmt.spec.mjs.snap b/tests/class/__snapshots__/jsfmt.spec.mjs.snap
index a8fda07..cf60d29 100644
--- a/tests/class/__snapshots__/jsfmt.spec.mjs.snap
+++ b/tests/class/__snapshots__/jsfmt.spec.mjs.snap
@@ -1,4 +1,4 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
 
 exports[`anonymous.php 1`] = `
 ====================================options=====================================
@@ -918,6 +918,31 @@ readonly class ReadOnlyCls {
 
 class FinalCost { final public const FOO = 'foo'; }
 
+// PHP 8.3 typed class constants
+enum E {
+    const string TEST = "Test1";   // E::TEST is a string
+}
+
+trait T {
+    const string TEST = E::TEST;   // T::TEST is a string too
+}
+
+interface I {
+    const string TEST = E::TEST;   // I::TEST is a string as well
+}
+
+class Foo implements I {
+    use T;
+
+    const string TEST = E::TEST;  // Foo::TEST must also be a string
+}
+
+class Bar extends Foo {
+    const string TEST = "Test2";  // Bar::TEST must also be a string, but the value can change
+    public const int|null I = null;
+    public const ?int J = null;
+}
+
 =====================================output=====================================
 <?php
 
@@ -1368,5 +1393,35 @@ class FinalCost
     final public const FOO = "foo";
 }
 
+// PHP 8.3 typed class constants
+enum E
+{
+    const string TEST = "Test1"; // E::TEST is a string
+}
+
+trait T
+{
+    const string TEST = E::TEST; // T::TEST is a string too
+}
+
+interface I
+{
+    const string TEST = E::TEST; // I::TEST is a string as well
+}
+
+class Foo implements I
+{
+    use T;
+
+    const string TEST = E::TEST; // Foo::TEST must also be a string
+}
+
+class Bar extends Foo
+{
+    const string TEST = "Test2"; // Bar::TEST must also be a string, but the value can change
+    public const int|null I = null;
+    public const ?int J = null;
+}
+
 ================================================================================
 `;
diff --git a/tests/class/class.php b/tests/class/class.php
index 7c64b32..6261332 100644
--- a/tests/class/class.php
+++ b/tests/class/class.php
@@ -314,3 +314,28 @@ readonly class ReadOnlyCls {
 }
 
 class FinalCost { final public const FOO = 'foo'; }
+
+// PHP 8.3 typed class constants
+enum E {
+    const string TEST = "Test1";   // E::TEST is a string
+}
+
+trait T {
+    const string TEST = E::TEST;   // T::TEST is a string too
+}
+
+interface I {
+    const string TEST = E::TEST;   // I::TEST is a string as well
+}
+
+class Foo implements I {
+    use T;
+
+    const string TEST = E::TEST;  // Foo::TEST must also be a string
+}
+
+class Bar extends Foo {
+    const string TEST = "Test2";  // Bar::TEST must also be a string, but the value can change
+    public const int|null I = null;
+    public const ?int J = null;
+}