czwartek, 4 sierpnia 2011

Listowanie nazw pól klasy w PHP

Chcąc dobrać się do zmiennych klasy - ich nazw oraz ewentualnie wartości - dokopałem się do trzech sposobów. Dwa z nich (get_class_vars() i get_object_vars()) pochodzą jeszcze z czasów strukturalnego pisania kodu, zaś trzeci (ReflectionClass) jest już cudem współczesnego przerośniętego obiektowego PHP5.

No ale przechodząc do rzeczy, a właściwie do przykładu obrazującego o co mi chodzi :)

class First
{
    private $firstPrivate;
    protected $firstProtected;
    public $firstPublic="DEFAULT";
}

class Second extends First
{
    private $secondPrivate;
    protected $secondProtected;
    public $secondPublic;
}

class Third extends Second
{
    private $thirdPrivate;
    protected $thirdProtected;
    public $thirdPublic;
}
Otóż mamy 3 klasy, a w każdej z nich 3 zmienne - private, protected i public... Na początku miałem nadzieję, że uda się dobrać do nich wszystkich, jednak okazało się, że do pól prywatnych można dostać się jedynie z kontekstu tej samej klasy - tak więc musiałbym w każdej klasie umieszczać dedykowany kod (lub użyć przerośniętej funkcji działającej w oparciu o Reflection), a takie rozwiązanie w grę nie wchodziło :x Tak więc poświęciłem prywatne zmienne i zamieniłem je na protected i ten problem zniknął ^.^

Pobieranie nazw parametrów
Jednak wracając do zagadnienia - samo uzyskanie nazw zmiennych jest dość banalne i można je osiągnać choćby dzięki dowolnej z poniższych metod:

function getClassVars()
{
    return get_class_vars(get_class($this));
}

function getObjectVars()
{
    return get_object_vars($this);
}

function reflectionGetDefaultProperties()
{
    $reflectionClass = new ReflectionClass($this);
    return $reflectionClass->getDefaultProperties();
}

function reflectionGetProperties()
{
    $reflectionClass = new ReflectionClass($this);
    return $reflectionClass->getProperties();
}

3 pierwsze z nich zwracają tablicę, w której klucze elementów mają nazwę parametrów, a wartości do tych kluczy przypisane równe są wartościom obiektów. Tylko, że tutaj uwaga, gdyż get_class_vars() oraz ->getDefaultProperties zwracają jedynie wartości na sztywno przypisane (domyślne) w kodzie do poszczególnych zmiennych, a nie aktualne wartości tych zmiennych w instancji obiektu.

Tak więc po wykonaniu kodu:

class First
{
    private $firstPrivate;
    protected $firstProtected;
    public $firstPublic="DEFAULT";
    static $firstStatic = "STATIC";

/** metody identyczne do podanych w poprzednim przykładzie */
 function getClassVars() {...}
 function getObjectVars() {...}
 function reflectionGetDefaultProperties() {...}
 function reflectionGetProperties() {...}
}

$object = new A();
$object->firstPublic = "NEW";

print_r($d->getClassVars());
print_r($d->getObjectVars());
print_r($d->reflectionGetDefaultProperties());
print_r($d->reflectionGetProperties());
otrzymamy:
Array
( 
    [firstStatic] => STATIC
    [firstPrivate] => 
    [firstProtected] => 
    [firstPublic] => DEFAULT
)
Array
(
    [firstPrivate] => 
    [firstProtected] => 
    [firstPublic] => NEW
)
Array
(
    [firstPrivate] => 
    [firstProtected] => 
    [firstPublic] => DEFAULT
    [firstStatic] => STATIC
)
Array
(
    [0] => ReflectionProperty Object
        (
            [name] => firstPrivate
            [class] => First
        )

    [1] => ReflectionProperty Object
        (
            [name] => firstProtected
            [class] => First
        )

    [2] => ReflectionProperty Object
        (
            [name] => firstPublic
            [class] => First
        )

    [3] => ReflectionProperty Object
        (
            [name] => firstStatic
            [class] => First
        )
)

Jednak jeśli zależy nam tylko na nazwach zmiennych to generalnie nie ma wielkiej różnicy w wybranej metodzie. Poza tym jak widać ostatnia metoda (->getProperties) zwraca nam obiekty klasy ReflectionProperty.. ale o tym w innym wpisie. Warto jeszcze wspomnieć o tym, że get_object_vars() nie zwraca statycznych pól klasy, ale po co nam one jeśli chodzi nam o stan konkretnej instancji obiektu?
Wydajność
Generalnie jeśli zależy nam jedynie na uzyskaniu nazw parametrów to dlaczego by nie wybrać najszybszej metody? Testy przeprowadziłem na hostingu ovh (większa stabilność wyników niż przy testowaniu na własnym kompie):

get_class_vars:   3.3929491043091
get_object_vars:   2.7406477928162
->getDefaultProperties:  3.6021101474762
->getProperties:   8.8276469707489

Test polegał na wykonaniu pętli
for ($i = 0; $i < 1000000; $i++){ $d->bref(); }
dla każdego z zapytań i zmierzenie czasu za pomocą funkcji microtime(true). Jak widać po wynikach najszybciej działa funkcja get_object_vars(), a najwolniejsze w użyciu są klasy z rodziny Reflection. Oczywiście w przypadku wpisania nazwy klas na sztywno (literka s przy opisie) zamiast używania get_class() wyniki są trochę lepsze (wyniki w dwóch zestawach gdyż sumarycznie przekraczają 30 sekundowy maksymalny czas wykonania skryptu):

get_class_vars:   3.3419530391693
get_class_vars (s):   3.0308630466461
get_object_vars:   2.6966669559479

get_object_vars:   2.6544170379639
->getDefaultProperties:  3.5368349552155
->getDefaultProperties (s):  3.5258779525757
->getProperties:   8.8363699913025
->getProperties (s):   8.7472901344299 

Co ciekawe najbardziej na tej zmianie zyskuje get_class_vars().

Przykładowy kod do przeprowadzenia testów można pobrać (jak zawsze) z mojego chomika (link).

2 komentarze:

  1. "jednak okazało się, że do pól prywatnych można dostać się jedynie z kontekstu tej samej klasy"

    Niesamowite...

    OdpowiedzUsuń
  2. a żebyś wiedział :P Szczerze miałem nadzieję, że przy pomocy wbudowanych funkcji da się ujrzeć pełen obraz klasy i jej przodków.

    Bo np. za pomocą Reflection da się uzyskać informacje o prywatnych polach dowolnej klasy poza jej kontekstem, ot wystarczy mieć jej instancję.

    OdpowiedzUsuń