รับชื่อของคลาสลูกในคลาสแม่ (บริบทคงที่)


93

ฉันกำลังสร้างห้องสมุด 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();

เห็นได้ชัดว่านี่ไม่ใช่พฤติกรรมที่ฉันคาดหวังไว้ (แม้ว่าพฤติกรรมจริงก็สมเหตุสมผลเช่นกัน) .. ดังนั้นคำถามของฉันคือถ้าพวกคุณรู้ค่าเฉลี่ยที่จะได้รับในคลาสผู้ปกครองชื่อของคลาสลูก

คำตอบ:


98

ในระยะสั้น เป็นไปไม่ได้ ใน php4 คุณสามารถใช้แฮ็คที่น่ากลัวได้ (ตรวจสอบdebug_backtrace()) แต่วิธีนั้นใช้ไม่ได้ใน PHP5 อ้างอิง:

แก้ไข : ตัวอย่างของการผูกแบบคงที่ล่าช้าใน PHP 5.3 (กล่าวถึงในความคิดเห็น) โปรดทราบว่ามีปัญหาที่อาจเกิดขึ้นในการใช้งานปัจจุบัน ( 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 แต่นั่นไม่ใช่ความเป็นไปได้ในกรณีของฉัน ขอขอบคุณ.
saalaa

188

คุณไม่จำเป็นต้องรอ PHP 5.3 หากคุณสามารถคิดวิธีทำสิ่งนี้ได้นอกบริบทคงที่ ใน php 5.2.9 ในเมธอด non-static ของคลาสพาเรนต์คุณสามารถทำได้:

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

หวานเหรอ?


11
หากคุณกำลังใช้คลาสนามธรรมสิ่งนี้เป็นสิ่งที่ต้องรู้
Levi Morrison

1
ผมคิดว่าควรจะเป็น$x = new Parent(); $x = new Child();
Justin C

นี่มันเรื่อง pancit!
bdalina

ชั้นเรียนเด็กอาจไม่มีตัวสร้าง
shmnff

21

ฉันรู้ว่าคำถามนี้เก่ามาก แต่สำหรับผู้ที่มองหาวิธีแก้ปัญหาที่เป็นประโยชน์มากกว่าการกำหนดคุณสมบัติในทุกคลาสที่มีชื่อคลาส:

คุณสามารถใช้staticคีย์เวิร์ดสำหรับสิ่งนี้

ตามที่อธิบายไว้ในบันทึกของผู้ให้ข้อมูลนี้ในเอกสารประกอบ php

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

1
ขอขอบคุณ! กำลังดิ้นรนกับ ReflectionClass แต่การโทรstatic()คือหนทางที่จะไป!
godbout

17

ฉันรู้ว่ามันเป็นโพสต์เก่า แต่ต้องการแบ่งปันวิธีแก้ปัญหาที่ฉันพบ

ทดสอบด้วย PHP 7+ ใช้ลิงค์ฟังก์ชันget_class()

<?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"

6

ในกรณีที่คุณไม่ต้องการใช้ get_called_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)

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) เพื่อรับ () อินสแตนซ์ของ Model ที่กำหนด (อาจเป็น User, Role หรืออะไรก็ได้) ฉันจะอัปเดตคำถาม ..
saalaa

โอเคอัปเดตแล้ว แน่นอนว่าคลาส BaseModel มีวิธีการอื่น ๆ อีกมากมายรวมถึงบางส่วนเพื่อติดตามสิ่งที่เปลี่ยนแปลงในออบเจ็กต์และอัปเดตเฉพาะสิ่งที่ถูกไล่ล่า ฯลฯ ... แต่ยังไงก็ขอขอบคุณ :)
saalaa

2

บางทีนี่อาจไม่ได้ตอบคำถาม แต่คุณสามารถเพิ่มพารามิเตอร์เพื่อ 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);

นี่อาจจะพังถ้าคุณเป็นผู้ใช้ย่อย แต่ฉันคิดว่าคุณสามารถแทนที่get_parent_class(__CLASS__)ด้วย'BaseModel'ในกรณีนั้น


ที่จริงใช่ ข้อ จำกัด ของ PHP นี้มีประโยชน์อย่างมากในการบังคับให้ฉันทบทวนการออกแบบของฉัน มันจะดูเหมือน $ connection-> get ('User', 24); เนื่องจากอนุญาตให้มีการเชื่อมต่อหลายรายการในเวลาเดียวกันและยังมีความหมายที่ถูกต้องมากขึ้น แต่คุณมีจุด :)
saalaa

array_shift ดูเหมือนจะช้าเพราะจะต้องเปลี่ยนทุกคีย์ของอาร์เรย์ ... แค่ $ args [0] แทน;
Xesau

0

ปัญหาไม่ใช่ข้อ จำกัด ด้านภาษา แต่เป็นการออกแบบของคุณ ไม่เป็นไรคุณมีเรียน วิธีการคงที่เชื่อในขั้นตอนมากกว่าการออกแบบเชิงวัตถุ คุณกำลังใช้ global state ในบางรูปแบบ (จะget_row_from_db_as_array()รู้ได้อย่างไรว่าจะหาฐานข้อมูลได้อย่างไร) และสุดท้ายก็ดูยากมากที่จะทดสอบหน่วย

ลองทำตามเส้นเหล่านี้

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

0

คำตอบของเพรสตันสองรูปแบบ:

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

หมายเหตุ: การเริ่มต้นชื่อพร็อพเพอร์ตี้ด้วย _ เป็นอนุสัญญาที่โดยพื้นฐานแล้วหมายความว่า "ฉันรู้ว่าฉันเปิดเผยต่อสาธารณะ แต่จริงๆแล้วควรได้รับการคุ้มครอง แต่ฉันทำเช่นนั้นไม่ได้และบรรลุเป้าหมายของฉัน"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.