PHP: обнаружение, когда значение переменных было изменено

Мне было интересно, есть ли способ добавить что-то вроде прослушивателя изменений к переменной. Самый простой пример того, что я имею в виду, что-то будет работать в этом направлении;

// Start with a variable $variable = "some value"; // Define a listener function myChangeListener($variable) { // encode with json_encode and send in cookie } // Add my listener to the variable addListenerToVariable(&$variable, 'myChangeListener'); // Change the variables value, triggering my listener $variable = "new value"; 

Теперь вы, вероятно, спрашиваете, почему в мире мне даже нужно будет беспокоиться об этом подходе, почему бы просто не сделать функцию для настройки cookie. Ответ: у меня есть метод &__get($var) который возвращает ссылку на многомерный элемент массива, и я надеюсь использовать что-то подобное, чтобы определить, когда / если элемент массива или один из его дочерних элементов был отредактирован, а затем отправил файл cookie, если он есть. Ожидаемый результат будет работать примерно так:

 $cookies->testArray["test"] = "newValue"; // This would send the cookie 'testArray' with the value '{"test":"newValue"}' 

Я честно ожидаю, что это будет совершенно невозможно, и я прошу прощения, если я прав. Но я только что узнал, как правильно использовать ссылки вчера, поэтому я думал, что попрошу, прежде чем полностью списывать эту идею.

Спасибо за любые ответы, которые я получаю, будь то то, на что я надеялся, или то, что я ожидал.

РЕДАКТИРОВАТЬ:

Для большей ясности, вот примерный класс для того, что я пытаюсь выполнить;

 class MyCookies { private $cookies = array(); private $cookieTag = "MyTag"; public function __construct() { foreach($_COOKIE as $cookie => $value) { if(strlen($cookie)>strlen($this->cookieTag."_")&&substr($cookie,0,strlen($this->cookieTag."_"))==$this->cookieTag."_") { $cookieVar = substr($cookie,strlen($this->cookieTag."_")); $this->cookies[$cookieVar]['value'] = json_decode($value); } } } // This works great for $cookies->testArray = array("testKey" => "testValue"); // but never gets called when you do $cookies->testArray['testKey'] = "testValue"; public function __set($var, $value) { if(isset($value)) { $this->cookies[$var]['value'] = $value; setcookie($this->cookieTag.'_'.$var,json_encode($value),(isset($this->cookies[$var]['expires'])?$this->cookies[$var]['expires']:(time()+2592000)),'/',''); } else { unset($this->cookies[$var]); setcookie($this->cookieTag.'_'.$var,'',(time()-(172800)),'/',''); } return $value; } // This gets called when you do $cookies->testArray['testKey'] = "testValue"; public function &__get($var) { // I want to add a variable change listener here, that gets triggered // when the references value has been changed. // addListener(&$this->config[$var]['value'], array(&$this, 'changeListener')); return $this->config[$var]['value']; } /* public function changeListener(&$reference) { // scan $this->cookies, find the variable that $reference is the reference to (don't know how to do that ether) // send cookie } */ public function __isset($var) { return isset($this->cookies[$var]); } public function __unset($var) { unset($this->cookies[$var]); setcookie($this->cookieTag.'_'.$var,'',(time()-(172800)),'/',''); } public function setCookieExpire($var, $value, $expire=null) { if(!isset($expire)) { $expire = $value; $value = null; } if($expire<time()) $expire = time() + $expire; if(isset($value)) $this->cookies[$var]['value'] = $value; $this->cookies[$var]['expires'] = $expire; setcookie($this->cookieTag.'_'.$var,json_encode((isset($value)?$value:(isset($this->cookies[$var]['value'])?$this->cookies[$var]['value']:''))),$expire,'/',''); } } не class MyCookies { private $cookies = array(); private $cookieTag = "MyTag"; public function __construct() { foreach($_COOKIE as $cookie => $value) { if(strlen($cookie)>strlen($this->cookieTag."_")&&substr($cookie,0,strlen($this->cookieTag."_"))==$this->cookieTag."_") { $cookieVar = substr($cookie,strlen($this->cookieTag."_")); $this->cookies[$cookieVar]['value'] = json_decode($value); } } } // This works great for $cookies->testArray = array("testKey" => "testValue"); // but never gets called when you do $cookies->testArray['testKey'] = "testValue"; public function __set($var, $value) { if(isset($value)) { $this->cookies[$var]['value'] = $value; setcookie($this->cookieTag.'_'.$var,json_encode($value),(isset($this->cookies[$var]['expires'])?$this->cookies[$var]['expires']:(time()+2592000)),'/',''); } else { unset($this->cookies[$var]); setcookie($this->cookieTag.'_'.$var,'',(time()-(172800)),'/',''); } return $value; } // This gets called when you do $cookies->testArray['testKey'] = "testValue"; public function &__get($var) { // I want to add a variable change listener here, that gets triggered // when the references value has been changed. // addListener(&$this->config[$var]['value'], array(&$this, 'changeListener')); return $this->config[$var]['value']; } /* public function changeListener(&$reference) { // scan $this->cookies, find the variable that $reference is the reference to (don't know how to do that ether) // send cookie } */ public function __isset($var) { return isset($this->cookies[$var]); } public function __unset($var) { unset($this->cookies[$var]); setcookie($this->cookieTag.'_'.$var,'',(time()-(172800)),'/',''); } public function setCookieExpire($var, $value, $expire=null) { if(!isset($expire)) { $expire = $value; $value = null; } if($expire<time()) $expire = time() + $expire; if(isset($value)) $this->cookies[$var]['value'] = $value; $this->cookies[$var]['expires'] = $expire; setcookie($this->cookieTag.'_'.$var,json_encode((isset($value)?$value:(isset($this->cookies[$var]['value'])?$this->cookies[$var]['value']:''))),$expire,'/',''); } } не class MyCookies { private $cookies = array(); private $cookieTag = "MyTag"; public function __construct() { foreach($_COOKIE as $cookie => $value) { if(strlen($cookie)>strlen($this->cookieTag."_")&&substr($cookie,0,strlen($this->cookieTag."_"))==$this->cookieTag."_") { $cookieVar = substr($cookie,strlen($this->cookieTag."_")); $this->cookies[$cookieVar]['value'] = json_decode($value); } } } // This works great for $cookies->testArray = array("testKey" => "testValue"); // but never gets called when you do $cookies->testArray['testKey'] = "testValue"; public function __set($var, $value) { if(isset($value)) { $this->cookies[$var]['value'] = $value; setcookie($this->cookieTag.'_'.$var,json_encode($value),(isset($this->cookies[$var]['expires'])?$this->cookies[$var]['expires']:(time()+2592000)),'/',''); } else { unset($this->cookies[$var]); setcookie($this->cookieTag.'_'.$var,'',(time()-(172800)),'/',''); } return $value; } // This gets called when you do $cookies->testArray['testKey'] = "testValue"; public function &__get($var) { // I want to add a variable change listener here, that gets triggered // when the references value has been changed. // addListener(&$this->config[$var]['value'], array(&$this, 'changeListener')); return $this->config[$var]['value']; } /* public function changeListener(&$reference) { // scan $this->cookies, find the variable that $reference is the reference to (don't know how to do that ether) // send cookie } */ public function __isset($var) { return isset($this->cookies[$var]); } public function __unset($var) { unset($this->cookies[$var]); setcookie($this->cookieTag.'_'.$var,'',(time()-(172800)),'/',''); } public function setCookieExpire($var, $value, $expire=null) { if(!isset($expire)) { $expire = $value; $value = null; } if($expire<time()) $expire = time() + $expire; if(isset($value)) $this->cookies[$var]['value'] = $value; $this->cookies[$var]['expires'] = $expire; setcookie($this->cookieTag.'_'.$var,json_encode((isset($value)?$value:(isset($this->cookies[$var]['value'])?$this->cookies[$var]['value']:''))),$expire,'/',''); } } 

Что касается того, почему я не хочу иметь функцию обновления, это действительно личное предпочтение. Это будет использоваться в рамках, которые могут расширить другие люди, и я думаю, что они могут обрабатывать файлы cookie, поскольку только переменные в одной строке кода чувствуют себя slicker.

Если вы хотите реализовать собственные обработчики / триггеры событий в массиве или что-то еще, вы можете использовать оболочку класса.

Для одной переменной __get() и __set() магические методы, как вы заметили. Для массива есть лучший обработчик: ArrayAccess . Он позволяет эмулировать действия массива с помощью методов класса и общей семантики массивов (например, коллекции на некоторых других языках).

 <?php header('Content-Type: text/plain'); // ArrayAccess for Array events class TriggerableArray implements ArrayAccess { protected $array; // container for elements protected $triggers; // callables to actions public function __construct(){ $this->array = array(); // predefined actions, which are availible for this class: $this->triggers = array( 'get' => null, // when get element value 'set' => null, // when set existing element's value 'add' => null, // when add new element 'exists' => null, // when check element with isset() 'unset' => null // when remove element with unset() ); } public function __destruct(){ unset($this->array, $this->triggers); } public function offsetGet($offset){ $result = isset($this->array[$offset]) ? $this->array[$offset] : null; // fire "get" trigger $this->trigger('get', $offset, $result); return $result; } public function offsetSet($offset, $value){ // if no offset provided if(is_null($offset)){ // fire "add" trigger $this->trigger('add', $offset, $value); // add element $this->array[] = $value; } else { // if offset exists, then it is changing, else it is new offset $trigger = isset($this->array[$offset]) ? 'set' : 'add'; // fire trigger ("set" or "add") $this->trigger($trigger, $offset, $value); // add or change element $this->array[$offset] = $value; } } public function offsetExists($offset){ // fire "exists" trigger $this->trigger('exists', $offset); // return value return isset($this->array[$offset]); } public function offsetUnset($offset){ // fire "unset" trigger $this->trigger('unset', $offset); // unset element unset($this->array[$offset]); } public function addTrigger($trigger, $handler){ // if action is not availible and not proper handler provided then quit if(!(array_key_exists($trigger, $this->triggers) && is_callable($handler)))return false; // assign new trigger $this->triggers[$trigger] = $handler; return true; } public function removeTrigger($trigger){ // if wrong trigger name provided then quit if(!array_key_exists($trigger, $this->triggers))return false; // remove trigger $this->triggers[$trigger] = null; return true; } // call trigger method: // first arg - trigger name // other args - trigger arguments protected function trigger(){ // if no args supplied then quit if(!func_num_args())return false; // here is supplied args $arguments = func_get_args(); // extract trigger name $trigger = array_shift($arguments); // if trigger handler was assigned then fire it or quit return is_callable($this->triggers[$trigger]) ? call_user_func_array($this->triggers[$trigger], $arguments) : false; } } function check_trigger(){ print_r(func_get_args()); print_r(PHP_EOL); } function check_add(){ print_r('"add" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_get(){ print_r('"get" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_set(){ print_r('"set" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_exists(){ print_r('"exists" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_unset(){ print_r('"unset" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } $victim = new TriggerableArray(); $victim->addTrigger('get', 'check_get'); $victim->addTrigger('set', 'check_set'); $victim->addTrigger('add', 'check_add'); $victim->addTrigger('exists', 'check_exists'); $victim->addTrigger('unset', 'check_unset'); $victim['check'] = 'a'; $victim['check'] = 'b'; $result = $victim['check']; isset($victim['check']); unset($victim['check']); var_dump($result); ?> с <?php header('Content-Type: text/plain'); // ArrayAccess for Array events class TriggerableArray implements ArrayAccess { protected $array; // container for elements protected $triggers; // callables to actions public function __construct(){ $this->array = array(); // predefined actions, which are availible for this class: $this->triggers = array( 'get' => null, // when get element value 'set' => null, // when set existing element's value 'add' => null, // when add new element 'exists' => null, // when check element with isset() 'unset' => null // when remove element with unset() ); } public function __destruct(){ unset($this->array, $this->triggers); } public function offsetGet($offset){ $result = isset($this->array[$offset]) ? $this->array[$offset] : null; // fire "get" trigger $this->trigger('get', $offset, $result); return $result; } public function offsetSet($offset, $value){ // if no offset provided if(is_null($offset)){ // fire "add" trigger $this->trigger('add', $offset, $value); // add element $this->array[] = $value; } else { // if offset exists, then it is changing, else it is new offset $trigger = isset($this->array[$offset]) ? 'set' : 'add'; // fire trigger ("set" or "add") $this->trigger($trigger, $offset, $value); // add or change element $this->array[$offset] = $value; } } public function offsetExists($offset){ // fire "exists" trigger $this->trigger('exists', $offset); // return value return isset($this->array[$offset]); } public function offsetUnset($offset){ // fire "unset" trigger $this->trigger('unset', $offset); // unset element unset($this->array[$offset]); } public function addTrigger($trigger, $handler){ // if action is not availible and not proper handler provided then quit if(!(array_key_exists($trigger, $this->triggers) && is_callable($handler)))return false; // assign new trigger $this->triggers[$trigger] = $handler; return true; } public function removeTrigger($trigger){ // if wrong trigger name provided then quit if(!array_key_exists($trigger, $this->triggers))return false; // remove trigger $this->triggers[$trigger] = null; return true; } // call trigger method: // first arg - trigger name // other args - trigger arguments protected function trigger(){ // if no args supplied then quit if(!func_num_args())return false; // here is supplied args $arguments = func_get_args(); // extract trigger name $trigger = array_shift($arguments); // if trigger handler was assigned then fire it or quit return is_callable($this->triggers[$trigger]) ? call_user_func_array($this->triggers[$trigger], $arguments) : false; } } function check_trigger(){ print_r(func_get_args()); print_r(PHP_EOL); } function check_add(){ print_r('"add" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_get(){ print_r('"get" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_set(){ print_r('"set" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_exists(){ print_r('"exists" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_unset(){ print_r('"unset" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } $victim = new TriggerableArray(); $victim->addTrigger('get', 'check_get'); $victim->addTrigger('set', 'check_set'); $victim->addTrigger('add', 'check_add'); $victim->addTrigger('exists', 'check_exists'); $victim->addTrigger('unset', 'check_unset'); $victim['check'] = 'a'; $victim['check'] = 'b'; $result = $victim['check']; isset($victim['check']); unset($victim['check']); var_dump($result); ?> с <?php header('Content-Type: text/plain'); // ArrayAccess for Array events class TriggerableArray implements ArrayAccess { protected $array; // container for elements protected $triggers; // callables to actions public function __construct(){ $this->array = array(); // predefined actions, which are availible for this class: $this->triggers = array( 'get' => null, // when get element value 'set' => null, // when set existing element's value 'add' => null, // when add new element 'exists' => null, // when check element with isset() 'unset' => null // when remove element with unset() ); } public function __destruct(){ unset($this->array, $this->triggers); } public function offsetGet($offset){ $result = isset($this->array[$offset]) ? $this->array[$offset] : null; // fire "get" trigger $this->trigger('get', $offset, $result); return $result; } public function offsetSet($offset, $value){ // if no offset provided if(is_null($offset)){ // fire "add" trigger $this->trigger('add', $offset, $value); // add element $this->array[] = $value; } else { // if offset exists, then it is changing, else it is new offset $trigger = isset($this->array[$offset]) ? 'set' : 'add'; // fire trigger ("set" or "add") $this->trigger($trigger, $offset, $value); // add or change element $this->array[$offset] = $value; } } public function offsetExists($offset){ // fire "exists" trigger $this->trigger('exists', $offset); // return value return isset($this->array[$offset]); } public function offsetUnset($offset){ // fire "unset" trigger $this->trigger('unset', $offset); // unset element unset($this->array[$offset]); } public function addTrigger($trigger, $handler){ // if action is not availible and not proper handler provided then quit if(!(array_key_exists($trigger, $this->triggers) && is_callable($handler)))return false; // assign new trigger $this->triggers[$trigger] = $handler; return true; } public function removeTrigger($trigger){ // if wrong trigger name provided then quit if(!array_key_exists($trigger, $this->triggers))return false; // remove trigger $this->triggers[$trigger] = null; return true; } // call trigger method: // first arg - trigger name // other args - trigger arguments protected function trigger(){ // if no args supplied then quit if(!func_num_args())return false; // here is supplied args $arguments = func_get_args(); // extract trigger name $trigger = array_shift($arguments); // if trigger handler was assigned then fire it or quit return is_callable($this->triggers[$trigger]) ? call_user_func_array($this->triggers[$trigger], $arguments) : false; } } function check_trigger(){ print_r(func_get_args()); print_r(PHP_EOL); } function check_add(){ print_r('"add" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_get(){ print_r('"get" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_set(){ print_r('"set" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_exists(){ print_r('"exists" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } function check_unset(){ print_r('"unset" trigger'. PHP_EOL); call_user_func_array('check_trigger', func_get_args()); } $victim = new TriggerableArray(); $victim->addTrigger('get', 'check_get'); $victim->addTrigger('set', 'check_set'); $victim->addTrigger('add', 'check_add'); $victim->addTrigger('exists', 'check_exists'); $victim->addTrigger('unset', 'check_unset'); $victim['check'] = 'a'; $victim['check'] = 'b'; $result = $victim['check']; isset($victim['check']); unset($victim['check']); var_dump($result); ?> 

Показывает:

 "add" trigger Array ( [0] => check [1] => a ) "set" trigger Array ( [0] => check [1] => b ) "get" trigger Array ( [0] => check [1] => b ) "exists" trigger Array ( [0] => check ) "unset" trigger Array ( [0] => check ) string(1) "b" 

Я предполагаю, что вы можете изменить конструктор в этом коде с аргументом ссылки массива, чтобы иметь возможность передавать суперглобальный массив, например $_COOKIE . Другой способ – заменить $this->array на $_COOKIE непосредственно внутри класса. Кроме того, есть способ заменить callables анонимными функциями .

Как насчет того, чтобы никогда не менять переменную вообще.

Как только получить доступ к переменной через функцию (или метод класса), которая может обрабатывать любые «прослушивающие» задачи, которые вы хотели выполнить?

Зачем вам нужно: $variable = 5; ?

Когда вы могли бы сделать: setMyVariable(5) ?

 function setMyVariable($new_value) { $variable = $new_value; // do other stuff } 

Я думаю, что у вас есть некоторая ошибка в вашем мышлении, потому что только переменная может изменить ее значение в php, если вы ее измените. Поэтому, когда вы меняете свою переменную, запустите свой код.

Если вам нужен общий способ сделать это, я бы предложил инкапсулировать либо массив, либо его значения в класс. Но действительно нет веской причины, по которой вы не можете просто запустить sendCookie () после каждого изменения стоимости.

Думаю, смена слушателей для многопоточного программирования.