29 07 2010

Konfiguracja klas i obiektów - metoda settings

Założeniem biblioteki jest łatwość konfiguracji zachowania danej klasy. Standardowo, w większości bibliotek/frameworków istnieją odpowiednie wartości przypisane do danego ustawienia.

kategoria: Tworzymy bibliotekę autor: Michał Juszczak

Tutaj tak nie ma, chciałem wyróżnić, że dana wartość pochodzi z ustawień a inna jest po prostu przetwarzana – powstała wartość:

static private $_classSettings = array();

Dlaczego statyczna? Niezależnie czy dana klasa ma metody wyłącznie statyczne, czy też posługujemy się obiektami danej klasy – ustawiamy raz i zapominamy.

Oczywiście należy pamiętać o tym, że będziemy potrzebowali zmienić ustawienia w trakcie działania skryptu. Tak więc obsługa zmiany również powinna być dołączona.

Do obsługi ustawień potrzebować będziemy dwóch metod: wprowadzania ustawień i ich odczytu. Ustawienia są ściśle zdefiniowane w $_classSettings, podanie innego klucza tablicy powinno nas jakoś informować o błędzie (“literówkę”). Metody prezentują się następująco:

Wprowadzanie ustawień

Przekazywany parametr jako tablica np:

klasa::settings(array('sciezka'=>'/sciezka/do/zapisu'));

static public function settings($classSettings)
{
    if (!is_array($classSettings)) {
        return false;
    }
    foreach ($classSettings as $key=>$row) {
        if (array_key_exists($key, self::$_classSettings)) {
            self::$_classSettings[$key] =   $row;
        } else {
            // rzutuj wyjątek lub zapisz błąd o braku klucza w ustawieniach
            // przydatne gdy ktoś zrobi “literówkę”
        }
    }
    return true;
}

Pobranie ustawień

Jako parametr podajemy nazwę klucza w tablicy ustawień, którego wartość chcemy poznać, jeżeli chcemy pobrać całą tablicę nie podajemy żadego parametru.

$_wszystkieUstawienia = klasa::getSetting();
 
static public function getSetting($setting=null)
{
    // jeśli chcemy pobrać całą tablicę ustawień
    if ($setting===null) {
        return self::$_classSettings;
    }
    if (! array_key_exists($setting, self::$_classSettings)) {
        // rzutuj wyjątek lub zapisz błąd o braku klucza w ustawieniach
        // przydatne gdy ktoś zrobi “literówkę”
        return "setting {$setting} not defined";
    }
    return self::$_classSettings[$setting];
}

Prawie skończone, problem z dziedziczeniem

To by było na tyle. Ale wszystko można usprawnić. Jeżeli dane metody występują w każdej klasie, dlaczego nie utworzyć klasy settings i dziedziczyć po niej? Kod wyglądałby następująco


class Settings
{
    static protected $_classSettings = array();
    
    static public function setting($classSettings)
    {
        // tu ten sam kod co wyżej
    }
    
    static public function getSetting($setting=null)
    {
        // tu ten sam kod co wyżej
    }
}

I każda klasa która wymaga ustawień, dziedziczyłaby po niej i wystarczyłoby wprowadzić wyłącznie tablicę ustawień.


class test extends Settings
{
    static protected $_classSettings = array('path'=>'path/to/file');
}

I możemy pobrać ustawienia klasy test echo test::getSetting('path');

Wszystko zależy od wersji

Obojętnie od rodzaju wersji PHP nie będzie to działać – nie ma czegoś takiego, jak statyczne dziedziczenie. Na logikę powinno, ale niestety. Jednakże w wersji 5.3 wprowadzono coś takiego jak Late Static Binding Sposób wprowadzania kodu jest inny, ale zapewni to co chcieliśmy osiągnąć.

Kod w Late Static Binding

Kod w użyciu LSB wyglądać będzie następująco:


class Settings
{
    static protected $_classSettings = array();
    
    static public function setting($classSettings)
    {
        if (!is_array($classSettings)) {
            return false;
        }
        foreach ($classSettings as $key=>$row) {
            if (array_key_exists($key, static::$_classSettings)) {
                static::$_classSettings[$key] =   $row;
            } else {
                /*
                rzutuj wyjątek lub zapisz błąd o braku klucza w ustawieniach
                przydatne gdy ktoś zrobi “literówkę”
                */
            }
        }
        return true;
    }
    
    static public function getSetting($setting=null)
    {
        /*
        jeśli chcemy pobrać całą tablicę ustawień
        */
        if ($setting===null) {
            return static::$_classSettings;
        }
        if (! array_key_exists($setting, static::$_classSettings)) {
            // /*
            // rzutuj wyjątek lub zapisz błąd o braku klucza w ustawieniach
            // przydatne gdy ktoś zrobi “literówkę”
            // */
            return "setting {$setting} not defined";
        }
        return static::$_classSettings[$setting];
    }
}

Proszę zwrócić uwagę na wyraz static zamiast self. Ta zmiana umożliwi nam poprawne działanie, ale tylko w PHP5.3 i wyższych.

Konkluzja

I tutaj należy wybrać, dziedziczenie czy też pisanie wszędzie metod obsługujących ustawienia klasy. Wybrałem drugie rozwiązanie, gdyż wersja 5.3 jest praktycznie nie dostępna na serwerach hostingowych. A biblioteka ma działać praktycznie wszędzie.

Ustawienia możemy przechowywać w pliku np ini o następującej budowie:


        [nazwa klasy]
        ustawienie1 = "test"
        ustawienie2 = true
        ustawienie3 = 3
        [nazwa klasy 2]
        .
        .
        .

I wczytywać przez funkcję:


function _config($file, $throwException=false)
{
    if (!file_exists($file) and $throwException) {
        throw new exception(sprintf('File %s not exists', $file));
    }
    $_settings                      =   parse_ini_file($file, true);

    foreach ($_settings as $_class=>$_classSettings) {
        if (!class_exists($_class, false) and $throwException) {
            throw new exception(sprintf('Class %s not exists', $_class));
        }
        if (!method_exists($_class, 'settings') and $throwException) {
            throw new exception(
                sprintf('Settings method in class %s not exists', $_class)
            );
        }
        foreach ($_classSettings as $key=>$val) {
            if (strtolower($val)==="true") {
                $_classSettings[$key]=  true;
            } elseif (strtolower($val)==="false") {
                $_classSettings[$key]=  false;
            } elseif (strtolower($val)==="null") {
                $_classSettings[$key]=  null;
            }
        }
        call_user_func(array($_class, 'settings'), $_classSettings);
    }
}

Dodaj komentarz:

Komentarze podlegają moderacji, nie dopuszczam komentarzy spamujących, z wyzwiskami, wulgaryzmami, oszczerstwami. Rozumiem przez to również nie akceptowanie komentarzy, których treść jest prawnie zabroniona. Pozostałe komentarze, nawet takie które będą sprzeczne z moimi poglądami są akceptowane. Należy czekać na akceptację, każdy komentarz zostanie sprawdzony przeze mnie a następnie ukaże się na stronie.

Komentarze:

1
2010-10-26 by Zyx (grrrr, mój nick jest trzyliterowa!!!)

Niestety, takie rozwiązanie jest wybitnie niepraktyczne. Już to przerabiałem spory kawałek czasu temu przy okazji tworzenia Open Power Libs 2.0 i do dziś odbija mi się to czkawką. Po pierwsze, używasz do tego elementów statycznych, czyli już na wejściu utrudniasz sobie testowanie i wprowadzasz elementy niedeterminizmu, które mogą być źródłem podobnych problemów, jakie powoduje "global". Po drugie, Twoja funkcja do ładowania konfiguracji też jest niepraktyczna, bowiem wymaga uprzedniego załadowania wszystkich możliwych klas nawet, jeśli ich nie używamy, gdyż inaczej oberwiemy wyjątkiem. Dobrym, sprawdzonym rozwiązaniem jest idea wstrzykiwania zależności. PS. Masz u mnie minusa za nałożenie limitu min. 5 znaków na długość nicka, który jest idiotyczny, jako że jest całkiem sporo osób, które mają czteroliterowe nicki, a trzyliterowe takie, jak mój, też się trafiają.

Ostatnie wiadomości:

Ostatnie komentarze:

  • (2010-10-26) Nie wiem, że metody statyczne są szybsze i nie wiem, skąd bierzesz tak...
    NEWS Tworzenie biblioteki Underscore: Nie wiem, że metody statyczne są szybsze i nie wiem, skąd bierzesz takie rewelacje, bo odkąd pamiętam, zawsze było na odwrót. Nawet przed chwilą zrobiłem szybki benchmark i nic się nie zmieniło w tym względzie. Ponadto z tego, co piszesz (zwłaszcza o krótkości kodu), wydaje mi się, że nie rozumiesz sensu programowania obiektowego. Robienie metod typu wordCount() to głupota do kwadratu - przecież po to się robi obiekty, by te metody pracowały w ich kontekście i wykonywały coś na nich, a nie działały tak sobie w próżni. O wiele lepsze wyniki wydajnościowe osiągniesz dobrymi algorytmami i odpowiednią architekturą, a nie robieniem sobie pseudoobiektowego śmietnika. Ponadto co z takimi kwestiami, jak niezawodność i elastyczność? Zyx (grrrr, mój nick jest trzyliterowy!)
  • (2010-10-26) Niestety, takie rozwiązanie jest wybitnie niepraktyczne. Już to przera...
    NEWS Konfiguracja klas i obiektów - metoda settings: Niestety, takie rozwiązanie jest wybitnie niepraktyczne. Już to przerabiałem spory kawałek czasu temu przy okazji tworzenia Open Power Libs 2.0 i do dziś odbija mi się to czkawką. Po pierwsze, używasz do tego elementów statycznych, czyli już na wejściu utrudniasz sobie testowanie i wprowadzasz elementy niedeterminizmu, które mogą być źródłem podobnych problemów, jakie powoduje "global". Po drugie, Twoja funkcja do ładowania konfiguracji też jest niepraktyczna, bowiem wymaga uprzedniego załadowania wszystkich możliwych klas nawet, jeśli ich nie używamy, gdyż inaczej oberwiemy wyjątkiem. Dobrym, sprawdzonym rozwiązaniem jest idea wstrzykiwania zależności. PS. Masz u mnie minusa za nałożenie limitu min. 5 znaków na długość nicka, który jest idiotyczny, jako że jest całkiem sporo osób, które mają czteroliterowe nicki, a trzyliterowe takie, jak mój, też się trafiają. Zyx (grrrr, mój nick jest trzyliterowa!!!)

Kategorie:

Tworzenie stron internetowych Blog Zarządzenie przedsiębiorstwem Tworzymy bibliotekę Zarządzanie jakością