Now I've heard over and over again that a parent class should NOT rely on the child class to implement functionality
Exactly. And your switch
would be such an unwanted dependency, while a call to getPrice
would not, as long as it's defined as an abstract method in the parent and overridden in the children. Then the parent class does not need to know the concrete child classes and still can call their methods. If that sounds strange to you, read about polymorphism, it's important to understand this concept to understand OOP.
But your problem goes deeper:
Here is the main point: How do I call a child method if have only a productid? (In the above scenario, the Product class retrieves the type from the database in the constructor and populates the $type property accordingly.
Obviously you don't ever create instances of Chainsaw or Toothbrush. You can't create a product with new Product
and afterwards tell it "now you are a chainsaw". The actual type of an object is immutable. You tried to get around that with creating a new chainsaw inside of the product that was supposed to be a chainsaw just to have access to its price. This is horribly wrong and I think you already realized it.
This is why the factory pattern was suggested in the comments. A factory is a class that instantiates objects and decides which subtype is used based on parameters. Also it is a valid place for such a switch statement.
Example:
class ProductFactory
{
public function makeProduct($id)
{
$record = perform_your_database_lookup_here();
switch ($record['type']) {
case 'toothbrush':
return new Toothbrush($id, $record);
case 'chainsaw':
return new Chainsaw($id, $record);
}
}
}
$factory = new ProductFactory();
$product = $factory->makeProduct(123);
echo $product->getPrice();
For simplicity I put the database lookup in the factory. A better solution would be to have it completely separated from both classes, for example in a ProductTableGateway
class that would be responsible for all database queries related to the products table. The factory then would only recieve the result.
By the way, I'd also recommend to get rid of these subclasses in the end. No serious online shop has hard coded classes for each product type, instead different attribute sets are created dynamically and different price calculations are delegated to other classes. But this is an advanced topic, which would go too far now.