Skip to content

Commit d933591

Browse files
committed
Add support for $obj::class
This allows $obj::class, which gives the same result as get_class($obj). Anything other than an object results in TypeError. RFC: https://wiki.php.net/rfc/class_name_literal_on_object Closes GH-5065.
1 parent 69819ba commit d933591

11 files changed

+940
-701
lines changed

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,9 @@ PHP 8.0 UPGRADE NOTES
367367
class B extends A {
368368
public function method(...$everything) {}
369369
}
370+
. It is now possible to fetch the class name of an object using
371+
`$object::class`. The result is the same as `get_class($object)`.
372+
RFC: https://wiki.php.net/rfc/class_name_literal_on_object
370373

371374

372375
- Date:

Zend/tests/class_name_of_var.phpt

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
An error should be generated when using ::class on a constant evaluated expression
3+
--FILE--
4+
<?php
5+
6+
(1+1)::class;
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot use ::class on value of type int in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
::class on an expression cannot be used inside constant expressions
3+
--FILE--
4+
<?php
5+
6+
const A = [0]::class;
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: (expression)::class cannot be used in constant expressions in %s on line %d

Zend/tests/class_on_object.phpt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Using ::class on an object
3+
--FILE--
4+
<?php
5+
6+
$obj = new stdClass;
7+
var_dump($obj::class);
8+
$ref =& $obj;
9+
var_dump($ref::class);
10+
var_dump((new stdClass)::class);
11+
12+
// Placed in a function to check that opcache doesn't perform incorrect constprop.
13+
function test() {
14+
$other = null;
15+
var_dump($other::class);
16+
}
17+
try {
18+
test();
19+
} catch (TypeError $e) {
20+
echo $e->getMessage(), "\n";
21+
}
22+
23+
?>
24+
--EXPECT--
25+
string(8) "stdClass"
26+
string(8) "stdClass"
27+
string(8) "stdClass"
28+
Cannot use ::class on value of type null

Zend/zend_compile.c

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,7 +1551,7 @@ static zend_bool zend_try_compile_const_expr_resolve_class_name(zval *zv, zend_a
15511551
zval *class_name;
15521552

15531553
if (class_ast->kind != ZEND_AST_ZVAL) {
1554-
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use ::class with dynamic class name");
1554+
return 0;
15551555
}
15561556

15571557
class_name = zend_ast_get_zval(class_ast);
@@ -8246,15 +8246,27 @@ void zend_compile_class_const(znode *result, zend_ast *ast) /* {{{ */
82468246
void zend_compile_class_name(znode *result, zend_ast *ast) /* {{{ */
82478247
{
82488248
zend_ast *class_ast = ast->child[0];
8249-
zend_op *opline;
82508249

82518250
if (zend_try_compile_const_expr_resolve_class_name(&result->u.constant, class_ast)) {
82528251
result->op_type = IS_CONST;
82538252
return;
82548253
}
82558254

8256-
opline = zend_emit_op_tmp(result, ZEND_FETCH_CLASS_NAME, NULL, NULL);
8257-
opline->op1.num = zend_get_class_fetch_type(zend_ast_get_str(class_ast));
8255+
if (class_ast->kind == ZEND_AST_ZVAL) {
8256+
zend_op *opline = zend_emit_op_tmp(result, ZEND_FETCH_CLASS_NAME, NULL, NULL);
8257+
opline->op1.num = zend_get_class_fetch_type(zend_ast_get_str(class_ast));
8258+
} else {
8259+
znode expr_node;
8260+
zend_compile_expr(&expr_node, class_ast);
8261+
if (expr_node.op_type == IS_CONST) {
8262+
/* Unlikely case that happen if class_ast is constant folded.
8263+
* Handle it here, to avoid needing a CONST specialization in the VM. */
8264+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use ::class on value of type %s",
8265+
zend_get_type_by_const(Z_TYPE(expr_node.u.constant)));
8266+
}
8267+
8268+
zend_emit_op_tmp(result, ZEND_FETCH_CLASS_NAME, &expr_node, NULL);
8269+
}
82588270
}
82598271
/* }}} */
82608272

@@ -8491,6 +8503,11 @@ void zend_compile_const_expr_class_name(zend_ast **ast_ptr) /* {{{ */
84918503
{
84928504
zend_ast *ast = *ast_ptr;
84938505
zend_ast *class_ast = ast->child[0];
8506+
if (class_ast->kind != ZEND_AST_ZVAL) {
8507+
zend_error_noreturn(E_COMPILE_ERROR,
8508+
"(expression)::class cannot be used in constant expressions");
8509+
}
8510+
84948511
zend_string *class_name = zend_ast_get_str(class_ast);
84958512
uint32_t fetch_type = zend_get_class_fetch_type(class_name);
84968513

Zend/zend_vm_def.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7992,14 +7992,32 @@ ZEND_VM_HANDLER(151, ZEND_ASSERT_CHECK, ANY, JMP_ADDR)
79927992
}
79937993
}
79947994

7995-
ZEND_VM_HANDLER(157, ZEND_FETCH_CLASS_NAME, UNUSED|CLASS_FETCH, ANY)
7995+
ZEND_VM_HANDLER(157, ZEND_FETCH_CLASS_NAME, CV|TMPVAR|UNUSED|CLASS_FETCH, ANY)
79967996
{
79977997
uint32_t fetch_type;
79987998
zend_class_entry *called_scope, *scope;
79997999
USE_OPLINE
80008000

8001-
fetch_type = opline->op1.num;
8001+
if (OP1_TYPE != IS_UNUSED) {
8002+
zval *op = GET_OP1_ZVAL_PTR(BP_VAR_R);
8003+
SAVE_OPLINE();
8004+
if (UNEXPECTED(Z_TYPE_P(op) != IS_OBJECT)) {
8005+
ZVAL_DEREF(op);
8006+
if (Z_TYPE_P(op) != IS_OBJECT) {
8007+
zend_type_error("Cannot use ::class on value of type %s",
8008+
zend_get_type_by_const(Z_TYPE_P(op)));
8009+
ZVAL_UNDEF(EX_VAR(opline->result.var));
8010+
FREE_OP1();
8011+
HANDLE_EXCEPTION();
8012+
}
8013+
}
80028014

8015+
ZVAL_STR_COPY(EX_VAR(opline->result.var), Z_OBJCE_P(op)->name);
8016+
FREE_OP1();
8017+
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
8018+
}
8019+
8020+
fetch_type = opline->op1.num;
80038021
scope = EX(func)->op_array.scope;
80048022
if (UNEXPECTED(scope == NULL)) {
80058023
SAVE_OPLINE();

0 commit comments

Comments
 (0)