piątek, 5 sierpnia 2011

Dostęp do prywatnych pól klas przodków w PHP

Ten wpis jest w zasadzie rozszerzeniem poprzedniego wpisu. To co tutaj zostanie omówione to stricte dostęp do pól prywatnych danej klasy, jak i również troszkę bardziej rozbudowany przykład wykorzystania klas z rodziny Reflection.

Jak można było zauważyć w poprzednim wpisie funkcja get_object_vars() realizuje de facto zadanie pobrania wartości i nazw pól klas. Jej wadą jest jednak niemożność dojścia do metod prywatnych przodków naszej klasy i właśnie w celu dodania tej funkcjonalności niezbędne okażą się klasy Reflection.

Na początek przypomnijmy jednak sobie naszą hierarchię klas...
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;
}

Aby dobrać się do metod prywatnych potrzebujemy o wiele bardziej rozbudowanego kodu niż ten umieszczony w poprzednim wpisie, oraz rekurencji koniecznej do przejścia po grafu dziedziczenia klas.
Takim chyba najprostszym przykładem pobrania wartości wszystkich parametrów jest funkcja:
function getAllFields($object)
{
    $globalFields = array();
    $reflectionClass = new ReflectionClass($object);
    do
    {
        $reflections = $reflectionClass->getProperties();

        foreach($reflections as $reflectionProperty)
        {
            $reflectionProperty->setAccessible(true);
            $globalFields[$reflectionProperty->name] = $reflectionProperty->getValue($object);
        }
        $reflectionClass = $reflectionClass->getParentClass();
    }
    while($reflectionClass);
    return $globalFields;
}
dająca taki oto rezultat
Array
(
    [thirdPrivate] => 
    [thirdProtected] => 
    [thirdPublic] => NEW
    [secondProtected] => 
    [secondPublic] => 
    [firstProtected] => 
    [firstPublic] => DEFAULT
    [secondPrivate] => 
    [firstPrivate] => NEW PRIVATE
)
Funkcję ta jednak ma główną wadę w postaci nadpisywania zmiennych klas bliższych badanemu obiektowi przez zmienne jego przodków. Na szczęście można tą funkcję dowolnie rozwijać i sprawić by generowała wszelkie informacje dotyczące badanej klasy:
function getAllFieldsStrict($object)
{
    $globalFields = array();
    $reflectionClass = new ReflectionClass($object);
    do
    {
        $reflections = $reflectionClass->getProperties();

        foreach($reflections as $reflectionProperty)
        {
            if($reflectionProperty->getDeclaringClass()->name == $reflectionProperty->name)
            {
                switch(true)
                {
                    case $reflectionProperty->isPrivate(): $globalFields[$reflectionProperty->name][$reflectionProperty->name]['access'] = 'private'; break;
                    case $reflectionProperty->isProtected(): $globalFields[$reflectionProperty->name][$reflectionProperty->name]['access'] = 'protected'; break;
                    case $reflectionProperty->isPublic(): $globalFields[$reflectionProperty->name][$reflectionProperty->name]['access'] = 'public'; break;
                }
                $reflectionProperty->setAccessible(true);
                $globalFields[$reflectionProperty->name][$reflectionProperty->name]['value'] = $reflectionProperty->getValue($object);
                $globalFields[$reflectionProperty->name][$reflectionProperty->name]['static'] = $reflectionProperty->isStatic();
            }
        }
        $reflectionClass = $reflectionClass->getParentClass();
    }
    while($reflectionClass);
    return $globalFields;
}
czego rezultatem jest
Array
(
    [Third] => Array
        (
            [thirdPrivate] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => private
                )
            [thirdProtected] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => protected
                )
            [thirdPublic] => Array
                (
                    [value] => NEW
                    [static] => 
                    [access] => public
                )
        )
    [Second] => Array
        (
            [secondPrivate] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => private
                )
            [secondProtected] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => protected
                )
            [secondPublic] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => public
                )
        )
    [First] => Array
        (
            [firstPrivate] => Array
                (
                    [value] => NEW PRIVATE
                    [static] => 
                    [access] => private
                )
            [firstProtected] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => protected
                )
            [firstPublic] => Array
                (
                    [value] => DEFAULT
                    [static] => 
                    [access] => public
                )
        )
)
Wydajność
get_object_vars:  0.28459501266479
getAllFields:   4.3010709285736
getAllFieldsStrict:  4.3191359043121
Podczas testu wykonano każde z zapytań 100000 razy i jak widać wygenerowanie rozbudowanej struktury klas zajmuje praktycznie tyle samo czasu co stworzenie prostszej. Mimo wszystko jednak generowanie trwa ok. 15 razy dłużej w porównaniu do wyciągnięcia informacji za pomocą get_object_vars(), dlatego też jeśli potrzebujemy dostać się do zmiennych klas i mamy możliwość deklarowania pól jako protected a nie private - to zdecydowanie lepiej tak zrobić i używać get_object_vars().

Przykładowy kod do testowania można pobrać z mojego chomika (link).

Brak komentarzy:

Prześlij komentarz