Гость
Форумы / PHP, Perl, Python [игнор отключен] [закрыт для гостей] / PHP - помогите разобраться со ссылками / 16 сообщений из 16, страница 1 из 1
05.03.2020, 20:31
    #39934930
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Допустим, есть такой код:
Код: php
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
trait PropertyBag
{
	private function _init()
	{
		$args = func_get_args();
		foreach ($args as $arg)
		{
			$this->local[$arg] = null;
		}
	}
	private function _flag()
	{
		$args = func_get_args();
		if (!empty($args))
		{
			$flag = array_shift($args);
			foreach ($args as $arg)
			{
				$this->flags[$arg] = $flag;
			}
		}
	}
	private function _fail($name)
	{
		$bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
		if (!array_key_exists($name, $this->local))
		{
			trigger_error("Invalid property '{$name}' in {$bt['file']}:{$bt['line']}", E_USER_NOTICE);
			return true;
		}
		if (!array_key_exists($name, $this->flags)) return;
		if ($this->flags[$name] == 'ro')
		{
			$fail = 'Read-only';
			if (in_array($bt['function'], ['__get', '__isset'])) return;
		}
		trigger_error("{$fail} property '{$name}' in {$bt['file']}:{$bt['line']}", E_USER_NOTICE);
		return true;
	}
	function __isset($name)
	{
		if ($this->_fail($name)) return;
		return isset($this->local[$name]);
	}
	function __get($name)
	{
		if ($this->_fail($name)) return;
		return $this->local[$name];
	}
	function __set($name, $value)
	{
		if ($this->_fail($name)) return;
		$this->local[$name] = $value;
	}
	function __unset ($name)
	{
		if ($this->_fail($name)) return;
		$this->local[$name] = null;
	}
	function __call($name, $arguments)
	{
		if ($this->_fail($name)) return;
		if (isset($arguments)) $this->local[$name] = $arguments[0];
		return $this->local[$name];
	}
	function set()
	{
		$args = func_get_args();
		if (count($args) > 0) $name = array_shift($args);
		if (count($args))
		{
			$this->local[$name] = $args[0];
		}
		else
		{
			unset($this->local[$name]);
		}
	}
	function getIterator()
	{
		return new ArrayIterator($this->local);
	}
}

class SubAppField implements IteratorAggregate
{
	use PropertyBag;
	private $local = [];
	private $flags = [];
	function __construct()
	{
		$this->_init('id', 'name', 'value');
		$this->_flag('ro', 'value');
	}
}

$fld = new SubAppField();
$fld->id = 'id1';
$fld->name = 'name1';
print_r($fld);
foreach($fld as $key=>&$val) { echo "field {$key} = *{$val}* \n"; $val = 123; }
print_r($fld);


Разве последняя строка не должна вывести значения 123?
...
Рейтинг: 0 / 0
06.03.2020, 06:02
    #39935000
vkle
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Не вижу на то причин.
Полагаю, такое поведение станет понятным после ответа на следующий простой вопрос. На какую именно переменную (в смысле область памяти, обозначенную некоторой меткой, имеющей имя) ссылается в данном случае $val на последней итерации цикла foreach?
...
Рейтинг: 0 / 0
06.03.2020, 11:22
    #39935088
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Ну поскольку используется стандартный итератор массивов, и ссылается он на массив, то я ожидал, что foreach ($fld as $key=>&$val) должен быть идентичен foreach ($this->local as $key=>&$val) внутри класса.
...
Рейтинг: 0 / 0
06.03.2020, 22:39
    #39935324
vkle
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Итератор тут сильно из другой йоперы.
На вопрос Вы ответить не попытались, видимо. Ладно, коротко, суть ссылающейся переменной есть алиас другой переменной - они обе указывают на одну и ту же область памяти. Примерно как хардлинк в файловой системе.
Когда переменная $val будет явно указывать на область памяти, заданную какой-то другой переменной или элементом массива, тогда содержимое этой области можно изменить, обращаясь к этой переменной.
...
Рейтинг: 0 / 0
10.03.2020, 14:37
    #39935958
ScareCrow
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Alibek B.,

Код: php
1.
2.
3.
4.
$a = [1,2,3,4,5];
foreach ($a as &$value) {}
foreach ($a as $value) {}
var_dump($a);
...
Рейтинг: 0 / 0
10.03.2020, 15:48
    #39935993
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
vkle
Когда переменная $val будет явно указывать на область памяти, заданную какой-то другой переменной или элементом массива, тогда содержимое этой области можно изменить, обращаясь к этой переменной.

Поясню свою логику.
Стандартный итератор позволяет в цикле изменять значения массива (если использовать foreach $array as &$item).
Значит стандартный итератор массива позволяет работать не со значением переменной, а со ссылкой на нее.
То есть моя цепочка рассуждений следующая: если я во внешнем коде использую &$val, то $val будет ссылаться на область памяти, которой соответствует элемент из private-массива $local, поскольку обход этого private-массива осуществляется через стандартный итератор массива.
Ведь класс SubAppField по сути просто обертка над массивом массивов, а итератор IteratorAggregate обеспечивает доступ к этому массиву.
Не так?

ScareCrow , в этом примере ничего неожиданного, такое поведение вполне укладывается в то, что я знаю о переменных PHP.
Если после первого цикла указать unset($value), то ничего в цикле изменяться не будет.
И если вместо var_dump использовать debug_zval_dump, то все будет более понятно.
...
Рейтинг: 0 / 0
10.03.2020, 20:53
    #39936090
vkle
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Alibek B.,

Как я понимаю эту кухню.
Значение 123 должно бы (гипотетически, по соображению, я так думаю и т.п.) нырнуть в область памяти, обозначенную, скажем $local['id']. Будь $local паблик переменной - так бы оно и было. Однако, $local в данном случае есть приватная переменная. И, стало быть, в ее отношении должен автоматически применяться волшебный метод __set(). Есть такой. И даже определен. А там, если верить доке, применима только лишь передача по значению, с явным указанием имени. Даже если итератор и прислал ссылку на область памяти в $val (ссылка - одна штука), то в сеттер она никак не лезет, так как он ожидает имя и значение (две штуки).
Ну вот так как-то, сеттер ссылку теряет тут.
...
Рейтинг: 0 / 0
10.03.2020, 20:59
    #39936091
vkle
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Alibek B.,

И еще, итератор тоже ж не дураками писан. Выше я лишь предположил "если итератор и прислал ссылку", а оно не факт. Со стороны итератора видно приватную переменную и уже заранее понятно должно быть, что ссылки там не работают. В этом смысле он может даже и не прислать ссылку, а прислать значение.
...
Рейтинг: 0 / 0
11.03.2020, 08:57
    #39936174
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
vkle
И, стало быть, в ее отношении должен автоматически применяться волшебный метод __set(). ... А там, если верить доке, применима только лишь передача по значению, с явным указанием имени.

Суть понял, но мне кажется, что в данном случае что-то другое.
Допустим в коде есть присвоение переменной, $var=$val. В виде функции это можно было бы записать как f(&$var, $var).
Но в моем случае волшебный метод set работает не с переменной (ссылкой на переменную), я в него передаю не саму переменную (которая хранит значение), а ключ массива. А уже внутри метода по этому ключу я обращаюсь к элементу массива.
То есть в виде функции это будет __set($key, $var), а внутри функции будет обращение к элементу массива по ключу $arr[$key]=$val.

Я скорее предполагал, что может быть это побочные эффекты trait, но в документации везде явно указывается, что trait это почти макрос и его код не переиспользуется, а повторяется.
...
Рейтинг: 0 / 0
11.03.2020, 12:41
    #39936251
vkle
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Alibek B.
а внутри функции будет обращение к элементу массива по ключу $arr[$key]=$val.
Да откуда ж там ключу взяться, если в Вашем варианте операции присваивания нет ни ключа, ни указания на переменную?
Было бы что-то вроде
Код: php
1.
$fld->$key = 123;

- тогда да, после развертывания левой части выражения $fld->$key вполне может трактоваться как имя переменной.
...
Рейтинг: 0 / 0
11.03.2020, 16:04
    #39936318
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
vkle
Да откуда ж там ключу взяться, если в Вашем варианте операции присваивания нет ни ключа, ни указания на переменную?

Вероятно да.
Я думал, что под капотом какая-нибудь магия происходит, следствием которой является то, что при использовании ссылки на переменную (например в foreach) фактически происходит присвоение.
Но сейчас почитал про итераторы, похоже что там что-то более сложное.
...
Рейтинг: 0 / 0
11.03.2020, 16:37
    #39936341
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
С учетом ограничений переделал класс для универсальной коллекции.
Но ведь это наверняка велосипед.
Не посоветуете, что добавить/улучшить?
Код: php
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
function _assoc($arr)
{
	if (!isset($arr)) return false;
	if (!is_array($arr)) return false;
	if (empty($arr)) return false;
	return array_keys($arr) !== range(0, count($arr) - 1);
}

// Модуль для работы с коллекциями
class Collection implements IteratorAggregate
{

	private $store;

	// new Collection(<field>, [<field>=><value>, ...], ...)
	function __construct()
	{
		$this->store = [];
		$args = func_get_args();
		foreach ($args as $arg)
		{
			if (!isset($arg)) continue;
			if (is_array($arg)) {
				$assoc = _assoc($arg);
				foreach ($arg as $k=>$v)
				{
					if ($assoc) {
						$this->store[$k] = $v;
					} else {
						$this->store[$v] = null;
					}
				}
				unset($assoc, $k, $v);
			} else {
				$this->store[$arg] = null;
			}
		}
	}

	private function _fail($name)
	{
		$bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
		if (!array_key_exists($name, $this->store))
		{
			trigger_error("Invalid field '{$name}' in {$bt['file']}:{$bt['line']}", E_USER_NOTICE);
			return true;
		}
	}
	function __isset($field)
	{
		if ($this->_fail($field)) return;
		return isset($this->store[$field]);
	}
	function __get($field)
	{
		if ($this->_fail($field)) return;
		return $this->store[$field];
	}
	function __set($field, $data)
	{
		if ($this->_fail($field)) return;
		$this->store[$field] = $data;
	}
	function __unset ($field)
	{
		if ($this->_fail($field)) return;
		$this->store[$field] = null;
	}
	function __call($field, $arguments)
	{
		if ($this->_fail($field)) return;
		if (isset($arguments)) $this->store[$field] = $arguments[0];
		return $this->store[$field];
	}

	// IteratorAggregate interface
	function getIterator()
	{
		return new ArrayIterator($this->store);
	}

	function _has($field)
	{
		if (isset($field))
		{
			if (array_key_exists($field, $this->store))
			{
				return true;
			}
		}
		return false;
	}

	function _get($field)
	{
		if (isset($field))
		{
			if (array_key_exists($field, $this->store))
			{
				return $this->store[$field];
			}
		}
	}

	function _set($field, $data)
	{
		$this->store[$field] = $data;
		return $this->store[$field];
	}

	function _del($field)
	{
		$item = $this->_get($field);
		if (isset($item)) unset($this->store[$field]);
		return $item;
	}

	function _cnt()
	{
		return count($this->store);
	}

	function _rst()
	{
		$this->store = [];
	}

}

$tmp = new Collection('int', 'str', 'arr', 'ref');
$tmp->int = 123;
$tmp->str('abc');
$tmp->_set('arr', [1,2,3]);
$tmp->ref = new Collection('test');
$tmp->ref->test = 'test1';
print_r($tmp);
...
Рейтинг: 0 / 0
11.03.2020, 16:48
    #39936350
vkle
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Гипотетически, такая магия возможна, если блок кода foreach будет передан в итератор. Однако, думаю, в таком случае получится абракадабра контекста. Ведь блок кода по сути выполняется во вне по отношению к объекту, а итератор внутри.

И еще вот что подумалось. Ведь прямой доступ к приватной переменной объекта извне его невозможен по определению. Таким образом, никакие указатели или ссылки на область памяти, обозначенную этой переменной за пределы объекта вылезти не должны. Наверно, это более простое объяснение.
...
Рейтинг: 0 / 0
11.03.2020, 17:15
    #39936364
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
vkle
Ведь прямой доступ к приватной переменной объекта извне его невозможен по определению. Таким образом, никакие указатели или ссылки на область памяти, обозначенную этой переменной за пределы объекта вылезти не должны. Наверно, это более простое объяснение.

Ну через методы это работает.
Возвратить ссылку у меня не получилось, а вот через аргумент по ссылке вполне.
...
Рейтинг: 0 / 0
11.03.2020, 23:03
    #39936450
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Столкнулся с сообщением "Indirect modification of overloaded property".
Можно ли от него избавится?
Ранее можно было просто указать &, чтобы передать ссылку.
Но начиная с какой-то версии (как минимум с 5.6) это запретили, & может указываться только в декларации function и foreach, использовать его в выражениях нельзя.
Вот тут в принципе почти мой вариант, но предложенное решение с & сейчас уже неприменимо.
...
Рейтинг: 0 / 0
12.03.2020, 09:21
    #39936545
Alibek B
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
PHP - помогите разобраться со ссылками
Отбой, это принципиально неразрешимый вопрос.
Подавить сообщение можно, но вопрос это не решает.
...
Рейтинг: 0 / 0
Форумы / PHP, Perl, Python [игнор отключен] [закрыт для гостей] / PHP - помогите разобраться со ссылками / 16 сообщений из 16, страница 1 из 1
Целевая тема:
Создать новую тему:
Автор:
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]