diff --git a/src/ast.js b/src/ast.js
index d11b556b5..5bed2679c 100644
--- a/src/ast.js
+++ b/src/ast.js
@@ -106,6 +106,7 @@ const Position = require("./ast/position");
  *       - [Namespace](#namespace)
  *     - [PropertyStatement](#propertystatement)
  *     - [Property](#property)
+ *       - [PropertyHook](#propertyhook)
  *     - [Declaration](#declaration)
  *       - [Class](#class)
  *       - [Interface](#interface)
@@ -552,6 +553,7 @@ AST.prototype.checkNodes = function () {
   require("./ast/print"),
   require("./ast/program"),
   require("./ast/property"),
+  require("./ast/propertyhook"),
   require("./ast/propertylookup"),
   require("./ast/propertystatement"),
   require("./ast/reference"),
diff --git a/src/ast/property.js b/src/ast/property.js
index ad01986ce..8a03219a2 100644
--- a/src/ast/property.js
+++ b/src/ast/property.js
@@ -18,6 +18,7 @@ const KIND = "property";
  * @property {boolean} readonly
  * @property {boolean} nullable
  * @property {Identifier|Array<Identifier>|null} type
+ * @propert {PropertyHook[]} hooks
  * @property {AttrGroup[]} attrGroups
  */
 module.exports = Statement.extends(
@@ -28,6 +29,7 @@ module.exports = Statement.extends(
     readonly,
     nullable,
     type,
+    hooks,
     attrGroups,
     docs,
     location,
@@ -38,6 +40,7 @@ module.exports = Statement.extends(
     this.readonly = readonly;
     this.nullable = nullable;
     this.type = type;
+    this.hooks = hooks;
     this.attrGroups = attrGroups;
   },
 );
diff --git a/src/ast/propertyhook.js b/src/ast/propertyhook.js
new file mode 100644
index 000000000..8ff8a4ea2
--- /dev/null
+++ b/src/ast/propertyhook.js
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2024 Glayzzle (BSD3 License)
+ * @authors https://github.com/glayzzle/php-parser/graphs/contributors
+ * @url http://glayzzle.com
+ */
+"use strict";
+
+const Node = require("./node");
+const KIND = "propertyhook";
+
+/**
+ * Defines a class property hook getter & setter
+ *
+ * @constructor PropertyHook
+ * @memberOf module:php-parser
+ * @extends {Node}
+ * @property {string} name
+ * @property {Boolean} isFinal
+ * @property {Boolean} byref
+ * @property {Parameter|null} parameter
+ * @property {Block|Statement} body
+ */
+module.exports = Node.extends(
+  KIND,
+  function PropertyHook(name, isFinal, byref, parameter, body, docs, location) {
+    Node.apply(this, [KIND, docs, location]);
+    this.name = name;
+    this.byref = byref;
+    this.parameter = parameter;
+    this.body = body;
+    this.isFinal = isFinal;
+  },
+);
diff --git a/src/ast/propertystatement.js b/src/ast/propertystatement.js
index 271c8e6a8..6cc46926d 100644
--- a/src/ast/propertystatement.js
+++ b/src/ast/propertystatement.js
@@ -52,6 +52,8 @@ PropertyStatement.prototype.parseFlags = function (flags) {
   }
 
   this.isStatic = flags[1] === 1;
+  this.isAbstract = flags[2] === 1;
+  this.isFinal = flags[2] === 2;
 };
 
 module.exports = PropertyStatement;
diff --git a/src/parser/class.js b/src/parser/class.js
index 996f5acf3..4d086c1a5 100644
--- a/src/parser/class.js
+++ b/src/parser/class.js
@@ -152,8 +152,6 @@ module.exports = {
         // reads a variable
         const variables = this.read_variable_list(flags, attrs);
         attrs = [];
-        this.expect(";");
-        this.next();
         result = result.concat(variables);
       } else {
         // raise an error
@@ -178,7 +176,7 @@ module.exports = {
    * ```
    */
   read_variable_list: function (flags, attrs) {
-    const result = this.node("propertystatement");
+    let property_statement = this.node("propertystatement");
 
     const properties = this.read_list(
       /*
@@ -201,28 +199,119 @@ module.exports = {
         const name = this.text().substring(1); // ignore $
         this.next();
         propName = propName(name);
-        if (this.token === ";" || this.token === ",") {
-          return result(propName, null, readonly, nullable, type, attrs || []);
-        } else if (this.token === "=") {
+
+        let value = null;
+        let property_hooks = [];
+
+        this.expect([",", ";", "=", "{"]);
+
+        // Property has a value
+        if (this.token === "=") {
           // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L815
-          return result(
-            propName,
-            this.next().read_expr(),
-            readonly,
-            nullable,
-            type,
-            attrs || [],
-          );
+          value = this.next().read_expr();
+        }
+
+        // Property is using a hook to define getter/setters
+        if (this.token === "{") {
+          property_hooks = this.read_property_hooks();
         } else {
-          this.expect([",", ";", "="]);
-          return result(propName, null, nullable, type, attrs || []);
+          this.expect([";", ","]);
         }
+
+        return result(
+          propName,
+          value,
+          readonly,
+          nullable,
+          type,
+          property_hooks,
+          attrs || [],
+        );
       },
       ",",
     );
 
-    return result(null, properties, flags);
+    property_statement = property_statement(null, properties, flags);
+
+    // semicolons are found only for regular properties definitions.
+    // Property hooks are terminated by a closing curly brace, }.
+    // property_statement is instanciated before this check to avoid including the semicolon in the AST end location of the property.
+    if (this.token === ";") {
+      this.next();
+    }
+    return property_statement;
   },
+
+  /**
+   * Reads property hooks
+   *
+   * @returns {PropertyHook[]}
+   */
+  read_property_hooks: function () {
+    if (this.version < 804) {
+      this.raiseError("Parse Error: Typed Class Constants requires PHP 8.4+");
+    }
+    this.expect("{");
+    this.next();
+
+    const hooks = [];
+
+    while (this.token !== "}") {
+      hooks.push(this.read_property_hook());
+    }
+
+    if (this.token === "}") {
+      this.next();
+      return hooks;
+    }
+    return [];
+  },
+
+  read_property_hook: function () {
+    const property_hooks = this.node("propertyhook");
+
+    const is_final = this.token === this.tok.T_FINAL;
+    if (is_final) this.next();
+
+    const is_reference = this.token === "&";
+    if (is_reference) this.next();
+
+    const method_name = this.text();
+
+    if (method_name !== "get" && method_name !== "set") {
+      this.raiseError(
+        "Parse Error: Property hooks must be either 'get' or 'set'",
+      );
+    }
+    this.next();
+
+    let parameter = null;
+    let body = null;
+    this.expect([this.tok.T_DOUBLE_ARROW, "{", "(", ";"]);
+
+    // interface or abstract definition
+    if (this.token === ";") {
+      this.next();
+    }
+
+    if (this.token === "(") {
+      this.next();
+      parameter = this.read_parameter(false);
+      this.expect(")");
+      this.next();
+    }
+
+    if (this.token === this.tok.T_DOUBLE_ARROW) {
+      this.next();
+      body = this.read_expr();
+      this.next();
+    } else if (this.token === "{") {
+      body = this.read_code_block();
+    }
+
+    return property_hooks(method_name, is_final, is_reference, parameter, body);
+  },
+
   /*
    * Reads constant list
    * ```ebnf
@@ -460,9 +549,11 @@ module.exports = {
           this.next();
         }
         attrs = [];
+      } else if (this.token === this.tok.T_STRING) {
+        result.push(this.read_variable_list(flags, attrs));
       } else {
         // raise an error
-        this.error([this.tok.T_CONST, this.tok.T_FUNCTION]);
+        this.error([this.tok.T_CONST, this.tok.T_FUNCTION, this.tok.T_STRING]);
         this.next();
       }
     }
diff --git a/test/snapshot/__snapshots__/acid.test.js.snap b/test/snapshot/__snapshots__/acid.test.js.snap
index 66955f4bb..6a5294025 100644
--- a/test/snapshot/__snapshots__/acid.test.js.snap
+++ b/test/snapshot/__snapshots__/acid.test.js.snap
@@ -831,6 +831,8 @@ Program {
               "visibility": "",
             },
             PropertyStatement {
+              "isAbstract": false,
+              "isFinal": false,
               "isStatic": false,
               "kind": "propertystatement",
               "loc": Location {
@@ -852,6 +854,7 @@ Program {
               "properties": [
                 Property {
                   "attrGroups": [],
+                  "hooks": [],
                   "kind": "property",
                   "loc": Location {
                     "end": Position {
diff --git a/test/snapshot/__snapshots__/attributes.test.js.snap b/test/snapshot/__snapshots__/attributes.test.js.snap
index 36fb1d4f0..8dd279b4a 100644
--- a/test/snapshot/__snapshots__/attributes.test.js.snap
+++ b/test/snapshot/__snapshots__/attributes.test.js.snap
@@ -365,6 +365,8 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
@@ -381,6 +383,7 @@ Program {
                   "kind": "attrgroup",
                 },
               ],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -399,6 +402,8 @@ Program {
           "visibility": "public",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
@@ -415,6 +420,7 @@ Program {
                   "kind": "attrgroup",
                 },
               ],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -433,6 +439,8 @@ Program {
           "visibility": "private",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
@@ -449,6 +457,7 @@ Program {
                   "kind": "attrgroup",
                 },
               ],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -2566,6 +2575,8 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
@@ -2652,6 +2663,7 @@ Program {
                   "kind": "attrgroup",
                 },
               ],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
diff --git a/test/snapshot/__snapshots__/class.test.js.snap b/test/snapshot/__snapshots__/class.test.js.snap
index 6fd21a3aa..67bb7f6c0 100644
--- a/test/snapshot/__snapshots__/class.test.js.snap
+++ b/test/snapshot/__snapshots__/class.test.js.snap
@@ -40,6 +40,8 @@ Program {
           ],
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "leadingComments": [
@@ -66,6 +68,7 @@ Program {
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -461,11 +464,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -509,11 +515,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -535,11 +544,14 @@ Program {
           "visibility": "public",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": true,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -594,11 +606,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": true,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -1158,163 +1173,7 @@ Program {
 }
 `;
 
-exports[`Test classes Test that readonly method parameters are throwing errors 1`] = `
-Program {
-  "children": [
-    Class {
-      "attrGroups": [],
-      "body": [
-        Method {
-          "arguments": [
-            Parameter {
-              "attrGroups": [],
-              "byref": false,
-              "flags": 1,
-              "kind": "parameter",
-              "name": null,
-              "nullable": false,
-              "readonly": false,
-              "type": null,
-              "value": null,
-              "variadic": false,
-            },
-          ],
-          "attrGroups": [],
-          "body": null,
-          "byref": false,
-          "isAbstract": false,
-          "isFinal": false,
-          "isReadonly": false,
-          "isStatic": false,
-          "kind": "method",
-          "name": Identifier {
-            "kind": "identifier",
-            "name": "foo",
-          },
-          "nullable": false,
-          "type": null,
-          "visibility": "public",
-        },
-        PropertyStatement {
-          "isStatic": false,
-          "kind": "propertystatement",
-          "properties": [
-            Property {
-              "attrGroups": null,
-              "kind": "property",
-              "name": Identifier {
-                "kind": "identifier",
-                "name": "id",
-              },
-              "nullable": TypeReference {
-                "kind": "typereference",
-                "name": "int",
-                "raw": "int",
-              },
-              "readonly": false,
-              "type": [],
-              "value": null,
-            },
-          ],
-          "visibility": "",
-        },
-      ],
-      "extends": null,
-      "implements": null,
-      "isAbstract": false,
-      "isAnonymous": false,
-      "isFinal": false,
-      "isReadonly": false,
-      "kind": "class",
-      "name": Identifier {
-        "kind": "identifier",
-        "name": "Bob",
-      },
-    },
-    ExpressionStatement {
-      "expression": undefined,
-      "kind": "expressionstatement",
-    },
-  ],
-  "errors": [
-    Error {
-      "expected": undefined,
-      "kind": "error",
-      "line": 3,
-      "message": "readonly properties can be used only on class constructor on line 3",
-      "token": undefined,
-    },
-    Error {
-      "expected": 222,
-      "kind": "error",
-      "line": 3,
-      "message": "Parse Error : syntax error, unexpected 'readonly' (T_READ_ONLY), expecting T_VARIABLE on line 3",
-      "token": "'readonly' (T_READ_ONLY)",
-    },
-    Error {
-      "expected": [
-        ",",
-        ")",
-      ],
-      "kind": "error",
-      "line": 3,
-      "message": "Parse Error : syntax error, unexpected 'readonly' (T_READ_ONLY) on line 3",
-      "token": "'readonly' (T_READ_ONLY)",
-    },
-    Error {
-      "expected": ")",
-      "kind": "error",
-      "line": 3,
-      "message": "Parse Error : syntax error, unexpected 'readonly' (T_READ_ONLY), expecting ')' on line 3",
-      "token": "'readonly' (T_READ_ONLY)",
-    },
-    Error {
-      "expected": "{",
-      "kind": "error",
-      "line": 3,
-      "message": "Parse Error : syntax error, unexpected 'readonly' (T_READ_ONLY), expecting '{' on line 3",
-      "token": "'readonly' (T_READ_ONLY)",
-    },
-    Error {
-      "expected": [
-        ",",
-        ";",
-        "=",
-      ],
-      "kind": "error",
-      "line": 3,
-      "message": "Parse Error : syntax error, unexpected ')' on line 3",
-      "token": "')'",
-    },
-    Error {
-      "expected": ";",
-      "kind": "error",
-      "line": 3,
-      "message": "Parse Error : syntax error, unexpected ')', expecting ';' on line 3",
-      "token": "')'",
-    },
-    Error {
-      "expected": [
-        198,
-        222,
-        182,
-      ],
-      "kind": "error",
-      "line": 3,
-      "message": "Parse Error : syntax error, unexpected '{' on line 3",
-      "token": "'{'",
-    },
-    Error {
-      "expected": "EXPR",
-      "kind": "error",
-      "line": 4,
-      "message": "Parse Error : syntax error, unexpected '}' on line 4",
-      "token": "'}'",
-    },
-  ],
-  "kind": "program",
-}
-`;
+exports[`Test classes Test that readonly method parameters are throwing errors 1`] = `"readonly properties can be used only on class constructor on line 3"`;
 
 exports[`Test classes Validate usual declarations 1`] = `
 Program {
@@ -1347,11 +1206,14 @@ Program {
           "visibility": "",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": true,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -1522,11 +1384,14 @@ Program {
           "visibility": "public",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -1541,11 +1406,14 @@ Program {
           "visibility": "protected",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -1917,11 +1785,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
diff --git a/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap
new file mode 100644
index 000000000..a28e33092
--- /dev/null
+++ b/test/snapshot/__snapshots__/classpropertyhooks.test.js.snap
@@ -0,0 +1,2106 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`classpropertyhooks abstract get + set 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": true,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": null,
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+                PropertyHook {
+                  "body": null,
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "email",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": true,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "User",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks abstract get 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": true,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": null,
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "email",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": true,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "User",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks abstract set 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": true,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": null,
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "email",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": true,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "User",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks can be access by reference 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "_baz",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "private",
+        },
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": PropertyLookup {
+                    "kind": "propertylookup",
+                    "offset": Identifier {
+                      "kind": "identifier",
+                      "name": "_baz",
+                    },
+                    "what": Variable {
+                      "curly": false,
+                      "kind": "variable",
+                      "name": "this",
+                    },
+                  },
+                  "byref": true,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+                PropertyHook {
+                  "body": Block {
+                    "children": [
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "_baz",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Call {
+                            "arguments": [
+                              Variable {
+                                "curly": false,
+                                "kind": "variable",
+                                "name": "value",
+                              },
+                            ],
+                            "kind": "call",
+                            "what": Name {
+                              "kind": "name",
+                              "name": "strtoupper",
+                              "resolution": "uqn",
+                            },
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                    ],
+                    "kind": "block",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "baz",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "Foo",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks final on the hook itself 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Block {
+                    "children": [
+                      If {
+                        "alternate": null,
+                        "body": Block {
+                          "children": [
+                            Throw {
+                              "kind": "throw",
+                              "what": New {
+                                "arguments": [
+                                  String {
+                                    "isDoubleQuote": false,
+                                    "kind": "string",
+                                    "raw": "'Invalid email'",
+                                    "unicode": false,
+                                    "value": "Invalid email",
+                                  },
+                                ],
+                                "kind": "new",
+                                "what": Name {
+                                  "kind": "name",
+                                  "name": "InvalidArgumentException",
+                                  "resolution": "uqn",
+                                },
+                              },
+                            },
+                          ],
+                          "kind": "block",
+                        },
+                        "kind": "if",
+                        "shortForm": false,
+                        "test": Unary {
+                          "kind": "unary",
+                          "type": "!",
+                          "what": Call {
+                            "arguments": [
+                              Variable {
+                                "curly": false,
+                                "kind": "variable",
+                                "name": "value",
+                              },
+                              Name {
+                                "kind": "name",
+                                "name": "FILTER_VALIDATE_EMAIL",
+                                "resolution": "uqn",
+                              },
+                              Name {
+                                "kind": "name",
+                                "name": "FILTER_FLAG_EMAIL_UNICODE",
+                                "resolution": "uqn",
+                              },
+                            ],
+                            "kind": "call",
+                            "what": Name {
+                              "kind": "name",
+                              "name": "filter_var",
+                              "resolution": "uqn",
+                            },
+                          },
+                        },
+                      },
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "email",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Variable {
+                            "curly": false,
+                            "kind": "variable",
+                            "name": "value",
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                    ],
+                    "kind": "block",
+                  },
+                  "byref": false,
+                  "isFinal": true,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "email",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "StandardUser",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks final on the property 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": true,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "name",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": true,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Call {
+                    "arguments": [
+                      Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "value",
+                      },
+                    ],
+                    "kind": "call",
+                    "what": Name {
+                      "kind": "name",
+                      "name": "strtolower",
+                      "resolution": "uqn",
+                    },
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "username",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "User",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks getter arrow function 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Bin {
+                    "kind": "bin",
+                    "left": String {
+                      "isDoubleQuote": false,
+                      "kind": "string",
+                      "raw": "'mailto:'",
+                      "unicode": false,
+                      "value": "mailto:",
+                    },
+                    "right": PropertyLookup {
+                      "kind": "propertylookup",
+                      "offset": Identifier {
+                        "kind": "identifier",
+                        "name": "email",
+                      },
+                      "what": Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "this",
+                      },
+                    },
+                    "type": ".",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "credits",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "BookViewModel",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks getter block 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Block {
+                    "children": [
+                      ExpressionStatement {
+                        "expression": Bin {
+                          "kind": "bin",
+                          "left": String {
+                            "isDoubleQuote": false,
+                            "kind": "string",
+                            "raw": "'mailto:'",
+                            "unicode": false,
+                            "value": "mailto:",
+                          },
+                          "right": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "email",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "type": ".",
+                        },
+                        "kind": "expressionstatement",
+                      },
+                    ],
+                    "kind": "block",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "credits",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "BookViewModel",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks not supported in php < 8.4 1`] = `"Parse Error: Typed Class Constants requires PHP 8.4+ on line 2"`;
+
+exports[`classpropertyhooks setter block form with explicit typed $value 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Block {
+                    "children": [
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": Variable {
+                            "curly": false,
+                            "kind": "variable",
+                            "name": "tmp",
+                          },
+                          "operator": "=",
+                          "right": Bin {
+                            "kind": "bin",
+                            "left": PropertyLookup {
+                              "kind": "propertylookup",
+                              "offset": Identifier {
+                                "kind": "identifier",
+                                "name": "credits",
+                              },
+                              "what": Variable {
+                                "curly": false,
+                                "kind": "variable",
+                                "name": "this",
+                              },
+                            },
+                            "right": Number {
+                              "kind": "number",
+                              "value": "1",
+                            },
+                            "type": "+",
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "propertyName",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Bin {
+                            "kind": "bin",
+                            "left": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "value",
+                            },
+                            "right": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "tmp",
+                            },
+                            "type": "+",
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                    ],
+                    "kind": "block",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": Parameter {
+                    "attrGroups": [],
+                    "byref": false,
+                    "flags": 0,
+                    "kind": "parameter",
+                    "name": Identifier {
+                      "kind": "identifier",
+                      "name": "value",
+                    },
+                    "nullable": false,
+                    "readonly": false,
+                    "type": TypeReference {
+                      "kind": "typereference",
+                      "name": "int",
+                      "raw": "int",
+                    },
+                    "value": null,
+                    "variadic": false,
+                  },
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "credits",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "int",
+                "raw": "int",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "BookViewModel",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks setter block form with explicit untyped $value 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Block {
+                    "children": [
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": Variable {
+                            "curly": false,
+                            "kind": "variable",
+                            "name": "tmp",
+                          },
+                          "operator": "=",
+                          "right": Bin {
+                            "kind": "bin",
+                            "left": PropertyLookup {
+                              "kind": "propertylookup",
+                              "offset": Identifier {
+                                "kind": "identifier",
+                                "name": "credits",
+                              },
+                              "what": Variable {
+                                "curly": false,
+                                "kind": "variable",
+                                "name": "this",
+                              },
+                            },
+                            "right": Number {
+                              "kind": "number",
+                              "value": "1",
+                            },
+                            "type": "+",
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "propertyName",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Bin {
+                            "kind": "bin",
+                            "left": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "value",
+                            },
+                            "right": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "tmp",
+                            },
+                            "type": "+",
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                    ],
+                    "kind": "block",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": Parameter {
+                    "attrGroups": [],
+                    "byref": false,
+                    "flags": 0,
+                    "kind": "parameter",
+                    "name": Identifier {
+                      "kind": "identifier",
+                      "name": "value",
+                    },
+                    "nullable": false,
+                    "readonly": false,
+                    "type": null,
+                    "value": null,
+                    "variadic": false,
+                  },
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "credits",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": null,
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "BookViewModel",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks setter block form with implicit $value 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Block {
+                    "children": [
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": Variable {
+                            "curly": false,
+                            "kind": "variable",
+                            "name": "tmp",
+                          },
+                          "operator": "=",
+                          "right": Bin {
+                            "kind": "bin",
+                            "left": PropertyLookup {
+                              "kind": "propertylookup",
+                              "offset": Identifier {
+                                "kind": "identifier",
+                                "name": "credits",
+                              },
+                              "what": Variable {
+                                "curly": false,
+                                "kind": "variable",
+                                "name": "this",
+                              },
+                            },
+                            "right": Number {
+                              "kind": "number",
+                              "value": "1",
+                            },
+                            "type": "+",
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "propertyName",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Bin {
+                            "kind": "bin",
+                            "left": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "value",
+                            },
+                            "right": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "tmp",
+                            },
+                            "type": "+",
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                    ],
+                    "kind": "block",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "credits",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "BookViewModel",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks setter expression form with explicit typed $value 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Assign {
+                    "kind": "assign",
+                    "left": PropertyLookup {
+                      "kind": "propertylookup",
+                      "offset": Identifier {
+                        "kind": "identifier",
+                        "name": "name",
+                      },
+                      "what": Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "this",
+                      },
+                    },
+                    "operator": "=",
+                    "right": Call {
+                      "arguments": [
+                        Variable {
+                          "curly": false,
+                          "kind": "variable",
+                          "name": "new_name",
+                        },
+                      ],
+                      "kind": "call",
+                      "what": Name {
+                        "kind": "name",
+                        "name": "strtolower",
+                        "resolution": "uqn",
+                      },
+                    },
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": Parameter {
+                    "attrGroups": [],
+                    "byref": false,
+                    "flags": 0,
+                    "kind": "parameter",
+                    "name": Identifier {
+                      "kind": "identifier",
+                      "name": "new_name",
+                    },
+                    "nullable": false,
+                    "readonly": false,
+                    "type": TypeReference {
+                      "kind": "typereference",
+                      "name": "string",
+                      "raw": "string",
+                    },
+                    "value": null,
+                    "variadic": false,
+                  },
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "name",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "Person",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks setter expression form with explicit untyped $value 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Assign {
+                    "kind": "assign",
+                    "left": PropertyLookup {
+                      "kind": "propertylookup",
+                      "offset": Identifier {
+                        "kind": "identifier",
+                        "name": "name",
+                      },
+                      "what": Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "this",
+                      },
+                    },
+                    "operator": "=",
+                    "right": Call {
+                      "arguments": [
+                        Variable {
+                          "curly": false,
+                          "kind": "variable",
+                          "name": "new_name",
+                        },
+                      ],
+                      "kind": "call",
+                      "what": Name {
+                        "kind": "name",
+                        "name": "strtolower",
+                        "resolution": "uqn",
+                      },
+                    },
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": Parameter {
+                    "attrGroups": [],
+                    "byref": false,
+                    "flags": 0,
+                    "kind": "parameter",
+                    "name": Identifier {
+                      "kind": "identifier",
+                      "name": "new_name",
+                    },
+                    "nullable": false,
+                    "readonly": false,
+                    "type": null,
+                    "value": null,
+                    "variadic": false,
+                  },
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "name",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": null,
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "Person",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks setter expression form with implicit $value 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Assign {
+                    "kind": "assign",
+                    "left": PropertyLookup {
+                      "kind": "propertylookup",
+                      "offset": Identifier {
+                        "kind": "identifier",
+                        "name": "credits",
+                      },
+                      "what": Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "this",
+                      },
+                    },
+                    "operator": "=",
+                    "right": Variable {
+                      "curly": false,
+                      "kind": "variable",
+                      "name": "value",
+                    },
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "credits",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "BookViewModel",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks support default value 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": PropertyLookup {
+                    "kind": "propertylookup",
+                    "offset": Identifier {
+                      "kind": "identifier",
+                      "name": "foo",
+                    },
+                    "what": Variable {
+                      "curly": false,
+                      "kind": "variable",
+                      "name": "this",
+                    },
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "foo",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": String {
+                "isDoubleQuote": false,
+                "kind": "string",
+                "raw": "'default value'",
+                "unicode": false,
+                "value": "default value",
+              },
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "Example",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks support setter + getter 1`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "modified",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "bool",
+                "raw": "bool",
+              },
+              "value": Boolean {
+                "kind": "boolean",
+                "raw": "false",
+                "value": false,
+              },
+            },
+          ],
+          "visibility": "private",
+        },
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Bin {
+                    "kind": "bin",
+                    "left": PropertyLookup {
+                      "kind": "propertylookup",
+                      "offset": Identifier {
+                        "kind": "identifier",
+                        "name": "foo",
+                      },
+                      "what": Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "this",
+                      },
+                    },
+                    "right": RetIf {
+                      "falseExpr": String {
+                        "isDoubleQuote": false,
+                        "kind": "string",
+                        "raw": "''",
+                        "unicode": false,
+                        "value": "",
+                      },
+                      "kind": "retif",
+                      "parenthesizedExpression": true,
+                      "test": PropertyLookup {
+                        "kind": "propertylookup",
+                        "offset": Identifier {
+                          "kind": "identifier",
+                          "name": "modified",
+                        },
+                        "what": Variable {
+                          "curly": false,
+                          "kind": "variable",
+                          "name": "this",
+                        },
+                      },
+                      "trueExpr": String {
+                        "isDoubleQuote": false,
+                        "kind": "string",
+                        "raw": "' (modified)'",
+                        "unicode": false,
+                        "value": " (modified)",
+                      },
+                    },
+                    "type": ".",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+                PropertyHook {
+                  "body": Block {
+                    "children": [
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "foo",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Call {
+                            "arguments": [
+                              Variable {
+                                "curly": false,
+                                "kind": "variable",
+                                "name": "value",
+                              },
+                            ],
+                            "kind": "call",
+                            "what": Name {
+                              "kind": "name",
+                              "name": "strtolower",
+                              "resolution": "uqn",
+                            },
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "modified",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Boolean {
+                            "kind": "boolean",
+                            "raw": "true",
+                            "value": true,
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                    ],
+                    "kind": "block",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": Parameter {
+                    "attrGroups": [],
+                    "byref": false,
+                    "flags": 0,
+                    "kind": "parameter",
+                    "name": Identifier {
+                      "kind": "identifier",
+                      "name": "value",
+                    },
+                    "nullable": false,
+                    "readonly": false,
+                    "type": TypeReference {
+                      "kind": "typereference",
+                      "name": "string",
+                      "raw": "string",
+                    },
+                    "value": null,
+                    "variadic": false,
+                  },
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "foo",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": String {
+                "isDoubleQuote": false,
+                "kind": "string",
+                "raw": "'default value'",
+                "unicode": false,
+                "value": "default value",
+              },
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "Example",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks support setter + getter 2`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "modified",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "bool",
+                "raw": "bool",
+              },
+              "value": Boolean {
+                "kind": "boolean",
+                "raw": "false",
+                "value": false,
+              },
+            },
+          ],
+          "visibility": "private",
+        },
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Bin {
+                    "kind": "bin",
+                    "left": PropertyLookup {
+                      "kind": "propertylookup",
+                      "offset": Identifier {
+                        "kind": "identifier",
+                        "name": "foo",
+                      },
+                      "what": Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "this",
+                      },
+                    },
+                    "right": RetIf {
+                      "falseExpr": String {
+                        "isDoubleQuote": false,
+                        "kind": "string",
+                        "raw": "''",
+                        "unicode": false,
+                        "value": "",
+                      },
+                      "kind": "retif",
+                      "parenthesizedExpression": true,
+                      "test": PropertyLookup {
+                        "kind": "propertylookup",
+                        "offset": Identifier {
+                          "kind": "identifier",
+                          "name": "modified",
+                        },
+                        "what": Variable {
+                          "curly": false,
+                          "kind": "variable",
+                          "name": "this",
+                        },
+                      },
+                      "trueExpr": String {
+                        "isDoubleQuote": false,
+                        "kind": "string",
+                        "raw": "' (modified)'",
+                        "unicode": false,
+                        "value": " (modified)",
+                      },
+                    },
+                    "type": ".",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+                PropertyHook {
+                  "body": Block {
+                    "children": [
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "foo",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Call {
+                            "arguments": [
+                              Variable {
+                                "curly": false,
+                                "kind": "variable",
+                                "name": "value",
+                              },
+                            ],
+                            "kind": "call",
+                            "what": Name {
+                              "kind": "name",
+                              "name": "strtolower",
+                              "resolution": "uqn",
+                            },
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                      ExpressionStatement {
+                        "expression": Assign {
+                          "kind": "assign",
+                          "left": PropertyLookup {
+                            "kind": "propertylookup",
+                            "offset": Identifier {
+                              "kind": "identifier",
+                              "name": "modified",
+                            },
+                            "what": Variable {
+                              "curly": false,
+                              "kind": "variable",
+                              "name": "this",
+                            },
+                          },
+                          "operator": "=",
+                          "right": Boolean {
+                            "kind": "boolean",
+                            "raw": "true",
+                            "value": true,
+                          },
+                        },
+                        "kind": "expressionstatement",
+                      },
+                    ],
+                    "kind": "block",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "foo",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": String {
+                "isDoubleQuote": false,
+                "kind": "string",
+                "raw": "'default value'",
+                "unicode": false,
+                "value": "default value",
+              },
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "Example",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`classpropertyhooks support setter + getter 3`] = `
+Program {
+  "children": [
+    Class {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": Bin {
+                    "kind": "bin",
+                    "left": PropertyLookup {
+                      "kind": "propertylookup",
+                      "offset": Identifier {
+                        "kind": "identifier",
+                        "name": "foo",
+                      },
+                      "what": Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "this",
+                      },
+                    },
+                    "right": RetIf {
+                      "falseExpr": String {
+                        "isDoubleQuote": false,
+                        "kind": "string",
+                        "raw": "''",
+                        "unicode": false,
+                        "value": "",
+                      },
+                      "kind": "retif",
+                      "parenthesizedExpression": true,
+                      "test": PropertyLookup {
+                        "kind": "propertylookup",
+                        "offset": Identifier {
+                          "kind": "identifier",
+                          "name": "modified",
+                        },
+                        "what": Variable {
+                          "curly": false,
+                          "kind": "variable",
+                          "name": "this",
+                        },
+                      },
+                      "trueExpr": String {
+                        "isDoubleQuote": false,
+                        "kind": "string",
+                        "raw": "' (modified)'",
+                        "unicode": false,
+                        "value": " (modified)",
+                      },
+                    },
+                    "type": ".",
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+                PropertyHook {
+                  "body": Call {
+                    "arguments": [
+                      Variable {
+                        "curly": false,
+                        "kind": "variable",
+                        "name": "value",
+                      },
+                    ],
+                    "kind": "call",
+                    "what": Name {
+                      "kind": "name",
+                      "name": "strtolower",
+                      "resolution": "uqn",
+                    },
+                  },
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "foo",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "string",
+                "raw": "string",
+              },
+              "value": String {
+                "isDoubleQuote": false,
+                "kind": "string",
+                "raw": "'default value'",
+                "unicode": false,
+                "value": "default value",
+              },
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "isReadonly": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "Example",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
diff --git a/test/snapshot/__snapshots__/comment.test.js.snap b/test/snapshot/__snapshots__/comment.test.js.snap
index 0288dac0c..507ca0338 100644
--- a/test/snapshot/__snapshots__/comment.test.js.snap
+++ b/test/snapshot/__snapshots__/comment.test.js.snap
@@ -1846,6 +1846,8 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "leadingComments": [
@@ -1864,6 +1866,7 @@ Program {
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -1876,6 +1879,7 @@ Program {
             },
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -1890,6 +1894,8 @@ Program {
           "visibility": "protected",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": true,
           "kind": "propertystatement",
           "leadingComments": [
@@ -1908,6 +1914,7 @@ Program {
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
diff --git a/test/snapshot/__snapshots__/graceful.test.js.snap b/test/snapshot/__snapshots__/graceful.test.js.snap
index cf45e3329..bdbda8496 100644
--- a/test/snapshot/__snapshots__/graceful.test.js.snap
+++ b/test/snapshot/__snapshots__/graceful.test.js.snap
@@ -5,7 +5,34 @@ Program {
   "children": [
     Interface {
       "attrGroups": [],
-      "body": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": Name {
+                "kind": "name",
+                "name": "baz",
+                "resolution": "uqn",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "",
+        },
+      ],
       "extends": null,
       "kind": "interface",
       "name": Identifier {
@@ -22,25 +49,34 @@ Program {
       "message": "Parse Error : syntax error, unexpected 'implement' (T_STRING), expecting '{' on line 1",
       "token": "'implement' (T_STRING)",
     },
+    Error {
+      "expected": 222,
+      "kind": "error",
+      "line": 1,
+      "message": "Parse Error : syntax error, unexpected '{', expecting T_VARIABLE on line 1",
+      "token": "'{'",
+    },
     Error {
       "expected": [
-        198,
-        182,
+        ",",
+        ";",
+        "=",
+        "{",
       ],
       "kind": "error",
       "line": 1,
-      "message": "Parse Error : syntax error, unexpected 'baz' (T_STRING) on line 1",
-      "token": "'baz' (T_STRING)",
+      "message": "Parse Error : syntax error, unexpected '}' on line 1",
+      "token": "'}'",
     },
     Error {
       "expected": [
-        198,
-        182,
+        ";",
+        ",",
       ],
       "kind": "error",
       "line": 1,
-      "message": "Parse Error : syntax error, unexpected '{' on line 1",
-      "token": "'{'",
+      "message": "Parse Error : syntax error, unexpected '}' on line 1",
+      "token": "'}'",
     },
   ],
   "kind": "program",
@@ -296,23 +332,52 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
-              "attrGroups": null,
+              "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
                 "name": "onst",
               },
-              "nullable": Name {
+              "nullable": false,
+              "readonly": false,
+              "type": Name {
                 "kind": "name",
                 "name": "foo",
                 "resolution": "uqn",
               },
+              "value": null,
+            },
+          ],
+          "visibility": "",
+        },
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "",
+              },
+              "nullable": false,
               "readonly": false,
-              "type": [],
+              "type": Name {
+                "kind": "name",
+                "name": "A",
+                "resolution": "uqn",
+              },
               "value": null,
             },
           ],
@@ -345,6 +410,7 @@ Program {
         ",",
         ";",
         "=",
+        "{",
       ],
       "kind": "error",
       "line": 1,
@@ -352,22 +418,43 @@ Program {
       "token": "'A' (T_STRING)",
     },
     Error {
-      "expected": ";",
+      "expected": [
+        ";",
+        ",",
+      ],
       "kind": "error",
       "line": 1,
-      "message": "Parse Error : syntax error, unexpected 'A' (T_STRING), expecting ';' on line 1",
+      "message": "Parse Error : syntax error, unexpected 'A' (T_STRING) on line 1",
       "token": "'A' (T_STRING)",
     },
+    Error {
+      "expected": 222,
+      "kind": "error",
+      "line": 1,
+      "message": "Parse Error : syntax error, unexpected '=', expecting T_VARIABLE on line 1",
+      "token": "'='",
+    },
     Error {
       "expected": [
-        198,
-        222,
-        182,
+        ",",
+        ";",
+        "=",
+        "{",
       ],
       "kind": "error",
       "line": 1,
-      "message": "Parse Error : syntax error, unexpected '=' on line 1",
-      "token": "'='",
+      "message": "Parse Error : syntax error, unexpected '1' (T_LNUMBER) on line 1",
+      "token": "'1' (T_LNUMBER)",
+    },
+    Error {
+      "expected": [
+        ";",
+        ",",
+      ],
+      "kind": "error",
+      "line": 1,
+      "message": "Parse Error : syntax error, unexpected '1' (T_LNUMBER) on line 1",
+      "token": "'1' (T_LNUMBER)",
     },
     Error {
       "expected": [
@@ -839,23 +926,52 @@ Program {
     Trait {
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
-              "attrGroups": null,
+              "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
                 "name": "mplement",
               },
-              "nullable": Name {
+              "nullable": false,
+              "readonly": false,
+              "type": Name {
                 "kind": "name",
                 "name": "bar",
                 "resolution": "uqn",
               },
+              "value": null,
+            },
+          ],
+          "visibility": "",
+        },
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "",
+              },
+              "nullable": false,
               "readonly": false,
-              "type": [],
+              "type": Name {
+                "kind": "name",
+                "name": "baz",
+                "resolution": "uqn",
+              },
               "value": null,
             },
           ],
@@ -889,6 +1005,7 @@ Program {
         ",",
         ";",
         "=",
+        "{",
       ],
       "kind": "error",
       "line": 1,
@@ -896,22 +1013,43 @@ Program {
       "token": "'baz' (T_STRING)",
     },
     Error {
-      "expected": ";",
+      "expected": [
+        ";",
+        ",",
+      ],
       "kind": "error",
       "line": 1,
-      "message": "Parse Error : syntax error, unexpected 'baz' (T_STRING), expecting ';' on line 1",
+      "message": "Parse Error : syntax error, unexpected 'baz' (T_STRING) on line 1",
       "token": "'baz' (T_STRING)",
     },
+    Error {
+      "expected": 222,
+      "kind": "error",
+      "line": 1,
+      "message": "Parse Error : syntax error, unexpected '{', expecting T_VARIABLE on line 1",
+      "token": "'{'",
+    },
     Error {
       "expected": [
-        198,
-        222,
-        182,
+        ",",
+        ";",
+        "=",
+        "{",
       ],
       "kind": "error",
       "line": 1,
-      "message": "Parse Error : syntax error, unexpected '{' on line 1",
-      "token": "'{'",
+      "message": "Parse Error : syntax error, unexpected '}' on line 1",
+      "token": "'}'",
+    },
+    Error {
+      "expected": [
+        ";",
+        ",",
+      ],
+      "kind": "error",
+      "line": 1,
+      "message": "Parse Error : syntax error, unexpected '}' on line 1",
+      "token": "'}'",
     },
   ],
   "kind": "program",
diff --git a/test/snapshot/__snapshots__/heredoc.test.js.snap b/test/snapshot/__snapshots__/heredoc.test.js.snap
index cdf9be706..7a9b38fcd 100644
--- a/test/snapshot/__snapshots__/heredoc.test.js.snap
+++ b/test/snapshot/__snapshots__/heredoc.test.js.snap
@@ -1716,11 +1716,14 @@ FOOBAR",
           "visibility": "",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
diff --git a/test/snapshot/__snapshots__/interface.test.js.snap b/test/snapshot/__snapshots__/interface.test.js.snap
index 14dd6a85c..577a7789a 100644
--- a/test/snapshot/__snapshots__/interface.test.js.snap
+++ b/test/snapshot/__snapshots__/interface.test.js.snap
@@ -125,3 +125,176 @@ Program {
   "kind": "program",
 }
 `;
+
+exports[`interface property hooks get + set 1`] = `
+Program {
+  "children": [
+    Interface {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": null,
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+                PropertyHook {
+                  "body": null,
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "readable",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "int",
+                "raw": "int",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "kind": "interface",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "I",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`interface property hooks getter 1`] = `
+Program {
+  "children": [
+    Interface {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": null,
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "get",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "readable",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "int",
+                "raw": "int",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "kind": "interface",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "I",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
+
+exports[`interface property hooks setter 1`] = `
+Program {
+  "children": [
+    Interface {
+      "attrGroups": [],
+      "body": [
+        PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
+          "isStatic": false,
+          "kind": "propertystatement",
+          "properties": [
+            Property {
+              "attrGroups": [],
+              "hooks": [
+                PropertyHook {
+                  "body": null,
+                  "byref": false,
+                  "isFinal": false,
+                  "kind": "propertyhook",
+                  "name": "set",
+                  "parameter": null,
+                },
+              ],
+              "kind": "property",
+              "name": Identifier {
+                "kind": "identifier",
+                "name": "readable",
+              },
+              "nullable": false,
+              "readonly": false,
+              "type": TypeReference {
+                "kind": "typereference",
+                "name": "int",
+                "raw": "int",
+              },
+              "value": null,
+            },
+          ],
+          "visibility": "public",
+        },
+      ],
+      "extends": null,
+      "kind": "interface",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "I",
+      },
+    },
+  ],
+  "errors": [],
+  "kind": "program",
+}
+`;
diff --git a/test/snapshot/__snapshots__/namespace.test.js.snap b/test/snapshot/__snapshots__/namespace.test.js.snap
index c59e4055c..ead6f0d16 100644
--- a/test/snapshot/__snapshots__/namespace.test.js.snap
+++ b/test/snapshot/__snapshots__/namespace.test.js.snap
@@ -167,6 +167,8 @@ Program {
           "attrGroups": [],
           "body": [
             PropertyStatement {
+              "isAbstract": false,
+              "isFinal": false,
               "isStatic": false,
               "kind": "propertystatement",
               "loc": Location {
@@ -185,6 +187,7 @@ Program {
               "properties": [
                 Property {
                   "attrGroups": [],
+                  "hooks": [],
                   "kind": "property",
                   "loc": Location {
                     "end": Position {
@@ -704,6 +707,8 @@ Program {
           "attrGroups": [],
           "body": [
             PropertyStatement {
+              "isAbstract": false,
+              "isFinal": false,
               "isStatic": false,
               "kind": "propertystatement",
               "loc": Location {
@@ -722,6 +727,7 @@ Program {
               "properties": [
                 Property {
                   "attrGroups": [],
+                  "hooks": [],
                   "kind": "property",
                   "loc": Location {
                     "end": Position {
diff --git a/test/snapshot/__snapshots__/nowdoc.test.js.snap b/test/snapshot/__snapshots__/nowdoc.test.js.snap
index 73ee4fa13..51d7e5363 100644
--- a/test/snapshot/__snapshots__/nowdoc.test.js.snap
+++ b/test/snapshot/__snapshots__/nowdoc.test.js.snap
@@ -223,11 +223,14 @@ FOOBAR",
           "visibility": "",
         },
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
diff --git a/test/snapshot/__snapshots__/property.test.js.snap b/test/snapshot/__snapshots__/property.test.js.snap
index 59f1edd5f..398f41fa4 100644
--- a/test/snapshot/__snapshots__/property.test.js.snap
+++ b/test/snapshot/__snapshots__/property.test.js.snap
@@ -7,11 +7,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -51,11 +54,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -98,11 +104,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -142,11 +151,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -189,11 +201,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -233,11 +248,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": true,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -277,11 +295,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": true,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -324,11 +345,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -371,11 +395,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -415,11 +442,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -462,11 +492,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -533,11 +566,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -594,11 +630,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -649,11 +688,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -697,11 +739,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -745,11 +790,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -795,11 +843,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -861,11 +912,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -912,11 +966,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -983,11 +1040,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -1033,11 +1093,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -1080,11 +1143,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
diff --git a/test/snapshot/__snapshots__/propertystatement.test.js.snap b/test/snapshot/__snapshots__/propertystatement.test.js.snap
index 3e3d14d9c..aa5dbba4d 100644
--- a/test/snapshot/__snapshots__/propertystatement.test.js.snap
+++ b/test/snapshot/__snapshots__/propertystatement.test.js.snap
@@ -7,11 +7,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -24,6 +27,7 @@ Program {
             },
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -36,6 +40,7 @@ Program {
             },
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -75,11 +80,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -92,6 +100,7 @@ Program {
             },
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -104,6 +113,7 @@ Program {
             },
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -143,11 +153,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
@@ -187,11 +200,14 @@ Program {
       "attrGroups": [],
       "body": [
         PropertyStatement {
+          "isAbstract": false,
+          "isFinal": false,
           "isStatic": false,
           "kind": "propertystatement",
           "properties": [
             Property {
               "attrGroups": [],
+              "hooks": [],
               "kind": "property",
               "name": Identifier {
                 "kind": "identifier",
diff --git a/test/snapshot/class.test.js b/test/snapshot/class.test.js
index b93d642cb..ba597ba52 100644
--- a/test/snapshot/class.test.js
+++ b/test/snapshot/class.test.js
@@ -182,19 +182,19 @@ describe("Test classes", function () {
   });
 
   it("Test that readonly method parameters are throwing errors", function () {
-    const ast = parser.parseEval(
-      `
+    expect(() => {
+      parser.parseEval(
+        `
       class Bob {
         public function foo(public readonly int $id) {}
       }`,
-      {
-        parser: {
-          version: "8.1",
-          suppressErrors: true,
+        {
+          parser: {
+            version: "8.1",
+          },
         },
-      },
-    );
-    expect(ast).toMatchSnapshot();
+      );
+    }).toThrowErrorMatchingSnapshot();
   });
 
   it("Test promoted nullable properties php 8", function () {
diff --git a/test/snapshot/classpropertyhooks.test.js b/test/snapshot/classpropertyhooks.test.js
new file mode 100644
index 000000000..36384874c
--- /dev/null
+++ b/test/snapshot/classpropertyhooks.test.js
@@ -0,0 +1,254 @@
+const parser = require("../main");
+//
+describe("classpropertyhooks", () => {
+  const test_parser = parser.create({
+    parser: {
+      version: "8.4",
+    },
+  });
+
+  it("not supported in php < 8.4", () => {
+    expect(() => {
+      parser.parseEval(
+        `class BookViewModel {
+            public string $credits {
+              get => 'mailto:' . $this->email;
+            }
+        }`,
+        {
+          parser: {
+            version: "8.3",
+          },
+        },
+      );
+    }).toThrowErrorMatchingSnapshot();
+  });
+
+  describe("getter", () => {
+    it("arrow function", () => {
+      expect(
+        test_parser.parseEval(
+          `class BookViewModel {
+            public string $credits {
+              get => 'mailto:' . $this->email;
+            }
+        }`,
+        ),
+      ).toMatchSnapshot();
+    });
+
+    it("block", () => {
+      expect(
+        test_parser.parseEval(
+          `class BookViewModel {
+          public string $credits {
+            get {
+              'mailto:' . $this->email;
+            }
+          }
+        }`,
+        ),
+      ).toMatchSnapshot();
+    });
+  });
+
+  describe("setter", () => {
+    describe("expression form", () => {
+      it("with implicit $value", () => {
+        expect(
+          test_parser.parseEval(
+            `class BookViewModel {
+          public string $credits {
+            set => $this->credits = $value;
+          }
+        }`,
+          ),
+        ).toMatchSnapshot();
+      });
+
+      it("with explicit untyped $value", () => {
+        expect(
+          test_parser.parseEval(
+            `class Person {
+            public $name {
+              set($new_name) => $this->name = strtolower($new_name);
+            }
+          }`,
+          ),
+        ).toMatchSnapshot();
+      });
+
+      it("with explicit typed $value", () => {
+        expect(
+          test_parser.parseEval(
+            `class Person {
+            public string $name {
+              set(string $new_name) => $this->name = strtolower($new_name);
+            }
+          }`,
+          ),
+        ).toMatchSnapshot();
+      });
+    });
+
+    describe("block form", () => {
+      it("with implicit $value", () => {
+        expect(
+          test_parser.parseEval(
+            `class BookViewModel {
+          public string $credits {
+            set {
+                $tmp = $this->credits + 1;
+              $this->propertyName = $value + $tmp;
+            }
+          }
+        }`,
+          ),
+        ).toMatchSnapshot();
+      });
+
+      it("with explicit untyped $value", () => {
+        expect(
+          test_parser.parseEval(
+            `class BookViewModel {
+          public $credits {
+            set ($value) {
+                $tmp = $this->credits + 1;
+              $this->propertyName = $value + $tmp;
+            }
+          }
+        }`,
+          ),
+        ).toMatchSnapshot();
+      });
+
+      it("with explicit typed $value", () => {
+        expect(
+          test_parser.parseEval(
+            `class BookViewModel {
+          public int $credits {
+            set (int $value) {
+                $tmp = $this->credits + 1;
+              $this->propertyName = $value + $tmp;
+            }
+          }
+        }`,
+          ),
+        ).toMatchSnapshot();
+      });
+    });
+  });
+
+  it("support default value", () => {
+    expect(
+      test_parser.parseEval(
+        `class Example {
+    public string $foo = 'default value' {
+        get => $this->foo ;
+    }
+}`,
+      ),
+    ).toMatchSnapshot();
+  });
+
+  [
+    `class Example {
+    private bool $modified = false;
+    public string $foo = 'default value' {
+        get => $this->foo . ($this->modified ? ' (modified)' : '');
+        set(string $value) {
+            $this->foo = strtolower($value);
+            $this->modified = true;
+        }
+    }
+}`,
+    `class Example
+{
+    private bool $modified = false;
+
+    public string $foo = 'default value' {
+        get => $this->foo . ($this->modified ? ' (modified)' : '');
+
+        set {
+            $this->foo = strtolower($value);
+            $this->modified = true;
+        }
+    }
+}`,
+    `class Example
+{
+    public string $foo = 'default value' {
+        get => $this->foo . ($this->modified ? ' (modified)' : '');
+        set => strtolower($value);
+    }
+}`,
+  ].forEach((code) => {
+    it("support setter + getter", () => {
+      expect(test_parser.parseEval(code)).toMatchSnapshot();
+    });
+  });
+
+  it("can be access by reference", () => {
+    expect(
+      test_parser.parseEval(`class Foo
+{
+    private string $_baz;
+
+    public string $baz {
+        &get => $this->_baz;
+        set {
+            $this->_baz = strtoupper($value);
+        }
+    }
+}`),
+    ).toMatchSnapshot();
+  });
+
+  describe("final", () => {
+    it("on the hook itself", () => {
+      expect(
+        test_parser.parseEval(`class StandardUser
+{
+    public string $email {
+        final set {
+           if (! filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE)) {
+               throw new InvalidArgumentException('Invalid email');
+           }
+           $this->email = $value;
+        }
+    }
+}`),
+      ).toMatchSnapshot();
+    });
+
+    it("on the property", () => {
+      const code = `class User {
+    // Child classes may not add hooks of any kind to this property.
+    public final string $name;
+ 
+    // Child classes may not add any hooks or override set,
+    // but this set will still apply.
+    public final string $username {
+        set => strtolower($value);
+    }
+}`;
+      expect(test_parser.parseEval(code)).toMatchSnapshot();
+    });
+  });
+
+  describe("abstract", () => {
+    [
+      ["get", `abstract class User { abstract public string $email { get; } }`],
+      ["set", `abstract class User { abstract public string $email { set; } }`],
+      [
+        "get + set",
+        `abstract class User { abstract public string $email { get; set; } }`,
+      ],
+    ].forEach(([name, code]) => {
+      // eslint-disable-next-line jest/valid-title
+      it(name, () => {
+        expect(test_parser.parseEval(code)).toMatchSnapshot();
+      });
+    });
+  });
+});
diff --git a/test/snapshot/interface.test.js b/test/snapshot/interface.test.js
index 0c52595b4..2ebb2d240 100644
--- a/test/snapshot/interface.test.js
+++ b/test/snapshot/interface.test.js
@@ -17,4 +17,39 @@ describe("interface", function () {
       }),
     ).toMatchSnapshot();
   });
+
+  describe("property hooks", function () {
+    const test_parser = parser.create({
+      parser: {
+        version: "8.4",
+      },
+    });
+
+    it("getter", () => {
+      const code = `interface I {
+    // An implementing class MUST have a publicly-readable property,
+    // but whether or not it's publicly settable is unrestricted.
+    public int $readable { get; }
+}`;
+      expect(test_parser.parseEval(code)).toMatchSnapshot();
+    });
+
+    it("setter", () => {
+      const code = `interface I {
+    // An implementing class MUST have a publicly-readable property,
+    // but whether or not it's publicly settable is unrestricted.
+    public int $readable { set; }
+}`;
+      expect(test_parser.parseEval(code)).toMatchSnapshot();
+    });
+
+    it("get + set", () => {
+      const code = `interface I {
+    // An implementing class MUST have a publicly-readable property,
+    // but whether or not it's publicly settable is unrestricted.
+    public int $readable { get; set;}
+}`;
+      expect(test_parser.parseEval(code)).toMatchSnapshot();
+    });
+  });
 });
diff --git a/types.d.ts b/types.d.ts
index a5ad9c93f..bc91586d9 100644
--- a/types.d.ts
+++ b/types.d.ts
@@ -777,6 +777,16 @@ declare module "php-parser" {
     type: Identifier | Identifier[] | null;
     attrGroups: AttrGroup[];
   }
+  /**
+   * Defines a class property hook getter & setter
+   */
+  class PropertyHook extends Node {
+    name: string;
+    isFinal: boolean;
+    byref: boolean;
+    parameter: Parameter | null;
+    body: Block | Statement;
+  }
   /**
    * Lookup to an object property
    */