Как сделать переменную приватной для черты?

Я хотел бы повторно использовать функциональность несколько раз в одном классе. Эта функция зависит от частной переменной:

trait Address {
    private $address;

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

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

Единственный способ, которым я нашел использовать признак дважды, выглядит следующим образом:

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

        getAddress as getWorkAddress;            
        setAddress as setWorkAddress;
    }
}

Проблема заключается в том, что при этом частная переменная $address делится между различными методами, и код не будет работать должным образом:

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

Есть ли решение действительно использовать признак дважды, не разделяя его частные переменные?

Ответ 1

Объявление признака с помощью use не создаст экземпляр этой черты. Черты - это просто код, который копируется и вставляется в класс использования. as создаст только псевдоним для этого метода, например. он добавит что-то вроде

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

в свой класс пользователя. Но это будет только одна черта. Не будет двух разных свойств $address, но только одного.

Вы можете сделать методы частными, а затем делегировать любые публичные вызовы ему через __call с помощью переключателя/оболочки на имя метода и с помощью массива для адреса, например.

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 …
        }
    }
}

Но это всего лишь банда червей.

Другими словами, вы не можете делать то, что вы пытаетесь сделать с чертами. Либо используйте две разные черты. Или используйте хорошую старую агрегацию и добавьте конкретные прокси-методы.

Ответ 2

Я подтвердил, что вы можете использовать псевдоним несколько раз, что было для меня неожиданностью. Хотя ZendStudio, по-видимому, только "помогает при кодировании" в последнем псевдониме функции.

Возможность многократного псевдонима одной и той же функции может привести к некоторому интересному поведению, если функция Trait может определить, как называется она. Но, похоже, мы не можем определить "псевдонимную" функцию в функции черты.

Вот мой тестовый код:

<?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();

Выход:

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

Возможно, было бы неплохо добавить новую константу __ALIAS__ для функций черты, чтобы определить, какой псевдоним они называли.

На самом деле я создал запрос функции PHP для этого:

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

Ответ 3

Возможно, я немного опоздал на вечеринку, но поведение, которое вы пытаетесь создать, не является чем-то, что должно быть покрыто чертой, а простой композицией объекта.

<?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 {}
}

Ответ 4

Несколько лет спустя я прокомментировал комментарий в ошибке 63629 и произвел следующее:

<?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";

который выводит

High Street, Luton
Business Name, London

Это можно сделать! (Благодарю Дэйва Фаррелла, чей ответ вдохновил его на это.) Аргументы в пользу debug_backtrace - это попытка минимизировать использование памяти, я не уверен, насколько это влияет на производительность.