부모 클래스 (정적 컨텍스트)에서 자식 클래스의 이름 가져 오기


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에서 작동하지 않습니다. 참조 :

edit : 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

187

정적 컨텍스트 외부에서이 작업을 수행하는 방법을 구상 할 수 있다면 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

달콤한 응?


11
추상 클래스를 사용하는 경우 반드시 알아야합니다.
Levi Morrison

1
나는 $x = new Parent();이어야 한다고 생각 한다 $x = new Child();.
Justin C

이것은 패싯입니다!
bdalina

하위 클래스는 생성자없이 수 있습니다
shmnff

19

이 질문이 정말 오래되었다는 것을 알고 있지만 클래스 이름을 포함하는 모든 클래스에서 속성을 정의하는 것보다 더 실용적인 솔루션을 찾는 사람들을 위해 :

이를 위해 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

16

이전 게시물을 알고 있지만 찾은 솔루션을 공유하고 싶습니다.

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에서 구현 됨)를 제공하려고합니다. 질문을 업데이트하겠습니다 ..
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

문제는 언어 제한이 아니라 귀하의 디자인입니다. 수업이 있다는 것에 신경 쓰지 마십시오. 정적 방법은 객체 지향 설계가 아닌 절차 적 설계에 속합니다. 또한 어떤 형태로든 전역 상태를 사용하고 있습니다. ( get_row_from_db_as_array()데이터베이스를 어디에서 찾을 수 있는지 어떻게 알 수 있습니까?) 그리고 마지막으로 단위 테스트가 매우 어려워 보입니다.

이 라인을 따라 무언가를 시도하십시오.

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

0

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

참고 : 속성 이름을 _로 시작하는 것은 기본적으로 "내가이 정보를 공개 한 것을 알고 있지만 실제로는 보호되어야했지만 그렇게 할 수 없었고 내 목표를 달성 할 수 없었습니다."를 의미하는 규칙입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.