При использовании классов PHP DOM (DOMNode, DOMEElement и т. д.) Я заметил, что они действительно обладают свойствами только для чтения. Например, я могу прочитать свойство $ nodeName DOMNode, но не могу писать в него (если я это сделаю, PHP выдает фатальную ошибку).
Как я могу создать собственные свойства только для чтения в PHP?
Это было рассмотрено как RFC в 2014 году (wiki.php.net/rfc/readonly_properties), но было отозвано после значительного количества разногласий (markmail.org/message/7l3ci3sboma2nlzq). Мне бы хотелось, чтобы readonly использовался как ключевое слово для свойств, которое значительно упростило бы жизнь, вместо того, чтобы постоянно определять геттеры или использовать шаблон прокси.
Дубликат: Как реализовать в PHP переменную-член только для чтения?
В настоящее время существует проект RFC (27 июня 2020 г.), предлагающий добавить функции только для чтения в PHP 8.0: «Это ранний черновик, в настоящее время мы ждем отзывов.» Адрес электронной почты автора указан, и я считаю, что вы можете отправить им предложения по электронной почте.






Сделать это можно так:
class Example {
private $__readOnly = 'hello world';
function __get($name) {
if ($name === 'readOnly')
return $this->__readOnly;
user_error("Invalid property: " . __CLASS__ . "->$name");
}
function __set($name, $value) {
user_error("Can't set property: " . __CLASS__ . "->$name");
}
}
Используйте это только тогда, когда вам это действительно нужно - это медленнее, чем обычный доступ к свойствам. Для PHP лучше всего принять политику использования только методов установки для изменения свойства извне.
Действительно! Метод __get работает очень медленно. У меня был класс конфигурации, который использовал что-то вроде этого, чтобы можно было получить доступ к каждой частной переменной, но не изменить. Когда я пропустил свой код через профилировщик, я был шокирован временем, которое он использовал :( Очень грустно. Мне хотелось, чтобы у php был атрибут readonly.
Без __get это можно реализовать только внутри (в расширении).
Странно то, что они документируют их как readonly public, а не как частные.
Прошло несколько лет. Выпущены новые версии PHP. Метод __get все еще слишком медленный в PHP? Я использую PHP 7.0
Если все свойства являются частными, то метод __set() не нужен, так как PHP сам поймает частное свойство. Альтернативный подход - сделать свойства public, а затем использовать Только метод __set(), чтобы отловить любую попытку установить свойства. В этом случае метод __get() не нужен, поскольку свойства можно читать напрямую.
Class PropertyExample {
private $m_value;
public function Value() {
$args = func_get_args();
return $this->getSet($this->m_value, $args);
}
protected function _getSet(&$property, $args){
switch (sizeOf($args)){
case 0:
return $property;
case 1:
$property = $args[0];
break;
default:
$backtrace = debug_backtrace();
throw new Exception($backtrace[2]['function'] . ' accepts either 0 or 1 parameters');
}
}
}
Вот как я имею дело с получением / установкой своих свойств, если вы хотите сделать Value () только для чтения ... тогда вам просто нужно вместо этого сделать следующее:
return $this->m_value;
Где, поскольку функция Value () прямо сейчас будет либо получать, либо устанавливать.
Я вижу, вы уже получили ответ, но для тех, кто все еще ищет:
Просто объявите все переменные, доступные только для чтения, как закрытые или защищенные и используйте волшебный метод __get () следующим образом:
/**
* This is used to fetch readonly variables, you can not read the registry
* instance reference through here.
*
* @param string $var
* @return bool|string|array
*/
public function __get($var)
{
return ($var != "instance" && isset($this->$var)) ? $this->$var : false;
}
Как видите, я также защитил переменную экземпляра $ this->, поскольку этот метод позволит пользователям читать все объявленные переменные. Чтобы заблокировать несколько переменных, используйте массив с in_array ().
Но частные свойства, отображаемые только с использованием __get (), не видны функциям, которые перечисляют элементы объекта - например, json_encode ().
Я регулярно передаю объекты PHP в Javascript с помощью json_encode (), поскольку это хороший способ передать сложные структуры с большим количеством данных, заполненных из базы данных. Я должен использовать общедоступные свойства в этих объектах, чтобы эти данные передавались в Javascript, который их использует, но это означает, что эти свойства должны быть общедоступными (и, следовательно, существует риск того, что другой программист не на той же длине волны (или, возможно, я после плохой ночи) мог бы изменить их напрямую). Если я сделаю их закрытыми и использую __get () и __set (), то json_encode () их не увидит.
Разве не было бы неплохо иметь ключевое слово доступности "только для чтения"?
Если переменная не предназначена для непосредственного редактирования, несмотря на то, что она является public, PHP-программисты часто используют соглашение $pleaseTouch по сравнению с $_doNotTouch, чтобы указать, следует ли полагаться на данное свойство извне или нет.
Класс, реализующий интерфейс JsonSerializable, может иметь настраиваемое свойство, которое будет закодировано путем определения его с помощью метода jsonSerialize. Вы можете показать там частные свойства, которые хотите закодировать.
Для тех, кто ищет способ раскрытия ваших частных / защищенных свойств для сериализации, если вы решите использовать метод получения, чтобы сделать их доступными только для чтения, вот способ сделать это (@Matt: для json в качестве примера):
interface json_serialize {
public function json_encode( $asJson = true );
public function json_decode( $value );
}
class test implements json_serialize {
public $obj = null;
protected $num = 123;
protected $string = 'string';
protected $vars = array( 'array', 'array' );
// getter
public function __get( $name ) {
return( $this->$name );
}
// json_decode
public function json_encode( $asJson = true ) {
$result = array();
foreach( $this as $key => $value )
if ( is_object( $value ) ) {
if ( $value instanceof json_serialize )
$result[$key] = $value->json_encode( false );
else
trigger_error( 'Object not encoded: ' . get_class( $this ).'::'.$key, E_USER_WARNING );
} else
$result[$key] = $value;
return( $asJson ? json_encode( $result ) : $result );
}
// json_encode
public function json_decode( $value ) {
$json = json_decode( $value, true );
foreach( $json as $key => $value ) {
// recursively loop through each variable reset them
}
}
}
$test = new test();
$test->obj = new test();
echo $test->string;
echo $test->json_encode();
Стоит отметить, что PHP теперь включает интерфейс JsonSerializable начиная с версии 5.4.
Вот способ визуализировать все свойства вашего класса read_only извне, у унаследованного класса есть доступ на запись ;-).
class Test {
protected $foo;
protected $bar;
public function __construct($foo, $bar) {
$this->foo = $foo;
$this->bar = $bar;
}
/**
* All property accessible from outside but readonly
* if property does not exist return null
*
* @param string $name
*
* @return mixed|null
*/
public function __get ($name) {
return $this->$name ?? null;
}
/**
* __set trap, property not writeable
*
* @param string $name
* @param mixed $value
*
* @return mixed
*/
function __set ($name, $value) {
return $value;
}
}
протестировано на php7
Чтобы это работало, ваши свойства должны быть недоступны, поэтому вам нужно изменить public на private или protected.
@story ты тестировал? поскольку у меня есть, и он работает, $ foo и $ bar доступны для чтения извне, но не для записи (кроме класса и унаследованного класса). Не знаю, задокументировано ли это, но извне __get и __set trap имеют приоритет. полезность public здесь предназначена для самоанализа IDE.
Да, я это тестировал. Вы можете получить и установить просто отлично, но это не пройдет через ваши магические методы. stackoverflow.com/questions/4713680/…
странно, сработало, как я ожидал для меня (я использовал public для самоанализа IDE). Я изменяю общедоступный на защищенный, как вы сказали
разве это не должно быть похоже на return $this->$name ?: null;?
@mrReiha, я считаю, что ответ на ваш вопрос - «нет», потому что тогда вы не могли бы вернуть значение переменных, которые оцениваются как false в логическом контексте, например 0 или '' ... вместо этого вы получите нулевое значение.
@ scott8035, тогда я не уверен, что я вообще знаю результат ??. Я думал, что это переведено как «проверить значение $ name и, если оно истинно, вернуть его, в противном случае вернуть ноль». поправьте меня, если я не на той странице.
@mrReiha, а что, если переменная содержит строку нулевой длины или нулевое значение? В этом случае он оценит значение false и вернет ноль вместо фактического значения, содержащегося в переменной.
@ scott8035, значит, вы говорите, что ?? не будет действовать как оператор if (). Он проверит, имеет ли эта переменная значение (что угодно, а не только правдиво) или нет. Это верно?
@mrReiha, это ?? возвращает левое выражение, если аргумент существуют равен ненулевой. В противном случае возвращается правильное выражение. Он действует как оператор if (), состоящий из двух частей: if (isset (expr1) &&! Is_null (expr1)) {return expr1; } else {return expr2; }.
Кажется, что «только для чтения» - это особое ключевое слово, которое можно использовать только с классами, скомпилированными в PHP. К сожалению, "общедоступное только для чтения" было бы отличным способом избежать использования __get () и __set ().