PHP OOP paradigm private array

i'm having some trouble with some part of the architecture of an API i am doing:

I have a parent class - Vehicle
i have several classes that extend Vehicle: Car, Truck, Bicicle, Motorcycle

I want some of the classes to have a property, doors, that is an array of Door objects. it's going to imply a private var $doors, and the methods getDoors(), getDoor($id), addDoor(Door $door), removeDoor($id)

As many of the classes won't have doors, i don't want to implement this in the Vehicle Class, but i don't want to repeat myself in all the classes that have doors.

So I thought using the magic method __call in Vehicle and a helper class Statics like this:

class Vehicle {
    ...
    public function __call($method, $args){
        if(!Statics::$method($this, $args)){
            throw new Exception('class ' . $this->get_class() . ' does not implement method ' . $method);           
        }
    }
}

class Car extends Vehicle {
    ...
    public $doors = array();
}

class Motorcycle extends Vehicle {
    ...
}

class Statics {
    function addDoor($vehicle, $args){
        try{
            array_push($vehicle->doors, $args[0]);
        } catch {
            return false;
        }
    }
}

In this way, if we try the addDoor method in the a Motorcycle object, the exception will be thrown.
This works like a charm, i just have a question:

How can I turn the #doors array accessible to the Statics class, but totally private to the programmer?

Upvotes: 1

Views: 224

Answers (4)

umlcat
umlcat

Reputation: 4143

The previous solutions like using an interface, restricting "doors" to a subclasss, or a composite class are ok.

Sometimes, when defining a class hierarchy, you may find a feature (method or property) that maybe implemented, or may not be implemented, in descendant classes. My proposed solution is, add that feature in the base class as an "abstract or virtual feature" and let each class to decide to override or not.

// check that in base classes, 
// is common to have most stuff private or protected, not public
class Vehicle {
    ...

    // using a protected variable field for properties
    protected $_doors = array();
    protected $_wings = array();

    // you may want to use the methods or the "__call" way,
    // Important, these are intentionally "protected", not "public"
    protected /* array */ getDoors()
    {
      return $this->_doors; 
    } // /* array */ getDoors(...)

    protected /* array */ setDoors(/* array */ p_doors)
    {
      $this->_doors = p_doors;  
    } // /* array */ SetDoors(...)

    protected /* void */ function addDoor(/* array */ $args)
    {
        array_push($this->doors, $args[0]);
    } // /* void */ function addDoor(...)

    // you may want to use the methods or the "__call" way,
    // Important, these are intentionally "protected", not "public"
    protected /* array */ getWings()
    {
      return $this->_wings; 
    } // /* array */ getWings(...)

    protected /* array */ setWings(/* array */ p_wings)
    {
      $this->_wings = p_wings;  
    } // /* array */ SetWings(...)

    protected /* void */ function addWing(/* array */ $args)
    {
        array_push($this->wings, $args[0]);
    } // /* void */ function addWing(...)

    // these one is always public in all classes
    public /* bool */ function supportsDoors()
    {
      return false;
    }

    // these one is always public in all classes
    public /* bool */ function supportsWings()
    {
      return false;
    }   
} // class Vehicle

class Car extends Vehicle {
    // these one is always public in all classes
    public /* bool */ function supportsDoors()
    {
      return true;
    }

    public /* array */ getDoors()
    {
      return $this->_doors; 
    } // /* array */ getDoors(...)

    // promoted from "protected" to "public"    
    public /* array */ setDoors(/* array */ p_doors)
    {
      $this->_doors = p_doors;  
    } // /* array */ SetDoors(...)

    // promoted from "protected" to "public"    
    public /* void */ function addDoor(/* array */ $args)
    {
        array_push($this->doors, $args[0]);
    } // /* void */ function addDoor(...)   
} // class Car 

class JetPack extends Vehicle {
    // these one is always public in all classes
    public /* bool */ function supportsWings()
    {
      return true;
    }   

    // promoted from "protected" to "public"    
    public /* array */ getWings()
    {
      return $this->_wings; 
    } // /* array */ getWings(...)

    // promoted from "protected" to "public"    
    public /* array */ setWings(/* array */ p_wings)
    {
      $this->_wings = p_wings;  
    } // /* array */ SetWings(...)

    public /* void */ function addWing(/* array */ $args)
    {
        array_push($this->wings, $args[0]);
    } // /* void */ function addWing(...)

} // class JetPack


class Boeing extends Vehicle {

    // these one is always public in all classes
    public /* bool */ function supportsDoors()
    {
      return true;
    }

    // these one is always public in all classes
    public /* bool */ function supportsWings()
    {
      return true;
    }   


    public /* array */ getDoors()
    {
      return $this->_doors; 
    } // /* array */ getDoors(...)

    // promoted from "protected" to "public"    
    public /* array */ setDoors(/* array */ p_doors)
    {
      $this->_doors = p_doors;  
    } // /* array */ SetDoors(...)

    // promoted from "protected" to "public"    
    public /* void */ function addDoor(/* array */ $args)
    {
        array_push($this->doors, $args[0]);
    } // /* void */ function addDoor(...)   

    // promoted from "protected" to "public"    
    public /* array */ getWings()
    {
      return $this->_wings; 
    } // /* array */ getWings(...)

    // promoted from "protected" to "public"    
    public /* array */ setWings(/* array */ p_wings)
    {
      $this->_wings = p_wings;  
    } // /* array */ SetWings(...)

    public /* void */ function addWing(/* array */ $args)
    {
        array_push($this->wings, $args[0]);
    } // /* void */ function addWing(...)

} // class JetPack

Resume: "Doors" & "Wings" are declared as a "protected virtual" in the base class, and hence, all descendant classes have it as protected, but only some classes implement that feature, and promote to public the features, wheter methods, or properties.

As an additional remark, I personally dislike using the "quick & dirty virtual" properties and methods, way of PHP, and instead, using explicit "getMyProperty" & "setMyProperty" methods, or "myMethod()", because are a best practice. I suggest to avoid using these common "_call" feature._

Upvotes: 1

Ben
Ben

Reputation: 305

instead of using __call, you can create a new class VehicleWithDoor which extends Vehicle, and contains door methods you wish to implement :

class VehicleWithDoor extends Vehicle {
    protected $_doors = array();

    // door methods
}

class Car extends VehicleWithDoor {

}

-----------------------------------------

EDIT 2 (BETTER SOLUTION i hope) :

u can add and implements some another interface to your properties

interface Vehicle_Property {
     public function getIndex();
}

class Vehicle_Property_Doors implements Vehicle_Property {
     protected $_index = "doors";

     public function getIndex()
     {
          return (string)$this->_index;
     }

     public function open()
     {

     }

     public function close()
     {

     }
}

class Vehicle_Property_Wings {
     protected $_index = "wings";

     public function getIndex()
     {
          return (string)$this->_index;
     }

     public function fly()
     {

     }

     public function backToTheFuture()
     {

     }
}

class Vehicle {
    protected $_properties = array();

    public function addProperty(Vehicle_Property $property)
    {
         $this->_properties[$property->getIndex()] = $property;
    }

    public function removeProperty($key)
    {
         if (!array_key_exists($key, $this->_properties)
         {
              return false;
         }

         return unset($this->_properties[$key]);
    }

    public function getProperty($key)
    {
         if (!array_key_exists($key, $this->_properties)
         {
              return false;
         }

         return $this->_properties[$key];
    }

    protected function getProperties()
    {
        return (array)$this->_properties;
    }
}

Upvotes: 1

Evert
Evert

Reputation: 99525

Your on the correct way. You've found an issue where the standard inheritance model doesn't work (unless you had multiple inheritance support). The correct answer in this case is to go to a composite model.

Instead of using a Statics:: class, you should simply create a 'doors' object. The problem with this Static:: approach is that you cannot subclass it and override features, if needed. If you turned 'Doors' into an object, and create it upon instantiation of any Vehicle that requires it, you retain the flexibility OOP provides.

This is a very abstract problem though, which you've probably intended. For the actual design problem you're facing the design may be more obvious.

Upvotes: 2

linepogl
linepogl

Reputation: 9335

The simplest solution is to move all the static functions of the Statics class into the abstract Vehicle class and change them to protected. Also, change all the private members into protected.

class Vehicle {
  ...
  protected function addDoor($args){
    try{
        array_push($this->doors, $args[0]);
    } catch {
        return false;
    }
  }
  ...
}

class Car {
  ...
  protected $doors = array();
  ...
}

class Motorcycle extends Vehicle {
  ...
}

It is kind of inverted logic here, because the abstract base class has knowledge of the internals of its subclasses, but strangely it works in PHP. The correct OOP way would be to create protected abstract methods to access these members.

abstract class Vehicle {
  ...
  protected abstract function &GetDoors();
  protected function addDoor($args){
    try{
        array_push($this->GetDoors(), $args[0]);
    } catch {
        return false;
    }
  }
  ...
}

class Car {
  ...
  protected function &GetDoors(){ return $this->doors; }
  ...
}

class Motorcycle extends Vehicle {
  ...
  protected function &GetDoors(){ throw new Exception(); }
  ...
}

Yet, you you are trying to achieve (not repeating the code) can be implemented directly by using Traits, the new feature of PHP 5.4. Maybe you should wait a little bit...

https://wiki.php.net/rfc/horizontalreuse

Upvotes: 1

Related Questions