13

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?

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 Answers4

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

Outputs:

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&#39;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

magic methods

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