1

I'm aware of a pattern for adding a static counter to a class to count the number of instances of that class

class Vehicle
{
  private static $_count=0;

  public static function getCount() {
    return self::$_count;
  }

  public function __construct()
  {
    self::$_count++;
  }
}

What I would like to do is to add a couple of subclasses to Vehicle and count instances of those independently

class Bike extends Vehicle
{
   ...
}

class Car extends Vehicle 
{
   ...
}

So calling Bike::getCount() and Car::getCount() would get the count of the number of Bikes and Cars respectively

Is this possible without

  • repeating code in the subclasses?
  • setting up some kind of counting array keyed by class name in the superclass?
Arth
  • 12,789
  • 5
  • 37
  • 69
  • 3
    Definitlety that's violation of Single responsibility principle. Class must not count instances of itself. Create another class which will hold collection of items of whatever. – u_mulder May 28 '20 at 09:15

2 Answers2

3

You can keep an array of counts in the parent class rather than just a single integer, indexed using static:class. This will reference the class name of the current instance, unlike self::, which always references the class in which it's being used.

class Vehicle
{
  private static $counts = [];

  public static function getCount()
  {
      return self::$counts[static::class];
  }

  public function __construct()
  {
      // Using a reference here to avoid undefined-index notices
      $target =& self::$counts[static::class];
      $target++;
  }
}

class Bike extends Vehicle {}

new Bike;
var_dump(Bike::getCount());
// int(1)

Full demo here: https://3v4l.org/Qibd7

I'd also agree with @u_mulder's comment that this is a bit of an unusual pattern. A class definition should only be concerned with attributes of a single instance, and not store global state. Maintaining a collection of instances (even in a plain PHP array) would mean that you could count them independently. But that's up to you.

iainn
  • 16,826
  • 9
  • 33
  • 40
  • Cool, I touched on this in the question, but thanks for taking the time to code it out. That's actually much neater than I thought it would be, worth thinking about! I didn't know about that reference trick either – Arth May 28 '20 at 10:01
  • 1
    @Arth Ha, yes, that's the only part of the question I completely missed. Glad it was at least partly useful though. I guess my question would have been - why don't you want to do it that way? To me it seems like the natural solution. – iainn May 28 '20 at 10:08
2

A lot depends on where you define the count and how you access it. If you have 1 count in the base class, then there is only 1 count. If you have a count in each class, then you need to be aware of how to access the right value. Using self or static is discussed more What is the difference between self::$bar and static::$bar in PHP?

class Vehicle
{
    protected static $_count=0;

    public static function getCount() {
        return static::$_count;
    }

    public function __construct($type, $year)
    {
        // Access the count in the current class (Bike or Car).
        static::$_count++;
        // Access the count in this class
        self::$_count++;
    }
}

class Bike extends Vehicle
{
    protected static $_count=0;
}

class Car extends Vehicle
{
    protected static $_count=0;
}

This has both, and in the constructor, it increments them both. This means there is a total of all vehicles and of each type...

echo "Vehicle::getCount()=".Vehicle::getCount().PHP_EOL;
echo "Car::getCount()=".Car::getCount().PHP_EOL;
echo "Bike::getCount()=".Bike::getCount().PHP_EOL;
$a = new Car("a", 1);
echo "Vehicle::getCount()=".Vehicle::getCount().PHP_EOL;
echo "Car::getCount()=".Car::getCount().PHP_EOL;
echo "Bike::getCount()=".Bike::getCount().PHP_EOL;
$a = new Bike("a", 1);
echo "Vehicle::getCount()=".Vehicle::getCount().PHP_EOL;
echo "Car::getCount()=".Car::getCount().PHP_EOL;
echo "Bike::getCount()=".Bike::getCount().PHP_EOL;

gives (not very clear though)...

Vehicle::getCount()=0
Car::getCount()=0
Bike::getCount()=0
Vehicle::getCount()=1
Car::getCount()=1
Bike::getCount()=0
Vehicle::getCount()=2
Car::getCount()=1
Bike::getCount()=1
Nigel Ren
  • 56,122
  • 11
  • 43
  • 55
  • Thanks! Am I right in thinking it would make sense to make Vehicle abstract to avoid a weird double count if someone instantiated that one? – Arth May 28 '20 at 09:58
  • 1
    If there is never a need to create an instance of a class then defining it as abstract enforces it. This just depends on your design and how you use it. – Nigel Ren May 28 '20 at 09:59
  • `static::$_count++` statement in the constructor of `Vehicle` class is called [*Late Static Binding*](https://www.php.net/manual/en/language.oop5.late-static-bindings.php) – unclexo May 28 '20 at 10:24
  • Seems like if one or more instances were unset for whatever reason the count would become inaccurate. I haven't actually experimented with it so I'm not sure. If so, maybe a decrement in the destructor would handle it. – Don't Panic May 30 '20 at 17:50
  • @Don'tPanic, it can be complicated to maintain this sort of structure. Any method of tracking objects would need to manage the creation and deletion of objects. This then goes to the fact they are only in memory so their lifetime could potentially be quite short anyway. – Nigel Ren May 30 '20 at 17:54