Lấy tên của một lớp con trong lớp cha (ngữ cảnh tĩnh)


93

Tôi đang xây dựng một thư viện ORM với mục đích tái sử dụng và đơn giản; mọi thứ đều ổn ngoại trừ việc tôi bị mắc kẹt bởi một giới hạn thừa kế ngu ngốc. Vui lòng xem xét đoạn mã dưới đây:

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

Rõ ràng, đây không phải là hành vi mà tôi mong đợi (mặc dù hành vi thực tế cũng có ý nghĩa) .. Vì vậy, câu hỏi của tôi là nếu các bạn biết về tên của lớp con có nghĩa là gì, trong lớp cha, tên của lớp con.

Câu trả lời:


98

Nói ngắn gọn. điều này là không thể. trong php4, bạn có thể thực hiện một vụ hack khủng khiếp (kiểm tra debug_backtrace()) nhưng phương pháp đó không hoạt động trong PHP5. người giới thiệu:

chỉnh sửa : một ví dụ về liên kết tĩnh muộn trong PHP 5.3 (được đề cập trong phần bình luận). lưu ý rằng có những vấn đề tiềm ẩn trong quá trình triển khai hiện tại của nó ( 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"

Vâng, tôi vừa mới đọc về debug_backtrace().. Một giải pháp khả thi là sử dụng liên kết tĩnh trễ từ PHP 5.3 nhưng đó không phải là khả năng trong trường hợp của tôi. Cảm ơn bạn.
saalaa

187

Bạn không cần phải đợi PHP 5.3 nếu bạn có thể hình dung ra cách thực hiện điều này bên ngoài ngữ cảnh tĩnh. Trong php 5.2.9, trong một phương thức không tĩnh của lớp cha, bạn có thể thực hiện:

get_class($this);

và nó sẽ trả về tên của lớp con dưới dạng một chuỗi.

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

điều này sẽ xuất ra:

Parent class: Parent
Child class: Child

ngọt ngào hả?


11
Nếu bạn đang sử dụng các lớp trừu tượng, đây là điều cần biết.
Levi Morrison

1
Tôi nghĩ là $x = new Parent();nên $x = new Child();.
Justin C

đây là nó pancit!
bdalina

Lớp trẻ có thể được mà không có constructor
shmnff

19

Tôi biết câu hỏi này thực sự cũ, nhưng đối với những người đang tìm kiếm một giải pháp thiết thực hơn là xác định một thuộc tính trong mọi lớp có chứa tên lớp:

Bạn có thể sử dụng statictừ khóa này.

Như đã giải thích trong ghi chú của người đóng góp này trong tài liệu php

các statictừ khóa có thể được sử dụng bên trong một lớp siêu để truy cập lớp tiểu mà từ đó một phương pháp được gọi.

Thí dụ:

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

1
Cảm ơn bạn! Đã rất vất vả với ReflectionClass nhưng gọi điện static()là cách tốt nhất!
godbout

16

Tôi biết bài viết cũ của nó nhưng muốn chia sẻ giải pháp tôi đã tìm thấy.

Đã thử nghiệm với PHP 7+ Sử dụng liên kết hàmget_class()

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

class foo extends bar {
}

new foo;
?>

Ví dụ trên sẽ xuất ra:

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

6

Trong trường hợp bạn không muốn sử dụng get_called_class (), bạn có thể sử dụng các thủ thuật khác về liên kết tĩnh muộn (PHP 5.3+). Nhưng nhược điểm trong trường hợp này là bạn cần phải có phương thức getClass () trong mọi mô hình. Đó không phải là một vấn đề lớn 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)

2

Có vẻ như bạn đang cố gắng sử dụng một mẫu singleton làm mẫu xuất xưởng. Tôi khuyên bạn nên đánh giá các quyết định thiết kế của bạn. Nếu một singleton thực sự phù hợp, tôi cũng khuyên bạn chỉ nên sử dụng các phương thức tĩnh khi không mong muốn kế thừa .

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

.. không. Bạn không hiểu tôi đang cố gắng bo bo cái gì, nhưng đó là do tôi giải thích chưa rõ :). Trên thực tế, tôi chỉ đang cố gắng cung cấp một phương thức tĩnh (được triển khai trong BaseModel) để lấy () một thể hiện của một Mô hình nhất định (có thể là Người dùng, Vai trò hoặc bất cứ thứ gì). Tôi sẽ cập nhật câu hỏi ..
saalaa

Ok, đã cập nhật. Tất nhiên lớp BaseModel có nhiều phương thức hơn, bao gồm một số phương thức để theo dõi những gì được thay đổi trong đối tượng và chỉ CẬP NHẬT những gì đã được lồng, v.v. Nhưng dù sao cũng cảm ơn bạn :).
saalaa

2

Có thể điều này không thực sự trả lời câu hỏi, nhưng bạn có thể thêm một tham số để get () xác định kiểu. sau đó bạn có thể gọi

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

thay vì gọi User :: get (). Bạn có thể thêm logic trong BaseModel :: get () để kiểm tra xem phương thức get có tồn tại trong lớp con hay không và sau đó gọi phương thức đó nếu bạn muốn cho phép lớp con ghi đè nó.

Nếu không, cách duy nhất tôi có thể nghĩ đến rõ ràng là thêm nội dung vào mỗi lớp con, điều này thật ngu ngốc:

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

Điều này có thể sẽ bị phá vỡ nếu sau đó bạn phân lớp Người dùng, nhưng tôi cho rằng bạn có thể thay thế get_parent_class(__CLASS__)bằng 'BaseModel'trong trường hợp đó


Trên thực tế, có. Hạn chế PHP này có lợi thế lớn buộc tôi phải xem lại thiết kế của mình. Nó sẽ trông giống như $ connect-> get ('Người dùng', 24); vì nó cho phép nhiều kết nối cùng lúc và cũng chính xác hơn về mặt ngữ nghĩa. Nhưng bạn có một điểm :).
saalaa

array_shift có vẻ chậm, vì nó sẽ phải thay đổi mọi khóa của mảng ... Thay vào đó, chỉ cần $ args [0];
Xesau

0

Vấn đề không phải là giới hạn về ngôn ngữ, đó là thiết kế của bạn. Đừng bận tâm rằng bạn có các lớp học; các phương thức tĩnh tin vào một thiết kế thủ tục hơn là hướng đối tượng. Bạn cũng đang sử dụng trạng thái toàn cầu ở một số hình thức. (Làm thế nào để get_row_from_db_as_array()biết nơi để tìm cơ sở dữ liệu?) Và cuối cùng nó trông rất khó để kiểm tra đơn vị.

Hãy thử một cái gì đó dọc theo những dòng này.

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

0

Hai biến thể trong câu trả lời của Preston:

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

Lưu ý: bắt đầu tên thuộc tính bằng _ là một quy ước về cơ bản có nghĩa là "tôi biết tôi đã công khai điều này, nhưng nó thực sự nên được bảo vệ, nhưng tôi không thể làm điều đó và đạt được mục tiêu của mình"

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.