2008-11-12 35 views
67

Tôi đang xây dựng một thư viện ORM có sử dụng lại và đơn giản trong tâm trí; mọi thứ đều tốt đẹp 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. Hãy xem xét các đoạn mã sau:Lấy tên của một lớp con trong lớp cha mẹ (ngữ cảnh tĩnh)

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 của tôi đã mong (mặc dù hành vi thực tế cũng làm cho tinh thần) .. Vì vậy, câu hỏi của tôi là nếu các bạn biết một bình để có được, trong lớp cha, tên của lớp con.

Trả lời

72

ngắn gọn. điều này là không thể. trong php4 bạn có thể thực hiện một hack khủng khiếp (kiểm tra các debug_backtrace()) nhưng phương pháp đó không hoạt động trong PHP5. tài liệu tham khảo:

chỉnh sửa: một ví dụ về cuối tĩnh ràng buộc trong PHP 5.3 (nêu trong ý kiến). lưu ý có những vấn đề tiềm tàng trong việc triển khai hiện tại (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" 
+0

Có, tôi chỉ đọc về 'debug_backtrace()' .. Một giải pháp có thể sẽ là sử dụng * ràng buộc tĩnh muộn * 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

2

Dường như bạn có thể đang cố sử dụng mẫu đơn lẻ làm mẫu nhà máy. Tôi sẽ 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ự là thích hợp, tôi cũng khuyên bạn nên chỉ sử dụng các phương thức tĩnh mà thừa kế là không phải là mong muốn.

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 
+0

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

+0

Ok, đã cập nhật. Tất nhiên lớp BaseModel có nhiều phương pháp hơn, bao gồm một số để theo dõi những gì được thay đổi trong đối tượng và UPDATE chỉ những gì đã bị dàn dựng, vv ... Nhưng cảm ơn bạn anyway :). – 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 thông số để nhận() chỉ định loại. 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 một phương thức get có tồn tại trong lớp con và sau đó gọi rằng nếu bạn muốn cho phép lớp con ghi đè nó.

Nếu không thì cách duy nhất tôi có thể nghĩ rõ ràng là bằng cách thêm công cụ để mỗi lớp con, đó là 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); 

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

+0

Thực ra, vâng. Giới hạn PHP này có lợi thế lớn để buộc tôi phải xem xét lại thiết kế của mình. Nó sẽ trông giống như $ connection-> get ('User', 24); vì nó cho phép nhiều kết nối cùng một 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

+0

array_shift có vẻ chậm, bởi vì nó sẽ phải thay đổi mọi khóa của mảng ... Chỉ cần $ args [0] thay thế; – Xesau

0

Vấn đề không phải là giới hạn 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 belie một thủ tục chứ không phải là thiết kế hướng đối tượng. Bạn cũng đang sử dụng trạng thái toàn cục ở một dạng nào đó. (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ó khăn để kiểm tra đơn vị.

Hãy thử điều gì đó dọc theo các dòng này.

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

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

get_class($this); 

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

ví dụ:

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

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

$x = new Child(); 

này sẽ đầu ra:

Parent class: Parent 
Child class: Child 

huh ngọt ngào?

+10

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

+0

đáng yêu! mẹo hay! –

+0

Điều này xứng đáng được thăng hạng nhiều hơn! – Mattisdada

5

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 của liên kết tĩnh muộn (PHP 5.3+). Nhưng nhược điểm trong trường hợp này bạn cần phải có phương thức getClass() trong mọi mô hình. Mà 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) 
0

Hai biến thể về 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ừ một tên sở hữu với _ là một quy ước rằng về cơ bản có nghĩa là " tôi biết tôi đã thực hiện công khai, nhưng nó thực sự cần phải được bảo vệ, nhưng tôi không thể làm điều đó và đạt được mục tiêu của tôi "

Các vấn đề liên quan