Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for "decorator" pattern #5168

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions Zend/tests/decorators/basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
--TEST--
Basic decorator test
--FILE--
<?php

interface Component {
public function getName(): string;
public function getValue1(): int;
public function getValue2(string $prefix): string;
}

class ConcreteBaseComponent implements Component {
public function getName(): string {
return "ConcreteBaseComponent";
}
public function getValue1(): int {
return 42;
}
public function getValue2(string $prefix): string {
return $prefix . "foo";
}
}

class DecoratedComponent {
private decorated Component $component;

public function __construct(Component $component) {
$this->component = $component;
}
public function getName(): string {
return "DecoratedComponent({$this->component->getName()})";
}
public function getValue1(): int {
return $this->component->getValue1() + 1;
}
}

class ExtendDecoratedComponent extends DecoratedComponent {
public function getValue1(): int {
return parent::getValue1() + 1;
}
public function getValue2(string $prefix): string {
return parent::getValue2($prefix) . "bar";
}
}

$baseComponent = new ConcreteBaseComponent;
var_dump($baseComponent->getName());
var_dump($baseComponent->getValue1());
var_dump($baseComponent->getValue2("prefix: "));
$decorated = new DecoratedComponent($baseComponent);
var_dump($decorated->getName());
var_dump($decorated->getValue1());
var_dump($decorated->getValue2("prefix: "));
$decorated = new ExtendDecoratedComponent($baseComponent);
var_dump($decorated->getName());
var_dump($decorated->getValue1());
var_dump($decorated->getValue2("prefix: "));

?>
--EXPECT--
string(21) "ConcreteBaseComponent"
int(42)
string(11) "prefix: foo"
string(41) "DecoratedComponent(ConcreteBaseComponent)"
int(43)
string(11) "prefix: foo"
string(41) "DecoratedComponent(ConcreteBaseComponent)"
int(44)
string(14) "prefix: foobar"
12 changes: 12 additions & 0 deletions Zend/tests/decorators/decorated_incorrect_type.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Decorated property illegal type
--FILE--
<?php

class Test {
public decorated ?Foo $prop;
}

?>
--EXPECTF--
Fatal error: Decorator property must have exactly one class type in %s on line %d
13 changes: 13 additions & 0 deletions Zend/tests/decorators/decorated_multiple_prop.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Cannot use "decorated" on multiple properties
--FILE--
<?php

class Test {
public decorated Foo $foo;
public decorated Bar $bar;
}

?>
--EXPECTF--
Fatal error: Cannot declare multiple properties as "decorated" in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/decorators/decorated_no_type.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Decorated property without type
--FILE--
<?php

class Test {
public decorated $prop;
}

?>
--EXPECTF--
Fatal error: Decorator property must have exactly one class type in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/decorators/decorated_prop_group.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Cannot use "decorated" with property group
--FILE--
<?php

class Test {
public decorated Foo $bar, $baz;
}

?>
--EXPECTF--
Fatal error: Cannot declare multiple properties as "decorated" in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/decorators/decorated_prop_nonexistent_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Decorated property references non-existing class
--FILE--
<?php

class Test {
public decorated DoesntExist $prop;
}

?>
--EXPECTF--
Fatal error: Class 'DoesntExist' not found in %s on line %d
20 changes: 20 additions & 0 deletions Zend/tests/decorators/decorated_prop_not_set.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
The decorated proprety has not been set
--FILE--
<?php

class DecoratedIterator implements Iterator {
// Oops, not initialized
public decorated Iterator $it;
}

$iter = new DecoratedIterator;
try {
var_dump($iter->valid());
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
Typed property DecoratedIterator::$it must not be accessed before initialization
8 changes: 8 additions & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -555,9 +555,17 @@ static void zend_init_exception_op(void) /* {{{ */

static void zend_init_call_trampoline_op(void) /* {{{ */
{
zend_op *decorated_ops = EG(call_decorated_ops);

memset(&EG(call_trampoline_op), 0, sizeof(EG(call_trampoline_op)));
EG(call_trampoline_op).opcode = ZEND_CALL_TRAMPOLINE;
ZEND_VM_SET_OPCODE_HANDLER(&EG(call_trampoline_op));

memset(decorated_ops, 0, sizeof(EG(call_decorated_ops)));
decorated_ops[0].opcode = ZEND_CALL_DECORATED;
ZEND_VM_SET_OPCODE_HANDLER(&decorated_ops[0]);
decorated_ops[1].opcode = ZEND_RETURN_DECORATED;
ZEND_VM_SET_OPCODE_HANDLER(&decorated_ops[1]);
}
/* }}} */

Expand Down
2 changes: 2 additions & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ struct _zend_class_entry {
zend_trait_alias **trait_aliases;
zend_trait_precedence **trait_precedences;

struct _zend_property_info *decorated_prop;

union {
struct {
zend_string *filename;
Expand Down
7 changes: 3 additions & 4 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -3476,7 +3476,7 @@ static zend_always_inline zend_bool is_persistent_class(zend_class_entry *ce) {
&& ce->info.internal.module->type == MODULE_PERSISTENT;
}

ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type) /* {{{ */
ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type) /* {{{ */
{
zend_property_info *property_info, *property_info_ptr;

Expand Down Expand Up @@ -3579,8 +3579,7 @@ ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name
property_info->type = type;

zend_hash_update_ptr(&ce->properties_info, name, property_info);

return SUCCESS;
return property_info;
}
/* }}} */

Expand Down Expand Up @@ -3713,7 +3712,7 @@ ZEND_API int zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval *zv, ze

ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment) /* {{{ */
{
return zend_declare_typed_property(ce, name, property, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0));
return zend_declare_typed_property(ce, name, property, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0)) ? SUCCESS : FAILURE;
}
/* }}} */

Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ ZEND_API zend_bool zend_make_callable(zval *callable, zend_string **callable_nam
ZEND_API const char *zend_get_module_version(const char *module_name);
ZEND_API int zend_get_module_started(const char *module_name);

ZEND_API int zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type);
ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type);

ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment);
ZEND_API int zend_declare_property(zend_class_entry *ce, const char *name, size_t name_length, zval *property, int access_type);
Expand Down
21 changes: 18 additions & 3 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify
ce->serialize_func = NULL;
ce->unserialize_func = NULL;
ce->__debugInfo = NULL;
ce->decorated_prop = NULL;
if (ce->type == ZEND_INTERNAL_CLASS) {
ce->info.internal.module = NULL;
ce->info.internal.builtin_functions = NULL;
Expand Down Expand Up @@ -6375,7 +6376,21 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) /
ZVAL_UNDEF(&value_zv);
}

zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type);
zend_property_info *prop_info =
zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type);

if (flags & ZEND_ACC_DECORATED) {
if (ce->decorated_prop) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot declare multiple properties as \"decorated\"");
}
ce->decorated_prop = prop_info;

if (!ZEND_TYPE_HAS_NAME(type) || ZEND_TYPE_PURE_MASK(type) != 0) {
zend_error_noreturn(E_COMPILE_ERROR,
"Decorator property must have exactly one class type");
}
}
}
}
/* }}} */
Expand Down Expand Up @@ -6690,7 +6705,7 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */

if (toplevel
/* We currently don't early-bind classes that implement interfaces or use traits */
&& !ce->num_interfaces && !ce->num_traits
&& !ce->num_interfaces && !ce->num_traits && !ce->decorated_prop
&& !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
if (extends_ast) {
zend_class_entry *parent_ce = zend_lookup_class_ex(
Expand Down Expand Up @@ -6751,7 +6766,7 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
if (extends_ast && toplevel
&& (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING)
/* We currently don't early-bind classes that implement interfaces or use traits */
&& !ce->num_interfaces && !ce->num_traits
&& !ce->num_interfaces && !ce->num_traits && !ce->decorated_prop
) {
CG(active_op_array)->fn_flags |= ZEND_ACC_EARLY_BINDING;
opline->opcode = ZEND_DECLARE_CLASS_DELAYED;
Expand Down
6 changes: 6 additions & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ typedef struct _zend_oparray_context {
/* op_array or class is preloaded | | | */
#define ZEND_ACC_PRELOADED (1 << 10) /* X | X | | */
/* | | | */
/* Property Flags (unused: 8...) | | | */
/* =========== | | | */
/* | | | */
/* Decorated property | | | */
#define ZEND_ACC_DECORATED (1 << 7) /* | | X | */
/* | | | */
/* Class Flags (unused: 13, 14, 15, 24...) | | | */
/* =========== | | | */
/* | | | */
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ struct _zend_executor_globals {

zend_function trampoline;
zend_op call_trampoline_op;
zend_op call_decorated_ops[2];

HashTable weakrefs;

Expand Down
78 changes: 77 additions & 1 deletion Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -2201,6 +2201,80 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */
}
/* }}} */

static zend_op_array *create_decorator_op_array(zend_class_entry *ce, zend_function *orig_func)
{
/* We use non-NULL value to avoid useless run_time_cache allocation.
* The low bit must be zero, to not be interpreted as a MAP_PTR offset. */
static const void *dummy = (void*)(intptr_t)2;

uint32_t preserve_flags = ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_HAS_RETURN_TYPE;

zend_op_array *func = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
memset(func, 0, sizeof(zend_op_array));

func->refcount = (uint32_t *) emalloc(sizeof(uint32_t));
*func->refcount = 1;

func->type = ZEND_USER_FUNCTION;
func->fn_flags = ZEND_ACC_PUBLIC;
func->fn_flags |= orig_func->common.fn_flags & preserve_flags;
func->function_name = zend_string_copy(orig_func->common.function_name);
func->scope = ce;
func->num_args = orig_func->common.num_args;
func->required_num_args = orig_func->common.required_num_args;
func->arg_info = orig_func->common.arg_info;

/* Place the method at position of the class declaration,
* as we don't have any more accurate location information. */
func->filename = ce->info.user.filename;
func->line_start = ce->info.user.line_start;
func->line_end = ce->info.user.line_start;

func->opcodes = EG(call_decorated_ops);
ZEND_MAP_PTR_INIT(func->run_time_cache, (void***)&dummy);

return func;
}

static void zend_decorate_class(zend_class_entry *ce)
{
zend_property_info *prop = ce->decorated_prop;

zend_class_entry *decorated_ce;
if (ZEND_TYPE_HAS_NAME(prop->type)) {
/* TODO: Allow exception here? */
zend_string *decorated_name = ZEND_TYPE_NAME(prop->type);
decorated_ce = zend_fetch_class_by_name(decorated_name, NULL, 0);
ZEND_TYPE_SET_CE(prop->type, decorated_ce);
zend_string_release(decorated_name);
} else if (ZEND_TYPE_HAS_CE(prop->type)) {
decorated_ce = ZEND_TYPE_CE(prop->type);
} else {
ZEND_ASSERT(0 && "Decorated property must have single class type");
}

zend_string *lcname;
zend_function *func;
ZEND_HASH_FOREACH_STR_KEY_PTR(&decorated_ce->function_table, lcname, func) {
/* Only the public interface of the decorated class is proxied. */
if (!(func->common.fn_flags & ZEND_ACC_PUBLIC)) {
continue;
}

/* Static methods are not proxied. */
if (func->common.fn_flags & ZEND_ACC_STATIC) {
continue;
}

/* The method was explicitly overridden in the class, skip. */
if (zend_hash_find(&ce->function_table, lcname)) {
continue;
}

zend_hash_add_new_ptr(&ce->function_table, lcname, create_decorator_op_array(ce, func));
} ZEND_HASH_FOREACH_END();
}

typedef struct {
enum {
OBLIGATION_DEPENDENCY,
Expand Down Expand Up @@ -2453,7 +2527,9 @@ ZEND_API int zend_do_link_class(zend_class_entry *ce, zend_string *lc_parent_nam
interfaces[num_parent_interfaces + i] = iface;
}
}

if (ce->decorated_prop) {
zend_decorate_class(ce);
}
if (parent) {
if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
add_dependency_obligation(ce, parent);
Expand Down
Loading