I need to create an immutable class which is simply a member field container. I want its fields to be instantiated once in its constructor (the values should be given as parameters to the constructor). I want the fields to be public but immutable. I could have done it with Java using the final
keyword before each field. How is it done in PHP?
Asked
Active
Viewed 5,080 times
13

snakile
- 52,936
- 62
- 169
- 241
-
Why must the fields be public? – pb149 Sep 06 '11 at 08:38
-
@pete171 probably so they're read-only – fbstj Sep 06 '11 at 08:40
-
@FallingBullets Quite the opposite if they're public! – pb149 Sep 06 '11 at 08:42
-
I suggest to create getter functions for each data and store it in vars internally. This layout scales up much better, as you are bound to either do ugly hacks that makes your job harder if you don’t have the function where to play with the value you are returning... – Smar Sep 06 '11 at 09:02
4 Answers
20
You should use __set
and __get
magic methods and declare that property as protected or private:
/**
* @property-read string $value
*/
class Example
{
private $value;
public function __construct()
{
$this->value = "test";
}
public function __get($key)
{
if (property_exists($this, $key)) {
return $this->{$key};
} else {
return null; // or throw an exception
}
}
public function __set($key, $value)
{
return; // or throw an exception
}
}
Example:
$example = new Example();
var_dump($example->value);
$example->value = "invalid";
var_dump($example->value);
string(4) "test"
string(4) "test"
@property-read
should help your IDE acknowledge existence of this magic property.

sanmai
- 29,083
- 12
- 64
- 76
-
For improved IDE support (code completion), consider adding the [@property PHPDoc tag](http://docs.phpdoc.org/references/phpdoc/tags/property.html) to the class, if the public members are fixed. E.g.: `/** @property string $value */` – sudoqux Oct 24 '18 at 10:52
0
<?php
declare(strict_types=1);
final class Immutable
{
/** @var string */
private $value;
public static function withValue(string $value): self
{
return new self($value);
}
public function __construct(string $value)
{
$this->value = $value;
}
public function value(): string
{
return $this->value;
}
}
// Example of usage:
$immutable = Immutable::withValue("my value");
$immutable->value(); // You can get its value but there is no way to modify it.

Chemaclass
- 1,933
- 19
- 24
0
You could use the __set()
magic method and throw an exception when someone tries to set a property directly.
class ClassName {
public function __set($key, $value) {
throw new Exception('Can't modify property directly.');
}
}
However, this would prevent modification of properties regardless of whether they're public or not.

Martin Bean
- 38,379
- 25
- 128
- 201
-
Given that properties should be declared private/protected anyway, that doesn't add anything useful. `__get()` on the other hand, is what we want. – Mchl Sep 06 '11 at 08:43
-
5__set() is run when writing data to inaccessible properties. It doesn't work with public – jbrond Sep 06 '11 at 08:53
0
so you can do better - if you have a dinamyc count of fields
class ClassName {
private $fields = array();
// use class : $cl = new ClassName(array('f'=>2,'field_4'=>5,''12));
// echo $cl->field_4; echo $cl->f;
public function __construct($data= array())
{
if (!is_array($data) || !count($data)) throw new Exception('Not enough args')
foreach ($data as $key=>$val)
{
if (is_numeric($key))
$this->fields['field_'.$key] = $val;
else
$this->fields[$key] = $val;
}
}
/* in this case you can use this class like $cl = new ClassName(12,14,13,15,12); echo $cl->field_1;
public function __construct()
{
$ata = funcs_get_args();
if (!count($data)) throw new Exception('Not enough args')
foreach ($data as $key=>$val)
{
if (is_numeric($key))
$this->fields['field_'.$key] = $val;
else
$this->fields[$key] = $val;
}
}
*/
public function __get($var) {
if (isset($this->fields[$var]))
return $this->fields[$var];
return false;
//or throw new Exception ('Undeclared property');
}
}

ZigZag
- 539
- 1
- 8
- 19