Skip to content
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
46 changes: 46 additions & 0 deletions Zend/tests/friends/friend_basic_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
A class can declare another class as a friend so that it may access protected and private properties
--FILE--
<?php

class Person
{
friend PersonFormatter;

private $firstName;
private $lastName;

public function __construct($firstName, $lastName)
{
$this->firstName = $firstName;
$this->lastName = $lastName;
}

public function format()
{
return new PersonFormatter($this);
}
}

class PersonFormatter
{
private $person;

public function __construct(Person $person)
{
$this->person = $person;
}

public function getFullName()
{
return $this->person->firstName . ' ' . $this->person->lastName;
}
}

$person = new Person('Alice', 'Wonderland');
$formatter = $person->format();

var_dump($formatter->getFullName());
?>
--EXPECTF--
string(%d) "Alice Wonderland"
44 changes: 44 additions & 0 deletions Zend/tests/friends/friend_basic_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
A class can declare multiple classes as friend
--FILE--
<?php

class PartyHost
{
friend PartyGoer, Bouncer;

private $name;

public function __construct($name)
{
$this->name = $name;
}
}

class PartyGoer
{
public function greet(PartyHost $person)
{
echo "Your name was {$person->name}, right? Nice party!" . PHP_EOL;
}
}

class Bouncer
{
public function greet(PartyHost $person)
{
echo "Hey {$person->name}! If you need backup, let me know." . PHP_EOL;
}
}

$host = new PartyHost('Dustin');
$attendee = new PartyGoer();
$bouncer = new Bouncer();

$attendee->greet($host);
$bouncer->greet($host);

?>
--EXPECT--
Your name was Dustin, right? Nice party!
Hey Dustin! If you need backup, let me know.
41 changes: 41 additions & 0 deletions Zend/tests/friends/friend_inheritance_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
A friend of class Base is not automatically a friend of class Derived and vice-versa
--FILE--
<?php

class Base
{
friend Friendly;

protected $property = 'base';
}

class Derived extends Base
{
protected $property = 'derived';
}

class Friendly
{
public function touch(Base $base)
{
echo $base->property . PHP_EOL;
}
}

$base = new Base();
$derived = new Derived();
$friend = new Friendly();

$friend->touch($base);
$friend->touch($derived);

?>
--EXPECTF--
base

Fatal error: Uncaught Error: Cannot access protected property Derived::$property in %s:%d
Stack trace:
#0 %s(%d): Friendly->touch(Object(Derived))
#1 {main}
thrown in %s on line %d
42 changes: 42 additions & 0 deletions Zend/tests/friends/friend_inheritance_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
Friendship is not transitive. If Base is a friend of another class, Derived is not automatically a friend.
--FILE--
<?php

class Subject
{
friend Base;

protected $property = 'subject';
}

class Base
{
protected $friend;

public function __construct(Subject $subject)
{
$this->friend = $subject;
}
}

class Derived extends Base
{
public function touch()
{
echo $this->friend->property . PHP_EOL;
}
}

$subject = new Subject();
$derived = new Derived($subject);

$derived->touch();

?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot access protected property Subject::$property in %s:%d
Stack trace:
#0 %s(%d): Derived->touch()
#1 {main}
thrown in %s on line %d
49 changes: 49 additions & 0 deletions Zend/tests/friends/friend_inheritance_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--TEST--
Access due to friendship is inherited
--FILE--
<?php

class Base
{
private $secret = 'secret';
protected $override = 'override';
}

class Derived extends Base
{
friend Friendly;

protected $derived = 'derived';

public function touch()
{
echo $this->secret . PHP_EOL;
}
}

class Friendly
{
public function touch(Derived $derived)
{
echo $derived->derived . PHP_EOL; // Allowed via normal friend functionality
echo $derived->override . PHP_EOL; // Allowed because inherited and accessible by Derived

echo $derived->secret . PHP_EOL; // Dis-allowed because not accessible by Derived
// In this case, it will behave just as if the derived itself
// attempted to access the private property from base
}
}

$derived = new Derived();

$derived->touch();
(new Friendly())->touch($derived);

?>
--EXPECTF--
Notice: Undefined property: Derived::$secret in %s on line %d

derived
override

Notice: Undefined property: Derived::$secret in %s on line %d
2 changes: 2 additions & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,13 @@ struct _zend_class_entry {

uint32_t num_interfaces;
uint32_t num_traits;
uint32_t num_friends;
zend_class_entry **interfaces;

zend_class_entry **traits;
zend_trait_alias **trait_aliases;
zend_trait_precedence **trait_precedences;
zend_class_entry **friends;

union {
struct {
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ enum _zend_ast_kind {
ZEND_AST_POST_INC,
ZEND_AST_POST_DEC,
ZEND_AST_YIELD_FROM,
ZEND_AST_FRIEND,

ZEND_AST_GLOBAL,
ZEND_AST_UNSET,
Expand Down
31 changes: 31 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,7 @@ void zend_do_early_binding(void) /* {{{ */
case ZEND_VERIFY_ABSTRACT_CLASS:
case ZEND_ADD_INTERFACE:
case ZEND_ADD_TRAIT:
case ZEND_ADD_FRIEND:
case ZEND_BIND_TRAITS:
/* We currently don't early-bind classes that implement interfaces */
/* Classes with traits are handled exactly the same, no early-bind here */
Expand Down Expand Up @@ -1758,6 +1759,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify
ce->traits = NULL;
ce->trait_aliases = NULL;
ce->trait_precedences = NULL;
ce->num_friends = 0;
ce->friends = NULL;
ce->serialize = NULL;
ce->unserialize = NULL;
ce->serialize_func = NULL;
Expand Down Expand Up @@ -6251,6 +6254,26 @@ void zend_compile_use_trait(zend_ast *ast) /* {{{ */
}
/* }}} */

void zend_compile_friend(zend_ast *ast) /* {{{ */
{
zend_ast_list *friends = zend_ast_get_list(ast->child[0]);
zend_class_entry *ce = CG(active_class_entry);
zend_op *opline;
uint32_t i;

for (i = 0; i < friends->children; ++i) {
zend_ast *friend_ast = friends->child[i];

opline = zend_emit_op(NULL, ZEND_ADD_FRIEND, &FC(implementing_class), NULL);
opline->op2_type = IS_CONST;
opline->op2.constant = zend_add_class_name_literal(CG(active_op_array),
zend_resolve_class_name_ast(friend_ast));

ce->num_friends++;
}
}
/* }}} */

void zend_compile_implements(znode *class_node, zend_ast *ast) /* {{{ */
{
zend_ast_list *list = zend_ast_get_list(ast);
Expand Down Expand Up @@ -6462,6 +6485,11 @@ void zend_compile_class_decl(zend_ast *ast) /* {{{ */
zend_emit_op(NULL, ZEND_BIND_TRAITS, &declare_node, NULL);
}

if (ce->num_friends > 0) {
ce->friends = NULL;
ce->num_friends = 0;
}

if (implements_ast) {
zend_compile_implements(&declare_node, implements_ast);
}
Expand Down Expand Up @@ -8199,6 +8227,9 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */
case ZEND_AST_USE_TRAIT:
zend_compile_use_trait(ast);
break;
case ZEND_AST_FRIEND:
zend_compile_friend(ast);
break;
case ZEND_AST_CLASS:
zend_compile_class_decl(ast);
break;
Expand Down
26 changes: 26 additions & 0 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,32 @@ ZEND_API void zend_do_implement_trait(zend_class_entry *ce, zend_class_entry *tr
}
/* }}} */

ZEND_API void zend_do_implement_friend(zend_class_entry *ce, zend_class_entry *friend) /* {{{ */
{
uint32_t i, ignore = 0;
uint32_t current_friend_num = ce->num_friends;
uint32_t parent_friend_num = ce->parent ? ce->parent->num_friends : 0;

for (i = 0; i < ce->num_friends; i++) {
if (ce->friends[i] == NULL) {
memmove(ce->friends + i, ce->friends + i + 1, sizeof(zend_class_entry*) * (--ce->num_friends - i));
i--;
} else if (ce->friends[i] == friend) {
if (i < parent_friend_num) {
ignore = 1;
}
}
}

if (!ignore) {
if (ce->num_friends >= current_friend_num) {
ce->friends = (zend_class_entry **) erealloc(ce->friends, sizeof(zend_class_entry *) * (++current_friend_num));
}
ce->friends[ce->num_friends++] = friend;
}
}
/* }}} */

static zend_bool zend_traits_method_compatibility_check(zend_function *fn, zend_function *other_fn) /* {{{ */
{
uint32_t fn_flags = fn->common.scope->ce_flags;
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_inheritance.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry
ZEND_API void zend_do_implement_trait(zend_class_entry *ce, zend_class_entry *trait);
ZEND_API void zend_do_bind_traits(zend_class_entry *ce);

ZEND_API void zend_do_implement_friend(zend_class_entry *ce, zend_class_entry *friend);

ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent_ce);
void zend_do_early_binding(void);

Expand Down
5 changes: 4 additions & 1 deletion Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token T_FINALLY "finally (T_FINALLY)"
%token T_THROW "throw (T_THROW)"
%token T_USE "use (T_USE)"
%token T_FRIEND "friend (T_FRIEND)"
%token T_INSTEADOF "insteadof (T_INSTEADOF)"
%token T_GLOBAL "global (T_GLOBAL)"
%token T_STATIC "static (T_STATIC)"
Expand Down Expand Up @@ -271,7 +272,7 @@ reserved_non_modifiers:
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE | T_ENDWHILE
| T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH | T_FINALLY
| T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_THROW | T_USE | T_FRIEND | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK
| T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C
Expand Down Expand Up @@ -724,6 +725,8 @@ class_statement:
{ $$ = $3; $$->attr = $1; }
| T_USE name_list trait_adaptations
{ $$ = zend_ast_create(ZEND_AST_USE_TRAIT, $2, $3); }
| T_FRIEND name_list ';'
{ $$ = zend_ast_create(ZEND_AST_FRIEND, $2); }
| method_modifiers function returns_ref identifier backup_doc_comment '(' parameter_list ')'
return_type backup_fn_flags method_body backup_fn_flags
{ $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1 | $12, $2, $5,
Expand Down
Loading