PHP Readonly Properties?

PhpProgramming Languages

Php Problem Overview


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?

Php Solutions


Solution 1 - Php

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.

Solution 2 - Php

Since PHP 8.1 there are implemented native readonly properties

Documentation

You can initialize readonly property only once during the declaration of the property.

class Test {
    public readonly string $prop;

    public function __construct(string $prop) {
        $this->prop = $prop;
    }
}

--

class Test {
    public function __construct(
        public readonly string $prop,
    ) {}
}

Trying to modify the readonly propety will cause following error:

Error: Cannot modify readonly property Test::$prop

Solution 3 - Php

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?

Solution 4 - Php

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().

Solution 5 - Php

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

Solution 6 - Php

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();

Solution 7 - Php

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmazniakView Question on Stackoverflow
Solution 1 - Phptoo much phpView Answer on Stackoverflow
Solution 2 - PhpJsowaView Answer on Stackoverflow
Solution 3 - PhpMattView Answer on Stackoverflow
Solution 4 - PhpDanielView Answer on Stackoverflow
Solution 5 - PhpLe Petit Monde de PurexoView Answer on Stackoverflow
Solution 6 - PhpPrecasticView Answer on Stackoverflow
Solution 7 - PhpJonathan DahanView Answer on Stackoverflow