Как стать автором
Обновить

Комментарии 64

Есть решение намного проще. Дело в том, что когда вы вытаетесь записать значение в несуществующий массив:
$my->some['sub'] = 'test';
то вызывается метод волшебный метод __get. Т.е. вы получаете значение $my->some, а потом в это значение пытаетесь добавить индекс ['sub']. Чтобы всё-таки записать элемент в массив тостаточно передавать его по ссылке:

class MyClass {
protected $data = array('some' => array('sub' => 'data'));

public function __set($name, $value) {
$this->data[$name] = $value;
}
public function &__get($name) {
return $this->data[$name];
}
}
Ооо, очень классно! Спасибо!
Я бы сказал, это решение не всегда подходит, т.к. зачастую (я думаю, даже в подавляющем большинстве случаев) MyClass не хочет, чтобы его волновали изменения в возвращенных скалярных переменных, а при возврате ссылки ведь так и получится, нет? Это про тот, на мой взгляд нередкий, вариант, когда в $this->data хранятся массивы вперемешку со скалярами.
Мне кажется, что как раз-таки он подходит для большинства случаев. Этот класс предоставляет какое-то API, методы, с которыми будут общаться другие компоненты системы, другие приложения. И они могут не знать(и не должны) о внутренней реализации хранения данных.
И в случае рефакторинга, вам не придётся переписывать ещё сотни классов для поддержки массивов.
Но придется помнить, что нельзя менять переменные, полученные из MyClass — чем же это лучше?
Нет, это не лучше, вообще юзать напрямую свойства класса не очень хорошо.
Только что подумал, может такой вариант будет уместен:

public function &__get($name)
{
    if (is_scalar($this->data[$name]))
    {
        $property = $this->data[$name];
    }
    else
    {
        $property = &$this->data[$name];
    }
    
    return $property;
}

В итоге массивы, объекты и ресурсы будут передаваться по ссылке, а обычные скалярные величины — по значению. И если, например, мы попытаемся сделать следующее:

$my->x = 1;
$x = &$my->x;
$x = 0;

То получим знакомое сообщение: Indirect modification of overloaded property my::$x has no effect. Но то же обращение к объекту, описанное выше в примере, будет работать.

Также можно добавить более сложную логику в __get. Например, для определённых типов клонировать объект и возвращать ссылку на копию.
Ещё можно указать, что значение передаётся по ссылке непосредственно при возврате:
return &$this->data[$name];
Ой, глупость написал :)
PHP — быдло язык!
Иди в С++!
«Элитный дотнетчик»? :D
блин спалился… =)
НЛО прилетело и опубликовало эту надпись здесь
Вы уж извините, но это как говориться: «Плохому танцору яйца мешают».
Зачем кодить на устаревшем недоязыке, если есть нормальные современные и удобные варианты?
Вас никто не заставляет это делать. Мне кажется, если язык все еще востребован и развивается, то он не такой уж и устаревший, как Вы выразились.
Неадекватные вещи можно писать на чем угодно, в равной степени как и шедевры. Не в языке дело.
Просто Ваше высказывание:
PHP — быдло язык!
как бы намекает на некий холивар.
Аналогично можно завернуть и в класс с интерфейсом ArrayAccess, чтобы никаких отличий в «пользовании» не было по сравнению с обычным массивом, однако у обоих вариантов есть одна большая проблема.
Сорри, отправился раньше времени. Так вот…

… у обоих вариантов есть одна большая проблема. Если я хочу записать ['a']['b']['c'], а у меня на этот момент имеется только ['a'], то первое же «оборачивание» не пройдет, т.к. is_array не выполнится. Т.е. это подходит только для изменения существующего массива, но не автосоздания нового.

p.s. Писал шаблонизатор свой, столкнулся с именно такой проблемой. Кстати, вариант с имплементацией ArrayAccess лучше в плане обращения например к цифровым индексам.
Только при заворачивании в ArrayAccess придется не забыть вложенные массивы на ходу преобразовывать в такие же объекты, иначе снова появится Indirect modification.

Кроме того, реализация ArrayAccess имеет свои закидоны. Например, в стандартных массивах можно сделать:

foreach ($array as $key=>$val) {
if (… ) unset($array[$key]);
}

И цикл спокойно пойдет дальше. А в собственной реализации приходится городить кода для поддержки этой возможности.

При этом — что особенно погано — реализация в SPL'евском ArrayObject как раз ведет себя в этом случае не так, как стандартные массивы.
> придется не забыть вложенные массивы на ходу преобразовывать в такие же объекты
В предложенном выше варианте — аналогично :)

> в собственной реализации приходится городить кода для поддержки этой возможности
Опять же, в предложенном изначально варианте реализации Traversable тоже нету
А, ну да, я спутал ArrayAccess с итераторами, правда я одно без другого не очень часто использую. Пожалуй, просто для доступа к переменным класса итераторы и не нужны.
имхо — велосипед. Передавайте ссылку на массив и не будет никаких проблем
Да, но нельзя часть методов передать по ссылке, а чать — по значению. Что в общем случае при данных обстоятельствах вроде бы как нужно.
а что мешает сделать проверку — если передается массив — return ссылку, если что-то другое — return значение
Вот именно эта «фича» с Indirect modification вызвала у меня стойкое понимание, что «волшебные методы» в пхп сделаны то ли с какими-то целями, которые мало сообразуются с моей практикой, то ли просто через жопу, то ли, что наиболее вероятно — никто из разрабов не понимает, зачем смешивать объекты и массивы.

Кстати, надо будет почитать, что говорят багтрекеры и западные интернеты о судьбе этой фичи.
НЛО прилетело и опубликовало эту надпись здесь
Не очень понятно, но очень интересно. Вы предлагаете реализовывать этот интерфейс для классов? Можете об этом по-подробнее?
+1, единственная действительно хорошая ссылка в этом топике
* там, кстати, и реализация приведена, и в комментах решение проблемы из поста тоже есть
Боже мой, автор не знает как передать переменную по ссылке, но уже гоняет ActiveRecord! Странно всё это, раньше было иначе.
один из смыслов заворачивания данных в декораторы — контроль над записью данных, в плоть до уровня readonly и возможность навешать сколько угодно проперти.
Например чтобы знать какие именно данных менялись.
1) Покажите в каком месте это декоратор! Если мне не изменяет память, декоратор должен декорировать данные. В данном примере я этого не наблюдаю!

2) С каких пор хард код называется контролем над записью данных?

protected $data = array('some' => array('sub' => 'data'));

я бы ещё понял если он инициализировал это в конструкторе:

$my = new MyClass(array('some' => array('sub' => 'data')));

3) Что значит навесить сколько угодно property? Разве есть какой то лимит?

В общем какая то непонятная реализация контейнера данных, причём непонятно зачем используются методы перегрузки, неужели без них не обойтись?
1) Покажите в каком месте это декоратор! Если мне не изменяет память, декоратор должен декорировать данные. В данном примере я этого не наблюдаю!

Любой прокси код который обеспечивает доступ к данным ЧЕРЕЗ себя — декоратор

2) С каких пор хард код называется контролем над записью данных?

C тез пор как этот хард код используется внутри или при создании\описании внутрених данных, но не при доступе к нему

3) Что значит навесить сколько угодно property? Разве есть какой то лимит?

Вы МОЖЕТЕ переопредилить get\set любого значения на выполнение любых действий.
Вплоть до того что при запросе some( в данном случае ) его внутрений массив будет подгружен. Это можно например расширить до развертки внешний ключей в моделе ORM.

Если же вы результатом GET получаете рефссылку на внутрение данные вы это ничего сделать НЕ СМОЖЕТЕ.
Любой прокси код который обеспечивает доступ к данным ЧЕРЕЗ себя — декоратор

то есть любой геттер это декоратор? Пмойму вы пишите бред!

C тез пор как этот хард код используется внутри или при создании\описании внутрених данных, но не при доступе к нему

Вообще-то это и называется хард-код! Ведь если каждый раз когда придётся пере использовать этот код надо будет вносить изменение в класс: сорри это не вяжется с принципами ООП

Вы МОЖЕТЕ переопредилить get\set любого значения на выполнение любых действий.
Вплоть до того что при запросе some( в данном случае ) его внутрений массив будет подгружен. Это можно например расширить до развертки внешний ключей в моделе ORM.


Что ты куришь?
1.да любой декоратор гета — декоратор гета.

2.array('some' => array('sub' => 'data')); — есть частный случая ПРИМЕРНОГО заполнения данных. Давайте я выложу вам пример… ну например $(".searchInput").autocomplete(блабла) — вы же мне не скажете что мой код работатает только для этого селекта, только с этими настройками фетча данных?
А тут почему-то тестовый пример вам глаза режет :)

3.С принципами ООП вяжется все.
Если я отнаследуюсь от MyClass и переопределю себе свой $data, который функции определеные в MyClass дружно начнут использовать… Чтож это практически и есть базис наследования.
Сам по себе хардкод ни к камому ООП и другим «принципам» не относиться.

4.Да вроде я тут просто описал что такое проперти и под каким соусом ядят
Посмотрите на класс Zend_Config из Zend Framework. Он решает эту задачу путем превращения любого «подмассива» в объект Zend_Config.
Резюмируя вышеуказанный диалог: основная проблема остается в невозможности в общем случае на момент выполнения __get() определить, будет ли это использоваться как массив, либо как конечная переменная. Если массив уже создан, то is_array подскажет. Но в «настоящих» массивах есть возможность обращаться к еще не созданным ячейкам: они создаются динамически во время присваивания. Так вот в обертке такое сделать нельзя.

Поясню:

$a = array();
$a['d']['c']['d'] = 'e';

С обычным массивом это отработает. Указанная же выше обертка «споткнется», т.к. не обернет в себя первый еще не созданный ключ.

Для 100% корректной реализации, это должен отслеживать перегруженный оператор =. Но к сожалению в php такой возможности нет. Соответственно выходы — только «костыли». Либо вручную вызывать какой-то leaf() метод, который бы отдавал именно конечную переменную, но по умолчанию бы ВСЕ заворачивал в массив. Либо отдавать по ссылке непосредственно. И то и другое имеет свои недостатки.
Совершенно верно, во всяком случае сделать это так гибко как на Python или Ruby не получится! Единственный простой способ сделать многоуровневые вложенные контейнеры данных это определять их самом, методы перегрузки не помогут!

class ParameterHolder
{
 private
  $parameters = array();
  
 public function __set($name, $value)
 {
  $this->parameters[$name] = $value;
 }
 
 public function & __get($name)
 {
  if (array_key_exists($name, $this->parameters))
  {
   $value = & $this->parameters[$name];
  }
  return $value;
 }
}

// Применение

$parameters = new ParameterHolder();

$parameters->a1 = new ParameterHolder();
$parameters->a2 = new ParameterHolder();
$parameters->a2->b2 = new ParameterHolder();

$parameters->a1->b1 = 'variable1'; // variable1
$parameters->a2->b2->c2 = 'variable2'; // variable2


* This source code was highlighted with Source Code Highlighter.
Зачем всё усложнять? :)

class MyClass
{
    protected $data = array('some' => array('sub' => 'data'));
 
    public function __set($name, $value)
    {
        $this->data[$name] = $value;
    }
    
    public functio
        if (is_scan &__get($name)
    {lar($this->data[$name]))
        {
            $property = $this->data[$name];
        }
        else
        {
            $property = &$this->data[$name];
        }
        
        return $property;
    }
}

$my = new MyClass();

$my->a1 = new stdClass();
$my->a1->b2 = new stdClass();

$my->a1->b1 = 'variable1';
$my->a1->b2->c2 = 'variable2';

var_dump($my->a1->b1, $my->a1->b2->c2);
/*
string(9) "variable1"
string(9) "variable2"
*/
ты бы перед тем как постить хотя бы проверил свой код! и в каком месте я усложнил, уж прощё твоего у меня точно
Я проверил свой код, если вы имеете в виду работоспособность (результат var_dump'ов брался из реального вывода). А также читайте ответ ниже habrahabr.ru/blogs/php/103834/#comment_3233757
не надо даже проверять, и глаз орла иметь не надо чтобы увидеть что код выше битый — не спеши так. Внизу код видел.
Я конкретный пример автора статьи рассмотрел и внёс в него небольшие доработки, чтобы можно было и «обратиться к несуществующему элементу массива», и использовать обращения вида $my->a1->b1 = 'variable1';.

Код рабочий, я его взял напрямую из своего скрипта примера, который до этого успешно протестировал.
блин да ты прочитай его:

public functio
        if (is_scan &__get($name)
    {lar($this->data[$name]))


ты видишь?
Прошу прощения, хрень какая-то при копировании произошла. Корректный код _get описан в комментарии ниже habrahabr.ru/blogs/php/103834/#comment_3233735
По крайней мере для такого простого примера можно обойтись и stdClass. Но если, например, параметры при задании проходят валидацию, либо ведётся лог, либо что-нибудь ещё, конечно, придётся создавать отдельный класс, который будет всё это реализовывать.
Полная версия для примера:

class MyClass
{
    protected $data = array('some' => array('sub' => 'data'));
 
    public function __set($name, $value)
    {
        $this->data[$name] = $value;
    }
    
    public function &__get($name)
    {
        if (is_scalar($this->data[$name]))
        {
            $property = $this->data[$name];
        }
        else
        {
            $property = &$this->data[$name];
        }
        
        return $property;
    }
}

$my = new MyClass();

$my->some['x']['y']['z'] = 'foo';
var_dump($my->some);

/*
array(2) {
  ["sub"]=>
  string(4) "data"
  ["x"]=>
  array(1) {
    ["y"]=>
    array(1) {
      ["z"]=>
      string(3) "foo"
    }
  }
}
*/

$my->something_else = array();
$my->something_else['a']['b']['c'] = 'bar';
var_dump($my->something_else);

/*
array(1) {
  ["a"]=>
  array(1) {
    ["b"]=>
    array(1) {
      ["c"]=>
      string(3) "bar"
    }
  }
}
*/
Хороший вариант! Однако Ваш вариант в 3ем комменте был лучше и лаконичнее.

А здесь — зачем эта проверка на is_scalar? Если это не массив, то __get вызовется только в случае чтения. Тогда нам не особо важно, читать по ссылке или по значению — все равно переменная не изменится. Поэтому промежуточную переменную делать и давать ссылку на нее ИМХО бессмысленно. А если запись в скаляр, то вызовется __set, и до проверки дело не дойдет. Не так ли?
Не совсем. Если __get реализовать так:

public function &__get($name)
{
    return $this->data[$name];
}


Тогда следующий код отработает на ура:

// Определяем скалярный член
$my->scalar = 1;


// Так, конечно, ничего не будет

$scalar = $my->scalar;
$scalar = 0;

var_dump($scalar, $my->scalar);
/*
int (0)
int (1)
*/


// А вот так можно будет поменять значение скалярного члена класса

$scalar = &$my->scalar;
$scalar = 0;

var_dump($scalar, $my->scalar);
/*
int (0)
int (0)
*/
Только что понял, что вы абсолютно правы: is_scalar тут лишняя — её нужно заменить на is_array.
Я может чего-то не понимаю, но есть же ArrayObject, к чему этот велосипед да еще и с пафосным названием ActiveArray?
$array = array('some' => array('sub' => 'data'),
'b' => 'two',
'c' => 'three');

$arrayObject = new ArrayObject($array, ArrayObject::ARRAY_AS_PROPS);
$arrayObject->some['sub'] = 'test';
print_r($arrayObject);

ArrayObject Object
(
[some] => Array
(
[sub] => test
)

[b] => two
[c] => three
)

Я не изобретаю велосипеды :) Просто рассматривал конкретный пример автора статьи
Здесь как бы идёт речь о вложенных многоуровневых массивах с применением метода перегрузки объекта. Решения могут быть разными, например я бы расцеловал если бы вы написали как сделать следующий код выполняемым:

$a = new Parameters;
$a->b->c->d = 'variables';

Есть варианты? Тока две строчки, неважно как и что вы опишите в классе, не считается только хард-код как у автора;
Согласен с вами. Но я рассматривал конкретно, как вы это называете, хардкорный пример автора :)
Если бы у меня была задача, которую вы описали выше, я бы конечно же решал её другим путём.
$a->b_c_d= 'variables'
не катит?
Разумеется нет :)

Тогда бы темы не было.
значит вам шашеки %-)
Можно и так сказать ;)

Без шашечек можно было писать функционально на пыхпыхе №4: Р
изменение вложенных структур в обход исходного объекта — довольно дурная «ооп» практика
Ну а кто говорит про «в обход»? Предложите вариант чтобы не в обход, только чтобы внутри была древовидная ассоциативная структура с неизвестным кол-вом уровней.

$a->b->c->d со всеми врапперами например.
что вы с ним общаетесь — он же ТРОЛЪ!!!
Кто такой тролЪ? :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации