【问题标题】:Getting the name of a child class in the parent class (static context)获取父类中子类的名称(静态上下文)
【发布时间】:2010-09-21 22:22:08
【问题描述】:

我正在构建一个考虑重用和简单性的 ORM 库;一切都很好,除了我被一个愚蠢的继承限制卡住了。请考虑以下代码:

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();

显然,这不是我所期望的行为(尽管实际行为也很有意义)。所以我的问题是你们是否知道在父类中获取子类名称的方法。

【问题讨论】:

    标签: php inheritance static-methods


    【解决方案1】:

    简而言之。这不可能。在 php4 中,您可以实现一个可怕的 hack(检查 debug_backtrace()),但该方法在 PHP5 中不起作用。参考资料:

    edit:PHP 5.3 中后期静态绑定的示例(在 cmets 中提到)。请注意,它的当前实现存在潜在问题 (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"
    

    【讨论】:

    • 是的,我刚刚读到debug_backtrace().. 一个可能的解决方案是使用 PHP 5.3 中的 late static binding ,但在我的情况下这是不可能的。谢谢。
    【解决方案2】:

    看来您可能正在尝试将单例模式用作工厂模式。我建议评估您的设计决策。如果单例确实合适,我还建议仅在不需要需要继承的情况下使用静态方法。

    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
    

    【讨论】:

    • .. 没有。你不明白我在说什么,但那是因为我没有很好地解释它:)。实际上,我只是想提供一个静态方法(在 BaseModel 中实现)来获取()给定模型的实例(可能是用户、角色或其他)。我会更新问题..
    • 好的,更新了。当然 BaseModel 类有更多的方法,包括一些用于跟踪对象中更改的内容和仅更新已更改的内容等...但无论如何谢谢你:)。
    【解决方案3】:

    也许这实际上并没有回答问题,但您可以在 get() 中添加一个参数来指定类型。那你就可以打电话了

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

    而不是调用 User::get()。您可以在 BaseModel::get() 中添加逻辑以检查子类中是否存在 get 方法,然后在您希望允许子类覆盖它时调用它。

    否则我能想到的唯一方法显然是向每个子类添加东西,这很愚蠢:

    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);
    

    如果您随后将 User 子类化,这可能会中断,但我想您可以在这种情况下将 get_parent_class(__CLASS__) 替换为 'BaseModel'

    【讨论】:

    • 其实是的。这个 PHP 限制有很大的优势,迫使我重新审视我的设计。它看起来更像 $connection->get('User', 24);因为它允许同时进行多个连接,并且在语义上也更正确。但你说得对:)。
    • array_shift 似乎很慢,因为它必须更改数组的每个键...只需 $args[0] 代替;
    【解决方案4】:

    问题不是语言限制,而是您的设计。别介意你有课;静态方法掩盖了过程而不是面向对象的设计。您还以某种形式使用全局状态。 (get_row_from_db_as_array() 怎么知道在哪里可以找到数据库?)最后看起来单元测试非常困难。

    尝试这些方法。

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

    【讨论】:

      【解决方案5】:

      如果您能够想出一种在静态上下文之外执行此操作的方法,则无需等待 PHP 5.3。在 php 5.2.9 中,在父类的非静态方法中,可以这样做:

      get_class($this);
      

      它会将子类的名称作为字符串返回。

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

      这将输出:

      Parent class: Parent
      Child class: Child
      

      甜吗?

      【讨论】:

      • 如果你使用抽象类,这是必须知道的。
      • 我认为$x = new Parent(); 应该是$x = new Child();
      • 这就是pancit!
      • 子类可能没有构造函数
      【解决方案6】:

      如果您不想使用 get_call_class(),您可以使用后期静态绑定 (PHP 5.3+) 的其他技巧。但是在这种情况下,您需要在每个模型中都有 getClass() 方法。 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)
      

      【讨论】:

        【解决方案7】:

        普雷斯顿回答的两个变体:

        1)

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

        2)

        class Base 
        {
            public static function _find($class, $id)
            {
                $table = static::$_table;
                $data = array('table' => $table, 'id' => $id);
                return new $class($data);
            }
        }
        
        class User extends Base
        {
            public static function find($id)
            {
                return self::_find(get_class($this), $id);
            }
        }
        

        注意:以 _ 开头的属性名称是一种约定,基本上意味着“我知道我公开了它,但它确实应该受到保护,但我无法这样做并实现我的目标”

        【讨论】:

          【解决方案8】:

          我知道这个问题真的很老了,但是对于那些寻找比在包含类名的每个类中定义属性更实用的解决方案的人:

          您可以为此使用 static 关键字。

          this contributor note in the php documentation 中所述

          static 关键字可以在超类中用于访问从中调用方法的子类。

          示例:

          class Base
          {
              public static function init() // Initializes a new instance of the static class
              {
                  return new static();
              }
          
              public static function getClass() // Get static class
              {
                  return static::class;
              }
          
              public function getStaticClass() // Non-static function to get static class
              {
                  return static::class;
              }
          }
          
          class Child extends Base
          {
          
          }
          
          $child = Child::init();         // Initializes a new instance of the Child class
          
                                          // Output:
          var_dump($child);               // object(Child)#1 (0) {}
          echo $child->getStaticClass();  // Child
          echo Child::getClass();         // Child
          

          【讨论】:

          • 谢谢!在 ReflectionClass 上苦苦挣扎,但调用 static() 是可行的方法!
          【解决方案9】:

          我知道它的旧帖子,但想分享我找到的解决方案。

          使用 PHP 7+ 测试 使用函数get_class()link

          <?php
          abstract class bar {
              public function __construct()
              {
                  var_dump(get_class($this));
                  var_dump(get_class());
              }
          }
          
          class foo extends bar {
          }
          
          new foo;
          ?>
          

          上面的例子会输出:

          string(3) "foo"
          string(3) "bar"
          

          【讨论】:

            猜你喜欢
            • 2020-09-23
            • 2012-03-23
            • 1970-01-01
            • 2016-06-25
            • 2016-09-17
            • 1970-01-01
            • 2012-08-08
            • 2020-02-20
            • 2011-07-07
            相关资源
            最近更新 更多