Just like in JavaScript, many PHP developers find themselves in situations that warrant read-only properties. Unfortunately, the most common practice is to use a pseudo getter (again, like JavaScript) that's more hack than it is design.

Again, I'm talking about things like [cci]$object->getProperty()[/cci], which works, but is poor design.

Like its JavaScript cousin, you've probably come across an object that looks something like this:

* Represent a used car.
*/
class Car {
protected $odometer = 0;

/**
* Increment the odometer
*
* @param float $miles
*/
public function drive( $miles ) {
$this?>odometer += abs( $miles );
}

/**
* Get a protected odometer reading
*
* @return float
*/
public function getOdometer() {
return $this->odometer;
}
}

$subaru = new Car();
$subaru->drive( 500 );
echo $subaru->getOdometer(); // 500

Like the JavaScript equivalent, this works, but it's an ugly, unituitive hack. Don't use this.

The Alternative

If you have build a class in PHP 5, you've used what's called a "magic method" - namely, [cci]__construct()[/cci]. A magic method is one that's not directly exposed on an object instance, but which is invoked in certain specific situations instead. For example, invoking [cci]new Car()[/cci] would invoke [cci]Car::__construct()[/cci] if such a method exists.

As it so happens, PHP includes magic methods for both getters and setters of properties. Build these into your objects, and you can completely control the behavior of certain properties.

The code below is the same as before, but refactored to expose a read-only property on our object:

* Represent a used car.
*
* @property-read float $odometer
*/
class Car {
protected $odometer = 0;

/**
* Increment the odometer
*
* @param float $miles
*/
public function drive( $miles ) {
$this?>odometer += abs( $miles );
}

/**
* Magic getter for our object.
*
* @param string $field
*
* @throws Exception Throws an exception if the field is invalid.
*
* @return mixed
*/
public function __get( $field ) {
switch( $field ) {
case 'odometer':
return $this->odometer;
default:
throw new Exception( 'Invalid property: ' . $field );
}
}
}

$subaru = new Car();
$subaru->drive( 500 );
echo $subaru->odometer; // 500
$subaru->odometer = 0; // Fatal error: Cannot access protected property
echo $subaru->odometer; // 500

The [cci]@property-read[/cci] annotation in our class' docblock will tell your IDE that the class has a read-only property. If you depend on things like inline autocomplete, this added documentation can be a lifesaver during development!

Other Uses

Magic getters and setters can also help adjust an object's internal state when properties are read or set. As I noted in the JavaScript walkthrough yesterday, this is an extremely rare use case and obscures the functionality of your object and application. Don't use it unless you know exactly what you're doing and understand the potential consequences:

* Represent an elementary particle.
*
* @property-read array $position
* @property-read int $speed
*/
class Fermion {
protected $position = array( 50, 35 );

protected $speed = 75;

/**
* Magic getter for an elementary particle that
* obeys the Heisenburg Uncertainty Principle.
*
* @param string $field
*
* @throws Exception
*
* @return mixed
*/
public function __get( $field ) {
switch( $field ) {
case 'position':
unset( $this?>speed );

return implode( ',', $this->position );
case 'speed':
unset( $this->position);

return $this->speed;
default:
throw new Exception( 'Invalid property: ' . $field );
}
}
}

$electron = new Fermion();
echo $electron->position; // 50,35
echo $electron->speed; // Warning: Undefined property

$electron = new Fermion();
echo $electron->speed; // 75
echo $electron->position; // Warning: Undefined property