2

Say I have an abstract class Entity, I also have a handful of abstract classes Provider, Model, Service,... all extending directly Entity. Finally, i have a last set of concrete classes ConcreteProvider, ConcreteModel, ... that extends respectively Provider, Model, ...

On each instance of Concrete* I want a method getId() that when called on an instance of a class which extends Provider ( like ConcreteProvider ), it returns the string 'Provider', and so on for Model and ...

The only way I found to achieve that is :

abstract class Entity {
    abstract function getId ();
}

abstract class Provider extends Entity {
    function getId () {
        return __CLASS__;
    }
}

abstract class Model extends Entity {
    function getId () {
        return __CLASS__;
    }
}

class ConcreteProvider extends Provider {
}

class ConcreteModel extends Model {
}

$cp = new ConcreteProvider();
echo $cp->getId() . "\n"; // returns the string Provider
$cm = new ConcreteModel();
echo $cm->getId(); // returns the string Model

Code duplication is here obvious. I'm looking for the same behaviour without duplicate. I guess it would be nice to put the method into Entity.

Have you an idea ? How would you do that ?

// Edit

Of course, there is a way to move getId into Entity :

abstract class Entity {
    function getId () {
      $class = get_called_class();
      while ( __CLASS__ !== get_parent_class($class) )
        $class = get_parent_class($class);
      return $class;
    }
}

But there is a lot of computation for, imo, nothing worth...

hakre
  • 193,403
  • 52
  • 435
  • 836
dader
  • 1,304
  • 1
  • 12
  • 31
  • 1
    is this level of code duplication unacceptable? You need some specialisation here to make the distinction between `Model` and `Provider`. – Greg K Jan 30 '12 at 21:57
  • If there's no other way, it'll be acceptable, for sure. – dader Jan 30 '12 at 22:20
  • 1
    I think your additional code in your edit sacrafices code intent and adds more code than your original duplication. It also makes your code PHP 5.3+ which may or may not be an issue. – Greg K Jan 31 '12 at 14:38
  • this is not an issue, moreover here i presented only two classes that duplicate the said code, irl there are 10+... – dader Feb 24 '14 at 12:56

5 Answers5

2

I'm not a big fan of magic, thus I recommend the straight-forward way

const ID = 'Provider';
public function getId () {
    return self::ID;
}

Or just

function getId () {
    return 'Provider';
}

but with the constant its easier to compare

$x->getId() == Provider::ID;
KingCrunch
  • 128,817
  • 21
  • 151
  • 173
  • I understand the point, but my goal is not to bother with maintaining one more value by hand... – dader Jan 30 '12 at 22:16
  • 1
    I see just two values and worth to say: Beware of too much magic. Apart from that its of course something you must decide yourself :) – KingCrunch Jan 31 '12 at 07:34
1

Here is the best I found, using my second proposition, and adding to it memoization.

abstract class Entity {
    private $subType = '';
    function getId () {
      if ( $this->subtype )
        return $this->subType;
      $class = get_called_class();
      while ( __CLASS__ !== get_parent_class($class) )
        $class = get_parent_class($class);
      return $this->subType = $class;
    }
}
dader
  • 1,304
  • 1
  • 12
  • 31
0

Update 2014:

If you are running PHP 5.4+ you could use a trait to share this function between Models and Providers.


What is to stop you from using the type system? Where you might currently call:

if ($object->getId() == 'Provider') {
    // provider specific code
}

Can't you use:

if ($object instanceof Provider) {
    // provider specific code
}

Admittedly using type conditionals like this defeats the purpose of polymorphism. I might be able to offer better advice with more context.

Greg K
  • 10,770
  • 10
  • 45
  • 62
  • Good point, but the code presented here is the root of my trouble, in fact getId is expected to return a string identifier. So there are no case where I do what you wrote, ony things like $id = $cp->getId(); – dader Jan 30 '12 at 22:08
  • 1
    @dader Then I would go with the solution in your question and follow the Three Strikes and Refactor rule: http://c2.com/cgi/wiki?ThreeStrikesAndYouRefactor – Greg K Jan 30 '12 at 22:13
  • haha, thanks for the link. But I think it's just something you cannot achieve otherways. – dader Jan 30 '12 at 22:29
  • Well it's good enough for Martin Fowler http://stackoverflow.com/a/2298357/122075 – Greg K Jan 30 '12 at 22:36
0
<?php
class Entity {
  public static function getId() {
    return get_called_class();
  }
}


class Model extends Entity {

}

class Provider extends Entity {

}

$a = new Model();
echo $a->getId(); //returns Model

$b = new Provider();
echo $b->getId(); //returns Provider

$c = new Entity();
echo $c->getId(); //returns Entity

Using late static binding. Your php version should be greater than 5.3.

http://www.php.net/manual/en/function.get-called-class.php

  • If he subclasses `Model` this method will defeat his inheritance chain and return the subclass name? – Greg K Jan 30 '12 at 22:06
  • it does not work, using late static binding leads to return the actually called class, so with ConcreteProvider, it will return ConcreteProvider, not provider – dader Jan 30 '12 at 22:10
  • Even though this solution didn't out-right prove to be the solution, kudos for suggesting LSB. – Mike Purcell Jan 30 '12 at 22:16
0

You should use PHP magic method __toString() for the classes you want to get class names for, then when you typecast the object as a string, it will return the class name:

abstract class Provider extends Entity {
    public function __toString() {
        return __CLASS__;
    }
}

abstract class Model extends Entity {
    public function __toString() {
        return __CLASS__;
    }
}

$cp = new ConcreteProvider();

var_dump($cp); // Outputs object

// Force cast of $cp object to a string, which calls __toString()
var_dump((string) $cp); // Outputs Provider

// Vanilla echo forces object cast to string
echo $cp; //Outputs Provider

$cm = new ConcreteModel();

var_dump($cm); // Outputs object

// Force cast of $cp object to a string, which calls __toString()
var_dump((string) $cm); // Outputs Model

// Vanilla echo forces object cast to string
echo $cm; 

With this approach you don't need to force a getId method upon child classes extending the parent Entity class.

Also, Using a getId() for this functionality is mis-leading imo, 99% of the time when I call on a classe's getId() method, I expect an int representing a specific instance of an object, fetched from a store (database, cache, flat-file).

Mike Purcell
  • 19,847
  • 10
  • 52
  • 89