emersonthis
emersonthis

Reputation: 33398

How to design php class with properties that can only be set once

I'm writing a "parent" class that is meant to be extended. I want the extenders to set a handful of properties once when they define the class but I don't want those values to ever change after they've been initially set. What's the correct way to do this?

Update:

To clarify, I want the property to act as a constant that the extending class sets once but can't modify afterwards. For this reason I can't simply make it a private property with a getter method. The extending class could just add a setter, right?

Upvotes: 1

Views: 1782

Answers (2)

Luca Rainone
Luca Rainone

Reputation: 16468

Declare the property as private and set it via a protected setter. In this setter you can do your control

abstract class YourClass {
   private $myProperty;

   protected function setMyProperty($value) {
        if(is_null($this->myProperty)) {
             $this->myProperty = $value;
        }else {
             throw new Exception("You can set this property only once");
        }
   }

   protected function getMyProperty() {
          return $this->myProperty;
   }
}

In order to fetch the property you need also a protected getter (or public if you need to have access from external).

Another point of view:

There is a common rule in OOP: prefer composition over inheritance

Try to think a different approach: if you need a set of immutable properties, you can encapsulate them in an object, then use this object as component.

class MyClass {
    /**
     * @var MyComponent
     */
    protected $component;

    public function __construct($property) {
        $this->setComponent(new MyComponent($property));
    }

    protected function setComponent(MyComponent $myComponentInstance) {
        $this->component = $myComponentInstance;
    }

    public function doSomething() {
        echo $this->component->getMyProperty();
    }
}

class MyComponent {
    private $myProperty;

    public function __construct($property) {
        $this->myProperty = $property;
    }
    public function getMyProperty() {
        return $this->myProperty;
    }

}

$myClass = new MyClass("Hello World");

$myClass->doSomething();

EDIT:

Reading your edit, I think you want this:

abstract class MyAbstractClass {
    const FOO = 1;

    public function getFoo() {
        // hint: use "static" instead of "self"
        return static::FOO;
    }
}

class MyClass extends MyAbstractClass{
    // override
    const FOO = 2;
}

$myClass = new MyClass();

echo $myClass->getFoo(); // 2

Upvotes: 3

Gordon
Gordon

Reputation: 317147

You set the variables you dont want to be writable as private and then don't provide any mutators for them to the child classes, e.g.

abstract class Foo 
{    
    private $value;    

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

    protected function getValue() 
    {
        return $this->value;
    }
}

Depending on what $value is, you might need to clone it before returning it to the child class.

In your child class, you'd do

class Bar extends Foo 
{
    public function someMethodUsingValue()
    {
        $value = $this->getValue();
        // do something with $value
    }
}

$bar = new Bar(42);
$bar->someMethodUsingValue();

Because $value is private, Bar can only access it through the provided Getter of Foo, but cannot access it directly. This prevents Bar from changing it (as long as it's not an object of course, see note above about cloning).

If your child class needs a different constructor than the parent, you need to make sure it actually invokes the parent constructor with the to be immutable values, e.g. in Bar

public function __construct($value, $something)
{
    parent::__construct($value);
    $this->something = $something;
}

Also see http://php.net/manual/en/language.oop5.visibility.php


Yet another (and preferable) option would be to encapsulate the immutable value(s) in one or several classes and aggregate/compose them within the parent classes, instead of inheriting them:

class Bar
{
    private $data;

    public function __construct()
    {
        $this->data = new ImmutableValue(42);
    }

    public function getValue()
    {
        return $this->data->getValue();
    }
}

class ImmutableValue
{
    private $value;

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

    public function getValue()
    {
        return $this->value;
    }
}

As you can see, this one does not use inheritance. It's not needed. Refer to the following two articles for more details:


Since you mention constants in your question, you can obviously achieve the whole thing without using inheritance or composition and just set a real constant, e.g.

class Bar
{
    const VALUE = 42;
}

echo Bar::VALUE; // 42

or use a constant method:

class Bar
{
    public final function getValue() 
    {
        return 42;
    }
}

Making the getter final prevents any subclass from modifying the method.


Regarding your comments:

What would happen if Bar added a method like setMyProperty($value)? Will $this->myProperty be indefinite because it's private?

You would end up with a new public property set in the Bar instance with the same name. But the private instance var of the parent would be left unchanged.

class Foo 
{
    private $value = 1;
}

class Bar extends Foo 
{
    public function setValue($value)
    {
        $this->value = $value;
    }
}

$bar = new Bar;
$bar->setValue(42);
var_dump($bar);

Would give you

object(Bar)#1 (2) {
  ["value:private"]=>
  int(1)
  ["value"]=>
  int(42)
}

So no, the extending class could not just add a setter. Private is private.


Also, is there a way to do this without passing the variables through the constructor? I'd like those attributes to be defined in the inherited class […]

You could utilize the pattern shown elsewhere on this page, where you add a setter method that allows setting only once and then raises an exception on subsequent calls. However, I don't like that since it's really what a constructor is for. And I don't see the issue with passing the variables through the constructor. Consider this:

public function __construct($somethingElse)
{
    parent::__construct(42);
    $this->somethingElse = $somethingElse;
}

This way, $value is set from the constructor in the inherited class. There is no way to change it from the outside, nor from within Bar. And no additional logic required in some sort of setter.

[…] so they're version controlled.

I am not sure you really mean version controlled. If you want version control, utilize a proper system for that, e.g. git or mercurial or any other widely available VCS.


On a side note: there is an RFC, that aims to add Immutable classes and properties as a native language feature. However, it's still in Draft as of today.

Upvotes: 5

Related Questions