Pancho Pancho - 4 months ago 10
PHP Question

PHP - enabling contained object access to container object information

I have a compound object class which has a contained component class, and on instantiation I want the contained object to have real-time access to the compound object's methods and properties.

In the following example of "Car" and "Wheel" I have written, a dynamic relationship between diameter of Wheel and height of Car is established. Changing the height of the car automatically changes the diameter of the wheel appropriately, thus retaining the car's proportions:

class Car {
private $wheel;
private $heightM;

function __construct() {
$this->wheel = new Wheel($this);
}

final public function wheel() {
return $this->wheel;
}

final public function setHeight($metres) {
$this->heightM = $metres;
}

final public function height() {
return $this->heightM;
}

}

class Wheel {
private $car;

final public function __construct(Car &$car) { // by reference
$this->car = &$car; // pointer
}

final public function diameter() {
return $this->car->height() * 20 / 100;
}

}

$toyota = new Car();
$toyota->setHeight(1);
print "Car height = " . $toyota->height() . "m"
. "; Wheel diameter = " . $toyota->wheel()->diameter() . "m"
. PHP_EOL;
$toyota->setHeight(2);
print "Car height = " . $toyota->height() . "m"
. "; Wheel diameter = " . $toyota->wheel()->diameter() . "m"
. PHP_EOL;


which outputs:

Car height = 1m; Wheel diameter = 0.2m
Car height = 2m; Wheel diameter = 0.4m


This is exactly the result I want, however I am seeking guidance as to whether the above constitutes an acceptable PHP coding approach or whether there are better OOP/PHP approaches for achieving the same. Thanks!

EDIT: In an effort to make this question more specific: I am really looking for an answer such: NO - this approach will break PHP for reason A,B and C; or NO - please see formal design pattern X which is the recognised standard for achieving this functionality. Alternatively, YES - the use of pointers to achieve what you are trying to do here is perfectly acceptable (of course this is within the constraints that I do understand the risks pointers can present)

obe obe
Answer
  1. In your example, there is no need to pass $car by reference to Wheel's constructor. What are you trying to achieve by passing it by reference?

  2. Your code won't break PHP :)

  3. One thing you may want to ask yourself is whether Wheel needs to have such intimate knowledge of the Car it belongs to.

For example: does it really need access to other (future?) Car properties such as its maxSpeed? Its fuelConsumptionRate? Its price?

Does it need access to its startEngine() and parkHere() functionalities?

Moreover, in the future, is it possible that you'd want to put Wheels on other things, like SupermarketShoppingCart or Rollerblades?

Consider not having Wheel reference a Car at all. One thing you could do instead, is define an interface, say IWheelOwner, that would declare the properties and functionality that Wheels are likely to expect from their owners. Then have Car implement that interface. Then, you can still pass $car down to $wheel, but treat it as an interface:

/** @var IWheelOwner */
private $wheelOwner;

final public function __construct(IWheelOwner $wheelOwner) {
    $this->wheelOwner = $wheelOwner;
}
  1. You may want to consider doing it the other way around. You could have Car adjust the size of the Wheels whenever its own height changes. If you consider the 20% ratio as a "Wheel knowledge" that the Car should be unaware of - simply give Wheel an readjustDiameter($ownerHeight) function and call it whenever the Car's height is changed.