Üst sınıfta bir alt sınıfın adını alma (statik bağlam)


93

Yeniden kullanım ve basitliği göz önünde bulundurarak bir ORM kitaplığı oluşturuyorum; aptal bir miras sınırlamasına takılıp kalmam dışında her şey yolunda gidiyor. Lütfen aşağıdaki kodu dikkate alın:

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

Açıkçası, beklediğim davranış bu değil (gerçek davranış da mantıklı olsa da) .. Yani sorum şu ki, ebeveyn sınıfında çocuk sınıfının adını almanın bir yolunu biliyorsanız.

Yanıtlar:


98

Kısacası. bu mümkün değil. php4'te korkunç bir hack uygulayabilirsiniz (inceleyin debug_backtrace()) ancak bu yöntem PHP5'te çalışmaz. Referanslar:

düzenleme : PHP 5.3'te geç statik bağlama örneği (yorumlarda bahsedilmiştir). mevcut uygulamasında ( src ) olası sorunlar olduğunu unutmayın .

class Base {
    public static function whoAmI() {
        return get_called_class();
    }
}

class User extends Base {}

print Base::whoAmI(); // prints "Base"
print User::whoAmI(); // prints "User"

Evet, biraz önce okudum debug_backtrace().. Olası bir çözüm PHP 5.3'ten geç statik bağlamayı kullanmak olabilir ama benim durumumda bu bir olasılık değil. Teşekkür ederim.
saalaa

188

Statik bir bağlamın dışında bunu yapmanın bir yolunu düşünebiliyorsanız PHP 5.3'ü beklemenize gerek yoktur. Php 5.2.9'da, ana sınıfın statik olmayan bir yönteminde şunları yapabilirsiniz:

get_class($this);

ve alt sınıfın adını bir dizge olarak döndürecektir.

yani

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

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

$x = new Child();

bu çıktı:

Parent class: Parent
Child class: Child

tatlı ha?


11
Soyut sınıflar kullanıyorsanız, bu bir zorunluluktur.
Levi Morrison

1
Bence $x = new Parent();olmalı $x = new Child();.
Justin C

bu pankit!
bdalina

Çocuk sınıfı kurucusuz olabilir
shmnff

21

Bu sorunun gerçekten eski olduğunu biliyorum, ancak her sınıfta sınıf adını içeren bir özelliği tanımlamaktan daha pratik bir çözüm arayanlar için:

Bunun için staticanahtar kelimeyi kullanabilirsiniz .

Php belgelerinde bu katılımcı notunda açıklandığı gibi

staticAnahtar kelime bir yöntemi çağrıldığında olan alt sınıf erişmek için süper sınıf içinde kullanılabilir.

Misal:

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
Teşekkür ederim! ReflectionClass ile mücadele ediyordu ama arama static()yolu bu!
godbout

17

Eski gönderisini biliyorum ama bulduğum çözümü paylaşmak istiyorum.

PHP 7+ ile test edilmiştir İşlev get_class()bağlantısını kullanın

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

class foo extends bar {
}

new foo;
?>

Yukarıdaki örnekte çıktı:

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

6

Get_called_class () işlevini kullanmak istemiyorsanız, diğer geç statik bağlama yöntemlerini de kullanabilirsiniz (PHP 5.3+). Ancak bu durumda olumsuz tarafı her modelde getClass () yöntemine sahip olmanız gerekir. Hangi önemli bir IMO değil.

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

Görünüşe göre bir fabrika kalıbı olarak bir tekil kalıbı kullanmaya çalışıyor olabilirsiniz. Tasarım kararlarınızı değerlendirmenizi tavsiye ederim. Bir tekil gerçekten uygun değilse, ben de miras statik yöntemler kullanılarak yalnızca öneriyoruz değil istenen.

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

.. Hayır. Ne yapmaya çalıştığımı anlamadınız ama bunun sebebi iyi açıklamamış olmam :) Aslında, belirli bir Modelin bir örneğini (Kullanıcı, Rol veya her neyse) elde etmek () için statik bir yöntem (BaseModel'de uygulanan) sağlamaya çalışıyorum. Soruyu güncelleyeceğim ..
saalaa

Tamam, güncellendi. Tabi ki BaseModel sınıfının çok daha fazla yöntemi vardır, bunlardan bazıları nesnede nelerin değiştiğini takip etmek ve yalnızca değiştirilenleri GÜNCELLEMEK vb ... Ama yine de teşekkürler :).
saalaa

2

Belki bu aslında soruyu cevaplamıyor olabilir, ancak türü belirleyen get () için bir parametre ekleyebilirsiniz. o zaman arayabilirsin

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

User :: get () çağırmak yerine. Alt sınıfta bir get yönteminin olup olmadığını kontrol etmek için BaseModel :: get () 'ye mantık ekleyebilir ve alt sınıfın onu geçersiz kılmasına izin vermek istiyorsanız bunu çağırabilirsiniz.

Aksi takdirde, açıkça düşünebileceğim tek yol, her alt sınıfa bir şeyler eklemektir, ki bu aptalca:

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

Eğer sonra Kullanıcı sınıflandırma, bu muhtemelen kıracak, ama yerine herhalde get_parent_class(__CLASS__)ile 'BaseModel'bu durumda


Aslında evet. Bu PHP sınırlaması beni tasarımımı gözden geçirmeye zorlayan büyük bir avantaja sahipti. Daha çok $ connection-> get ('Kullanıcı', 24); çünkü aynı anda birden fazla bağlantıya izin verir ve semantik olarak daha doğrudur. Ama haklısın :).
saalaa

array_shift yavaş görünüyor, çünkü dizinin her anahtarını değiştirmek zorunda kalacak ... Onun yerine sadece $ args [0];
Xesau

0

Sorun dil sınırlaması değil, tasarımınız. Derslerin olduğunu boşver; statik yöntemler, nesne yönelimli tasarımdan çok yordamsal bir tasarımı yalanlar. Ayrıca küresel durumu bir biçimde kullanıyorsunuz. ( get_row_from_db_as_array()Veritabanını nerede bulacağınızı nasıl bilebilir?) Ve nihayet birim testi yapmak çok zor görünüyor.

Bu satırlarda bir şeyler deneyin.

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

0

Preston'ın cevabının iki çeşidi:

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

Not: bir mülk adına _ ile başlamak, temelde "bunu kamuya açıkladığımı biliyorum, ancak gerçekten korunmalıydı, ancak bunu yapıp amacıma ulaşamadım" anlamına gelen bir sözleşmedir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.