When trying to change it,throw an exception.
-
There is a [draft rfc](https://wiki.php.net/rfc/readonly_and_immutable_properties?s[]=readonly) currently (Jun 27, 2020) to propose adding `readonly` features to PHP 8.0: **"This is a early draft, currently looking for feedback."** The author's email is listed & I believe you can email them with suggestions. – Reed Jun 27 '20 at 18:54
7 Answers
I suppose a solution, for class properties, would be to :
- not define a property with the name that interests you
- use the magic
__get
method to access that property, using the "fake" name - define the
__set
method so it throws an exception when trying to set that property. - See Overloading, for more informations on magic methods.
For variables, I don't think it's possible to have a read-only variable for which PHP will throw an exception when you're trying to write to it.
For instance, consider this little class :
class MyClass {
protected $_data = array(
'myVar' => 'test'
);
public function __get($name) {
if (isset($this->_data[$name])) {
return $this->_data[$name];
} else {
// non-existant property
// => up to you to decide what to do
}
}
public function __set($name, $value) {
if ($name === 'myVar') {
throw new Exception("not allowed : $name");
} else {
// => up to you to decide what to do
}
}
}
Instanciating the class and trying to read the property :
$a = new MyClass();
echo $a->myVar . '<br />';
Will get you the expected output :
test
While trying to write to the property :
$a->myVar = 10;
Will get you an Exception :
Exception: not allowed : myVar in /.../temp.php on line 19

- 395,085
- 80
- 655
- 663
-
-
`__call` is called when you're trying to call a method that doesn't exit in the class (like `__get` is called when you're trying to read a property that doesn't exist in the class) -- see http://fr2.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods – Pascal MARTIN Feb 26 '10 at 18:27
-
@Pascal MARTIN ,thanks!I also know that you are an experienced user of symfony/doctrine,can you take a look at this post:http://stackoverflow.com/questions/2339800/how-can-i-fetch-the-entire-tree-in-a-single-query-with-doctrine ? – user198729 Feb 26 '10 at 18:29
-
You're welcome :-) ;;; Symfony ? hu, I have never really used Symfony -- I might be more of a ZF user ;;; Oh, that question is about Doctrine, I seen, and not Symfony ;;; and I've never used trees with Doctrine yet -- sorry... – Pascal MARTIN Feb 26 '10 at 18:32
-
Oh,that's fine.What about this one:http://stackoverflow.com/questions/2331723/how-does-the-local-field-for-relation-work-in-doctrine?I'm really having a hard time converting sql to YAML,especially the `local/foreign` settings in `relation` part.. – user198729 Feb 26 '10 at 18:37
-
Just checking by again, that's not a read only version. Add the method `public function modify() { $this->_data['myVar'] = 'bla'; }`. Or just extend `MyClass` and overload the `__set()`-method. It's just as "read only" as **a regular protected value is** - it all depends on the class methods' bodies. – chelmertz Apr 04 '11 at 17:33
class test {
const CANT_CHANGE_ME = 1;
}
and you refer it as test::CANT_CHANGE_ME

- 20,399
- 5
- 40
- 46
-
-
5
-
And if I want two instances of `test` with two different values for `CANT_CHANGE_ME`? This is a class variable, not a member and it will exist only in one copy... – Erk Oct 25 '18 at 21:13
-
@Erk you could hide the value behind a method in an interface, and implement that interface in all classes that should have a different value. That goes both for a "set once" member that is probably requested by this question, or something that's defined before run-time. – chelmertz Oct 28 '18 at 18:58
The short answer is you can't create a read-only object member variable in PHP.
In fact, most object-oriented languages consider it poor form to expose member variables publicly anyway... (C# being the big, ugly exception with its property-constructs).
If you want a class variable, use the const
keyword:
class MyClass {
public const myVariable = 'x';
}
This variable can be accessed:
echo MyClass::myVariable;
This variable will exist in exactly one version regardless of how many different objects of type MyClass
you create, and in most object-oriented scenarios it has little to no use.
If, however, you want a read-only variable that can have different values per object, you should use a private member variable and an accessor method (a k a getter):
class MyClass {
private $myVariable;
public function getMyVariable() {
return $this->myVariable;
}
public function __construct($myVar) {
$this->myVariable = $myVar;
}
}
The variable is set in the constructor, and it's being made read-only by not having a setter. But each instance of MyClass
can have its own value for myVariable
.
$a = new MyClass(1);
$b = new MyClass(2);
echo $a->getMyVariable(); // 1
echo $b->getMyVariable(); // 2
$a->setMyVariable(3); // causes an error - the method doesn't exist
$a->myVariable = 3; // also error - the variable is private

- 1,159
- 15
- 9
-
Read-only fields wouldn't be poor at all in a DTO object. A DTO is used only to give well documented types to a data structure. On the other hand adding a lot of logic to a data structure may make it much less flexible. It's just a chunk of data, not a logical object with well defined behavior. – Gherman Apr 07 '21 at 15:52
I cooked up a version, too, using a trait.
Though in this case, the property can still be set by its declaring class.
Declare a class like:
class Person {
use Readonly;
protected $name;
//simply declaring this means "the 'name' property can be read by anyone"
private $r_name;
}
And this is the trait I made:
trait Readonly {
public function readonly_getProperty($prop){
if (!property_exists($this,$prop)){
//pretty close to the standard error if a protected property is accessed from a public scope
// throw new \Error("Property '{$prop}' on class '".get_class($this)."' does not exist");
trigger_error('Undefined property: '.get_class($this).'::\$'.$prop,E_USER_NOTICE);
}
$allow_read = property_exists($this, 'r_'.$prop );
if ($allow_read){
$actual = $this->$prop;
return $actual;
}
throw new \Error("Cannot access non-public property '{$prop}' of class '".get_class($this)."'");
}
public function __get($prop){
return $this->readonly_getProperty($prop);
}
}
See the source code & test on my gitlab

- 14,703
- 8
- 66
- 110
-
I might prefer [my other version](https://stackoverflow.com/a/66587746/802469) that uses `@readonly` in the docblock, but I suspect its a few more cpu cycles. – Reed Mar 11 '21 at 18:21
I made another version that uses @readonly
in the docblock instead of private $r_propname
. This still doesn't stop the declaring class from setting the property, but will work for public readonly access.
Sample Class:
class Person {
use Readonly;
/**
* @readonly
*/
protected $name;
protected $phoneNumber;
public function __construct($name){
$this->name = $name;
$this->phoneNumber = '123-555-1234';
}
}
The ReadOnly
trait
trait Readonly {
public function readonly_getProperty($prop){
if (!property_exists($this,$prop)){
//pretty close to the standard error if a protected property is accessed from a public scope
trigger_error('Undefined property: '.get_class($this).'::\$'.$prop,E_USER_NOTICE);
}
$refProp = new \ReflectionProperty($this, $prop);
$docblock = $refProp->getDocComment();
// a * followed by any number of spaces, followed by @readonly
$allow_read = preg_match('/\*\s*\@readonly/', $docblock);
if ($allow_read){
$actual = $this->$prop;
return $actual;
}
throw new \Error("Cannot access non-public property '{$prop}' of class '".get_class($this)."'");
}
public function __get($prop){
return $this->readonly_getProperty($prop);
}
}
See the source code & test on my gitlab

- 14,703
- 8
- 66
- 110
I know this is an old question, but PASCAL's answer really helped me and I wanted to add to it a bit.
__get() fires not only on nonexistent properties, but "inaccessible" ones as well, e.g. protected ones. This makes it easy to make read-only properties!
class MyClass {
protected $this;
protected $that;
protected $theOther;
public function __get( $name ) {
if ( isset( $this->$name ) ) {
return $this->$name;
} else {
throw new Exception( "Call to nonexistent '$name' property of MyClass class" );
return false;
}
}
public function __set( $name ) {
if ( isset( $this->$name ) ) {
throw new Exception( "Tried to set nonexistent '$name' property of MyClass class" );
return false;
} else {
throw new Exception( "Tried to set read-only '$name' property of MyClass class" );
return false;
}
}
}

- 3,512
- 1
- 28
- 45