How to make a variable private to a trait?

谁都会走 提交于 2019-12-03 12:44:56

问题


I'd like to reuse a functionality several times in a single class. This functionality relies on a private variable:

trait Address {
    private $address;

    public function getAddress() {
        return $this->address;
    }

    public function setAddress($address) {
        $this->address = $address;
    }
}

The only way I've found to use the trait twice, is the following:

class User  {
    use Address {
        getAddress as getHomeAddress;
        setAddress as setHomeAddress;

        getAddress as getWorkAddress;            
        setAddress as setWorkAddress;
    }
}

The problem is, by doing this, the private variable $address is shared across the different methods, and the code will not work as expected:

$user = new User();
$user->setHomeAddress('21 Jump Street');
echo $user->getWorkAddress(); // 21 Jump Street

Is there a solution to really use the trait twice, while not sharing its private variables?


回答1:


Declaring a trait with use will not create an instance of that trait. Traits are basically just code that is copy and pasted into the using class. The as will only create an Alias for that method, e.g. it will add something like

public function getHomeAddress()
{
    return $this->getAddress();
}

to your User class. But it will still only be that one trait. There will not be two different $address properties, but just one.

You could make the methods private and then delegate any public calls to it via __call by switch/casing on the method name and using an array for address, e.g.

trait Address {
    private $address = array();

    private function getAddress($type) {
        return $this->address[$type];
    }

    private function setAddress($type, $address) {
        $this->address[$type] = $address;
    }

    public function __call($method, $args) {
        switch ($method) {
            case 'setHomeAddress':
                return $this->setAddress('home', $args[0]);
            // more cases …
        }
    }
}

But that is just a can of worms.

In other words, you cannot sanely do what you are trying to do with traits. Either use two different traits. Or use good old aggregation and add concrete proxy methods.




回答2:


i may be a little late for the party, but the behavior you are trying to create is not something that should be covered by a trait, but by simple object composition.

<?php
class Adddress {
    private $street;
    private $number;
    public function __construct(string $street, ?string $number) {}
    public function street() : string {}
    public function number() : string {}
}
class User {
    private $homeAddress;
    private $workAddress;
    public function getHomeAddress() : Address {}
    public function setHomeAddress(Address $homeAddress) : self {}
    public function getWorkAddress() : Address {}
    public function setWorkAddress(Address $workAddress) : self {}
}



回答3:


I confirmed that you can alias the same function multiple times, which was a surprise to me. Although ZendStudio appears to only 'code assist' on the last alias of the function.

Being able to alias the same function multiple times could lend itself to some interesting behavior if the Trait function can determine what name it was called as. But it does not appear that we can determined the 'aliased' function within a trait function.

Here's my test code:

<?php
trait TestTrait
{
    public function test() { print __CLASS__ . ', ' . __TRAIT__ . ', ' . __METHOD__ . ', ' . __FUNCTION__  . "\n"; }
}
class TestClass
{
    use TestTrait { test as test1; test as test2; }
}
$c = new TestClass();
$c->test1();
$c->test2();

Output:

TestClass, TestTrait, TestTrait::test, test
TestClass, TestTrait, TestTrait::test, test

Perhaps it would be nice to add a new __ALIAS__ constant for trait functions to determine what alias they were called as.

In fact I have created PHP feature request for this:

https://bugs.php.net/bug.php?id=63629




回答4:


Some years down the line, I followed up on a comment in bug 63629 and produced the following:

<?php
trait TestTrait
{
    private $addresses = [];
    public function getAddress() {
        $calledAs = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['function'];
        return $this->addresses[substr($calledAs, 3)];
    }

    public function setAddress($address) {
        $calledAs = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['function'];
        $this->addresses[substr($calledAs, 3)] = $address;
    }
}
class TestClass
{
    use TestTrait { getAddress as getHomeAddress; setAddress as setHomeAddress; }
    use TestTrait { getAddress as getWorkAddress; setAddress as setWorkAddress; }
}
$c = new TestClass();

$c->setHomeAddress("High Street, Luton");
echo $c->getHomeAddress();
echo "\n";
$c->setWorkAddress("Business Name, London");
echo $c->getWorkAddress();
echo "\n";

which outputs

High Street, Luton
Business Name, London

It can be done! (With thanks to Dave Farrell whose answer inspired this one.) The arguments to debug_backtrace are an attempt to minimize memory usage, I'm not certain how much of an effect on performance that is.



来源:https://stackoverflow.com/questions/13582747/how-to-make-a-variable-private-to-a-trait

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!