59

In using PHP's DOM classes (DOMNode, DOMEElement, etc) I have noticed that they possess truly readonly properties. For example, I can read the $nodeName property of a DOMNode, but I cannot write to it (if I do PHP throws a fatal error).

How can I create readonly properties of my own in PHP?

hakre
  • 178,314
  • 47
  • 389
  • 754
mazniak
  • 5,769
  • 6
  • 26
  • 23
  • 10
    It seems that "readonly" is a special keyword that can only be used with classes that are compiled into PHP. Unfortunate, because "readonly public" would be an excellent way to avoid using __get() and __set(). – shadowhand Jul 15 '09 at 02:37
  • 2
    This was considered as an RFC in 2014 (https://wiki.php.net/rfc/readonly_properties) but was withdrawn after a fair amount of contention (http://markmail.org/message/7l3ci3sboma2nlzq). I would love to have seen `readonly` as a keyword for properties, would make life a lot easier instead of constantly defining getters or using the Proxy Pattern – e_i_pi May 25 '17 at 05:11
  • Duplicate: [How to implement a read-only member variable in PHP?](https://stackoverflow.com/questions/2343790/how-to-implement-a-read-only-member-variable-in-php) – Reed Jun 27 '20 at 19:15
  • There is a [draft rfc](https://wiki.php.net/rfc/readonly_and_immutable_properties?s%5B%5D=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 19:15

6 Answers6

44

You can do it like this:

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");
    }
}

Only use this when you really need it - it is slower than normal property access. For PHP, it's best to adopt a policy of only using setter methods to change a property from the outside.

too much php
  • 81,874
  • 33
  • 123
  • 133
  • 6
    Indeed! The __get method is extremely slow. I had a config class that used something like this, so that every private variable could be accessed but not changed. When I ran my code through a profiler I was shocked at the time it consumed :( Really sad. I wished php had the readonly attribute. – AntonioCS Dec 21 '09 at 17:39
  • Without `__get`, this can only be implemented internally (in an extension). – Artefacto Jun 21 '10 at 14:19
  • The strange thing is that they document them like `readonly public` instead of private. – Rafael Barros Mar 01 '14 at 12:49
  • 4
    Some years have passed now. New PHP versions are released. Is __get method still too slow in PHP? I'm using PHP 7.0 – rineez Sep 13 '18 at 11:40
  • If the properties are all private, then the `__set()` method is not needed, since PHP will catch the property being private by itself. An alternative approach is to make the properties `public` then use *only* the `__set()` method to catch any attempt to set the properties. The `__get()` method is then not needed since the properties can be read directly. – Jason Jan 18 '21 at 14:12
12

But private properties exposed only using __get() aren't visible to functions that enumerate an object's members - json_encode() for example.

I regularly pass PHP objects to Javascript using json_encode() as it seems to be a good way to pass complex structures with lots of data populated from a database. I have to use public properties in these objects so that this data is populated through to the Javascript that uses it, but this means that those properties have to be public (and therefore run the risk that another programmer not on the same wavelength (or probably myself after a bad night) might modify them directly). If I make them private and use __get() and __set(), then json_encode() doesn't see them.

Wouldn't it be nice to have a "readonly" accessibility keyword?

Matt
  • 121
  • 1
  • 2
  • If a variable is not meant to be edited directly despite being `public`, PHP-programmers often use the convention `$pleaseTouch` versus `$_doNotTouch` to signal whether a given property should be relied upon externally versus not. – Seldom 'Where's Monica' Needy Oct 26 '15 at 22:55
  • 5
    A class that implements `JsonSerializable` interface can have custom property to be encoded by defining it through the `jsonSerialize` method. You can show the private properties you want to encode there. – Taufik Nurrohman Feb 28 '19 at 06:48
5

I see you have already got your answer but for the ones who still are looking:

Just declare all "readonly" variables as private or protected and use the magic method __get() like this:

/**
 * 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;
}

As you can see I have also protected the $this->instance variable as this method will allow users to read all declared variabled. To block several variables use an array with in_array().

K-Gun
  • 10,121
  • 2
  • 50
  • 56
5

Here is a way to render all property of your class read_only from outside, inherited class have write access ;-).

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;
    }
}

tested in php7

  • Your properties have to be inaccessible for this to work, so you have to change `public` to `private` or `protected`. – story Jan 31 '17 at 23:18
  • @story have you test ? because i have and it work, $foo and $bar are readable from outside but not writeable (except in class and inherited class). Don't know if it's documented but from outside __get and __set trap have priority. utility of public here is for IDE introspection. – Le Petit Monde de Purexo Feb 15 '17 at 14:13
  • Yeah, I tested it. You can get and set just fine, but it won't pass through your magic methods. http://stackoverflow.com/questions/4713680/php-get-and-set-magic-methods – story Feb 16 '17 at 17:48
  • weird, worked as I expected for me (I used public for IDE introspection). I edit public to protected like you said – Le Petit Monde de Purexo Feb 21 '17 at 10:24
  • shouldn't it be like `return $this->$name ?: null;`? – mrReiha Apr 23 '19 at 14:17
  • @mrReiha, I believe the answer to your question is "no" because then you couldn't return the value of variables that evaluate to false in a boolean context, like 0 or ''...you would get the null value instead. – scott8035 Aug 23 '19 at 09:54
  • @scott8035 then I'm not sure if I know the result of `??` at all. I thought it got translated to " check the value of $name and if it's true, return it, otherwise return null". correct me if I'm on the wrong page. – mrReiha Aug 23 '19 at 17:18
  • @mrReiha, what if the variable contained a zero-length string, or the value zero? In that case, it would evaluate to false and return the null instead of the actual value contained in the variable. – scott8035 Aug 24 '19 at 20:06
  • @scott8035 so basically, what you're saying is `??` won't act like an `if()` statement. It will check if that variable has a value ( anything, not just truthy ) or not. Is that correct? – mrReiha Sep 01 '19 at 20:05
  • 3
    @mrReiha, the ?? returns the left hand expression if the argument _exists_ and is _not null_. Otherwise it returns the right hand expression. It acts as a 2-part if() statement: if (isset(expr1) && !is_null(expr1)) { return expr1; } else { return expr2; }. – scott8035 Sep 04 '19 at 14:06
0

For those looking for a way of exposing your private/protected properties for serialization, if you choose to use a getter method to make them readonly, here is a way of doing this (@Matt: for json as an example):

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();
Precastic
  • 3,172
  • 1
  • 19
  • 27
-1
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');
            }
        }


}

This is how I deal with getting/setting my properties, if you want to make Value() readonly ... then you simply just have it do the following instead:

    return $this->m_value;

Where as the function Value() right now would either get or set.