views:

2948

answers:

6

Hi everybody, I'm building an ORM library with reuse and simplicity in mind; everything goes fine except that I got stuck by a stupid inheritance limitation. Please consider the code below:

class BaseModel {
    /*
     * Return an instance of a Model from the database.
     */
    static public function get (/* varargs */) {
        // 1. Notice we want an instance of User
        $class = get_class(parent); // value: bool(false)
        $class = get_class(self);   // value: bool(false)
        $class = get_class();       // value: string(9) "BaseModel"
        $class =  __CLASS__;        // value: string(9) "BaseModel"

        // 2. Query the database with id
        $row = get_row_from_db_as_array(func_get_args());

        // 3. Return the filled instance
        $obj = new $class();
        $obj->data = $row;
        return $obj;
    }
}

class User extends BaseModel {
    protected $table = 'users';
    protected $fields = array('id', 'name');
    protected $primary_keys = array('id');
}
class Section extends BaseModel {
    // [...]
}

$my_user = User::get(3);
$my_user->name = 'Jean';

$other_user = User::get(24);
$other_user->name = 'Paul';

$my_user->save();
$other_user->save();

$my_section = Section::get('apropos');
$my_section->delete();

Obviously, this is not the behavior I was expecting (although the actual behavior also makes sense).. So my question is if you guys know of a mean to get, in the parent class, the name of child class.

+5  A: 

in short. this is not possible. in php4 you could implement a terrible hack (examine the debug_backtrace()) but that method does not work in PHP5. references:

edit: an example of late static binding in PHP 5.3 (mentioned in comments). note there are potential problems in it's current implementation (src).

class Base {
    public static function whoAmI() {
        return get_called_class();
    }
}

class User extends Base {}

print Base::whoAmI(); // prints "Base"
print User::whoAmI(); // prints "User"
Owen
Yes, I just read about `debug_backtrace()`.. A possible solution would be to use *late static binding* from PHP 5.3 but that's not a possibility in my case. Thank you.
Benoit Myard
+2  A: 

It appears you might be trying to use a singleton pattern as a factory pattern. I would recommend evaluating your design decisions. If a singleton really is appropriate, I would also recommend only using static methods where inheritance is not desired.

class BaseModel
{

 public function get () {
  echo get_class($this);

 }

 public static function instance () {
  static $Instance;
  if ($Instance === null) {
   $Instance = new self;

  }
  return $Instance;
 }
}

class User
extends BaseModel
{
 public static function instance () {
  static $Instance;
  if ($Instance === null) {
   $Instance = new self;

  }
  return $Instance;
 }
}

class SpecialUser
extends User
{
 public static function instance () {
  static $Instance;
  if ($Instance === null) {
   $Instance = new self;

  }
  return $Instance;
 }
}


BaseModel::instance()->get();   // value: BaseModel
User::instance()->get();        // value: User
SpecialUser::instance()->get(); // value: SpecialUser
.. no. You didn't understand what I was trying to bo, but that's because I didn't explain it well :). Actually, I'm just trying to provide a static method (implemented in BaseModel) to get() an instance of a given Model (may it be User, Role or whatever). I will update the question..
Benoit Myard
Ok, updated. Of course the BaseModel class has much more methods, including some to keep track of what is changed in the object and UPDATE only what has chaged, etc... But thank you anyway :).
Benoit Myard
+2  A: 

Maybe this isn't actually answering the question, but you could add a parameter to get() specifing the type. then you can call

BaseModel::get('User', 1);

instead of calling User::get(). You could add logic in BaseModel::get() to check whether a get method exists in the subclass and then call that if you want to allow the subclass to override it.

Otherwise the only way I can think of obviously is by adding stuff to each subclass, which is stupid:

class BaseModel {
    public static function get() {
        $args = func_get_args();
        $className = array_shift($args);

        //do stuff
        echo $className;
        print_r($args);
    }
}

class User extends BaseModel {
    public static function get() { 
        $params = func_get_args();
        array_unshift($params, __CLASS__);
        return call_user_func_array( array(get_parent_class(__CLASS__), 'get'), $params); 
    }
}


User::get(1);

This would probably break if you then subclassed User, but I suppose you could replace get_parent_class(__CLASS__) with 'BaseModel' in that case

Tom Haigh
Actually, yes. This PHP limitation had the great advantage to force me to review my design. It will much more look like $connection->get('User', 24); since it allows multiple connections at the same time and is also semantically more correct. But you got a point :).
Benoit Myard
A: 

The problem is not a language limitation, it is your design. Never mind that you have classes; the static methods belie a procedural rather than object-oriented design. You're also using global state in some form. (How does get_row_from_db_as_array() know where to find the database?) And finally it looks very difficult to unit test.

Try something along these lines.

$db = new DatabaseConnection('dsn to database...');
$userTable = new UserTable($db);
$user = $userTable->get(24);
Preston
+1  A: 

You don't need to wait for PHP 5.3 if you're able to conceive of a way to do this outside of a static context. In php 5.2.9, in a non-static method of the parent class, you can do:

get_class($this);

and it will return the name of the child class as a string.

i.e.

class Parent() {
    function __construct() {
        echo 'Parent class: ' . get_class() . "\n" . 'Child class: ' . get_class($this);
    }
}

class Child() {
    function __construct() {
        parent::construct();
    }
}

$x = new Parent();

this will output:

Parent class: Parent
Child class: Child

sweet huh?

+1  A: 

In case you don't want to use get_called_class() you can use other tricks of late static binding (PHP 5.3+). But the downside in this case you need to have getClass() method in every model. Which is not a big deal IMO.

<?php

class Base 
{
    public static function find($id)
    {
        $table = static::$_table;
        $class = static::getClass();
        // $data = find_row_data_somehow($table, $id);
        $data = array('table' => $table, 'id' => $id);
        return new $class($data);
    }

    public function __construct($data)
    {
        echo get_class($this) . ': ' . print_r($data, true) . PHP_EOL;
    }
}

class User extends Base
{
    protected static $_table = 'users';

    public static function getClass()
    {
        return __CLASS__;
    }
}

class Image extends Base
{
    protected static $_table = 'images';

    public static function getClass()
    {
        return __CLASS__;
    }
}

$user = User::find(1); // User: Array ([table] => users [id] => 1)  
$image = Image::find(5); // Image: Array ([table] => images [id] => 5)
Parad0X