Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Obsługa wielu parametrów metod w PHP - użyteczna klasa Params
Forum PHP.pl > Inne > Oceny
implico
Witajcie ponownie!

Tym razem, w ramach rozbudowy firmowego bloga, chcielibyśmy podzielić się bardzo prostą, ale równie użyteczną klasą pozwalającą na wygodną obsługę parametrów funkcji, które zanadto się "rozrosły".

Przykładowo, przypuśćmy że mamy taką oto funkcję/metodę:

  1. function load(address, async, useCache = false, debug = false, user = NULL, timeout = 0)
  2. {
  3. ...
  4. }


Aby ją wywołać, zamierzając zmienić np. tylko jeden domyślny parametr timeout, musimy przebrnąć przez wszystkie parametry:
  1. load('http://www.implico.pl/', true, false, false, NULL, 100);


Prosiłoby się użycie rozwiązania znanego z JavaScript, gdzie wystarczy przekazać jako jedyny parametr odpowiedni obiekt.

Z pomocą przychodzi nasz klasa Params. Dzięki niej wywołanie ma postać:
  1. load(new Params( array('address' => 'http://www.implico.pl/', 'async' => true, 'timeout' => 100) ));


Więcej o klasie i jej użyciu tutaj:
http://www.implico.pl/klasa_params_wygodna...od_w_php,8.html


P.S. Jeśli istnieje już podobne, być może lepsze rozwiązanie - dajcie znać.
hind
w czym te rozwiązanie jest lepsze od ValueObject lub po prostu tablicy ?
implico
Sądzę że zgrabniej i szybciej jest zapisać konstruktor klasy niż tworzyć nowy obiekt. Poza tym automatycznie obsługiwane są wymagane parametry i wartości domyślne, nie trzeba powielać kodu w każdej metodzie - patrz pełny opis na naszej stronie.
!*!
Takie coś też zapewni automatyczną zmianę parametrów
  1. load(array('async' => true, 'timeout' => 100));


I nie potrzeba dodatkowej klasy. Kwestia przyzwyczajeń do budowy i zarządzania nimi wink.gif
nospor
Ale co tu oceniać? Dwie metody na krzyz: set i get... pierwszy lepszy średnio nierozgarniety programista coś takiego stworzy.
Jak już napisano: tablica wystarczy. Zaś ta klasa jedynie ułatwia przypisywanie domyślnych wartosci.

Kontr klasa, zdecydowanie krótsza i moim zdaniem zdecydowanie bardziej wygodniejsza w użyciu
  1. class Params {
  2.  
  3. public static function Get($array, $name, $default = null){
  4. if (array_key_exists($name, $array))
  5. return $array[$name];
  6. return $default;
  7. }
  8. }


Korzystanie:
  1. function test($params){
  2. echo Params::Get($params, 'raz');
  3. echo Params::Get($params, 'dwa');
  4. echo Params::Get($params, 'trzy','wartosc domyslna dla trzy');
  5. }
  6.  
  7. test(array('raz' => 'razzzzz', 'dwa'=> 'dwaaaaa'));
  8.  

pyro
Moim zdaniem cudowanie na siłę. Poczytajcie o zasadzie KISS i stosujcie się do niej. Taka klasa mogła by mieć sens dla funkcji, w której jest 30 parametrów, ale czy widzieliście kiedyś taką funkcję?

@nospor, twoja klasa sprawia, że wymaganie parametry tak naprawdę nie są wymagane, więc pozwolę sobie nanieść drobną modyfikację

  1. class Params {
  2.  
  3. public static function Get($array, $name, $default = null){
  4. if (array_key_exists($name, $array))
  5. return $array[$name];
  6.  
  7. if(!is_null($default))
  8. return $default;
  9.  
  10. trigger_error('Required parameter not set for: '.$name, E_USER_ERROR);
  11. }
  12. }


Niestety tab mi nie działa w forumowym textarea closedeyes.gif
nospor
@pyro bo ja nie zakładałem, że coś jest wymagane. Jak chcesz takiego założenia, to musisz dodać czwarty parametry $required=false, bo tak Twoja poprawka teraz to wymusza na użytkowniku zawsze wymagalnośc parametru, a wcale tak być nie musi wink.gif
pyro
Cytat(nospor @ 6.02.2013, 12:55:52 ) *
@pyro bo ja nie zakładałem, że coś jest wymagane. Jak chcesz takiego założenia, to musisz dodać czwarty parametry $required=false, bo tak Twoja poprawka teraz to wymusza na użytkowniku zawsze wymagalnośc parametru, a wcale tak być nie musi wink.gif


@nosporku, sama konstrukcja i pojęcie "funkcji" w programowaniu z założenia może mieć obowiązkowe lub opcjonalne parametry, więc wypadałoby to uwzględnić.

Poza tym co wymusza? Nic nie wymusza

  1. function some_func($params)
  2. {
  3. Params::Get($params, 'name1'); // wymagany parametr
  4. Params::Get($params, 'name2', 'default value'); // opcjonalny parametr, w $params nie ustalono tej wartości, więc zostaje przypisana domyślna wartość
  5. // problem może być dla funkcji w której wartość domyślna ma być NULL, ale na to też jest rozwiązanie.
  6. }


Poza tym jak już wspomniałem - według mnie w ogóle tworzenie takiej klasy to bezsens. No chyba, że używana jest funkcja z 30 parametrami, ale czy natknęliście się kiedyś na taką funkcję?

Poza tym idąc dalej tokiem myślenia, klasę tą można przerobić tak, że nie trzeba podawać w metodzie Params::Get argumentu $params
implico
@*!*: takie coś wcale nie zapewni żadnej automatycznej zamiany parametrów. Myślę że to aż nazbyt oczywiste, ale co w przypadku, jeśli chciałbyś zmienić jeden parametr? Znowu trzeba przepisywać pozostałe.

@nospor: "Ale co tu oceniać? Dwie metody na krzyz: set i get... pierwszy lepszy średnio nierozgarniety programista coś takiego stworzy."
A jednak nikt z Was nie stworzył rozwiązania ekwiwalentnego, z obsługą parametrów wymaganych i sensowną obsługą wartości domyślnych. Zaproponowane przez Ciebie rozwiązanie w ogole nie obsługuje parametrow wymaganych i takie sprawdzenie, nawet po odpowiednich przeróbkach, nie jest możliwe bez odwołania się do zmiennej (trzeba zawsze sprawdzić jej wartość, inaczej klasa nie wykryje braku zmiennej wymaganej). Dodatkowo chcąc odwołać się dwukrotnie do zmiennej o wartosci domyślnej, należałoby ją przedtem przypisać do jakiejś zmiennej, aby nie dublować kodu. Poza tym łatwiej odwołać się do np. $par->get('raz'), niż do Params::Get($params, 'raz').

@pyro: zapewniam Cię, że funkcja jest przydatna również nawet w przypadku 5 parametrów. Ciekawe, @nospor twierdzi że to parę metod na krzyż, z kolei Ty każesz stosować KISS (Keep It Simple, Stupid) - taki paradoks. Mimo wszystko sądzę, że rozwiązanie jest proste zarówno w obsłudze, jak w implementacji.
Niestety Twoje rozwiązanie nie zapewnia obsługi parametrów wymaganych, a jedynie wymaga wprowadzenia parametru domyślnego. Raczej miałeś na myśli dodanie parametru, np. isRequired i sprawdzanie tego warunku w 7. linijce. Ale to również ma wadę - jak wspomniałem "takie sprawdzenie, nawet po odpowiednich przeróbkach, nie jest możliwe bez odwołania się do zmiennej (trzeba zawsze sprawdzić jej wartość)".

Ogólnie chodziło o stworzenie EKWIWALENTU (czyli PEŁNEJ funkcjonalności) zapewniającej obsługę parametrów z uwzględnieniem wymagalności i wartości domyślnych, a nie proponowanych przez Was półśrodków. Proszę Was jeszcze o przemyślenie kwestii.
nospor
Cytat
@nosporku, sama konstrukcja i pojęcie "funkcji" w programowaniu z założenia może mieć obowiązkowe lub opcjonalne parametry, więc wypadałoby to uwzględnić.
Tak, tylko że ja nie widzę sensu tego uwzględniania smile.gif
Ale jak już chcesz uwględniać rób to dobrze. Nie możesz uwazac, ze dajac null w default to oznacza ze jest to parametr wymagany. A co jesli nie jest to parametr wymagany? jak bedzie to ok, jak nie bedzie to ok? Jak mam to zapisać? Dajac 0 zamiast null? No ale 0 moze byc wartoscią a nie oznaczeniem, ze parametr nie jest wymagany. Dlatego wlasnie pisze, ze trzeba dopisac jeszcze $required smile.gif

Cytat
Zaproponowane przez Ciebie rozwiązanie w ogole nie obsługuje parametrow wymaganych
Jak juz pisalem: wystarczy dopisac parametr $required. Ja tego nie zrobilem, bo uwazam to za totalnie zbędne.
pyro
Cytat(nospor @ 6.02.2013, 13:12:34 ) *
Tak, tylko że ja nie widzę sensu tego uwzględniania smile.gif
Ale jak już chcesz uwględniać rób to dobrze. Nie możesz uwazac, ze dajac null w default to oznacza ze jest to parametr wymagany. A co jesli nie jest to parametr wymagany? jak bedzie to ok, jak nie bedzie to ok? Jak mam to zapisać? Dajac 0 zamiast null? No ale 0 moze byc wartoscią a nie oznaczeniem, ze parametr nie jest wymagany. Dlatego wlasnie pisze, ze trzeba dopisac jeszcze $required smile.gif


Tak, ale to już zresztą zaznaczyłem:

Cytat(pyro)
// problem może być dla funkcji w której wartość domyślna ma być NULL, ale na to też jest rozwiązanie.


Można przypisać $default jakąś konfiguracyjną daną i potem po niej sprawdzać albo jak już wspomniałeś, dodać parametr $isRequired.
implico
@pyro: Niestety, chyba nie do końca rozumiesz ideę klasy nospora.

"Params::Get($params, 'name1'); // wymagany parametr"

Zawsze zwróci błąd.
pyro
Może gdzieś popełniłem błąd, bo nie jestem u siebie i nie mam gdzie tego sprawdzić, ale według mnie:

  1. funtion some_func($params)
  2. {
  3. Params::Get($params, 'name1'); // Wartość wymgana
  4. Params::Get($params, 'name2', 'default value'); // Wartośc opcjonalna
  5. }
  6.  
  7. some_func(array('name1'=>'some value')); // Nie podano name2


Nie zwróci błędu.

Cytat(implico @ 6.02.2013, 13:10:24 ) *
@pyro: zapewniam Cię, że funkcja jest przydatna również nawet w przypadku 5 parametrów. Ciekawe, @nospor twierdzi że to parę metod na krzyż, z kolei Ty każesz stosować KISS (Keep It Simple, Stupid)


Zasada KISS dotyczy nie tylko uproszczania niezbędnego kodu, ale także pozbycia się nadmiernego, zbędnego kodu.



Cytat(implico @ 6.02.2013, 13:10:24 ) *
Niestety Twoje rozwiązanie nie zapewnia obsługi parametrów wymaganych, a jedynie wymaga wprowadzenia parametru domyślnego.


Nie wymaga, bo dla parametrów obowiązkowych zanim jest sprawdzane wartości domyślnej wywoływane jest return.


Cytat(implico @ 6.02.2013, 13:10:24 ) *
@pyro: zapewniam Cię, że funkcja jest przydatna również nawet w przypadku 5 parametrów. Ciekawe, @nospor twierdzi że to parę metod na krzyż, z kolei Ty każesz stosować KISS (Keep It Simple, Stupid) - taki paradoks. Mimo wszystko sądzę, że rozwiązanie jest proste zarówno w obsłudze, jak w implementacji.


Niestety nie wziąłeś pod uwagę np. dokumentacji, bo IDE zazwyczaj mają podpowiadanie parametrów dla user-made functions, a przy takim rozwiązaniu dla stworzonej funkcji jedyne co może się pokazać to...

Cytat
array $params - a list of parameters for the function


W dużych projektach takie coś jest naprawdę problematyczne.
ano
Czemu nie po prostu:
  1. function f(array $params) {
  2. $default = array('a' => 1, ...); // słownik domyślnych wartości
  3. $params = array_merge($default, $params);
  4. }


Bardzo często stosowane gdy np musimy przechowywać wiele "opcji" w klasie.
viking
Poza tym można też po prostu http://php.net/manual/en/function.func-get-args.php. A to tutaj przedstawione to przecież zwykły rejestr.
mstraczkowski
Moim zdaniem już samo pisanie tak wieloparametrowych funkcji/metod jest złą praktyką

A tworzenie "hooków", aby sobie to ułatwić to już całkiem herezje

Jeśli funkcja zaczyna posiadać sporą ilość parametrów to pierwszy sygnał, aby zadać sobie pytanie czy to nie jest odpowiedni moment, aby utworzyć klasę zamiast pisać kolejne i kolejne parametry .

Takie wieloparametrowe funkcje w większości (nie zawsze) są już tak rozbudowane, że spokojnie mogą stać się jakimś osobnym libem

A jeżeli już to zazwyczaj projektuje się funkcje/metody w taki sposób, aby móc łatwo zarządzać ich parametrami

Przykładowo dlaczego flagi true/false są parametrami przed integerem który na pewno będzie używany częściej niż wspomniane flagi, które z kolei w 90% mogą mieć wartość domyślną ?
hind
@mstraczkowski: mówisz o pisaniu klasy, ale co w przypadku gdy konstruktor przyjmuje pierdyliard parametrów? W tedy tylko tablica lub ValueObject
pyro
Cytat(hind @ 13.02.2013, 08:46:16 ) *
@mstraczkowski: mówisz o pisaniu klasy, ale co w przypadku gdy konstruktor przyjmuje pierdyliard parametrów? W tedy tylko tablica lub ValueObject


Jeżeli konstruktor przyjmuje pierdyliard parametrów, to w 99% przypadków jest to zły konstruktor.
hind
zawsze można potem użyć kilkunastu setterów, ale za sto konstruktor nie będzie musiał przyjmować parametrów.
mstraczkowski
Konstruktor nie służy do ustawiania nie wiadomo jakiej konfiguracji klasy.

Konstruktor powinien przyjmować tylko parametry, które są niezbędne do utworzenia obiektu danej klasy.
Całą resztę można rozwiązać za pomocą setterów, getterów, właściwości (jeżeli ustawiania mają być elastyczne) lub stałe klasy (jeżeli ustawienia klasy są sztywne)

Jeżeli istnieje coś co musi zostać podane zawsze przy tworzeniu obiektu (aby klasa mogła prawidłowo funkcjonować), to wtedy powinno to stać się parametrem konstruktora. Po to, aby w każdej innej metodzie nie sprawdzać czy ktoś za pomocą settera to ustawił i nie rzucać mu wyjątku.

Jeżeli twoja klasa staje się rozbudowana i ilość setterów / getterów cię przerasta można pomyśleć o osobnej klasie konfiguracyjnej dla danej klasy.
Tak jak przykładowo robi to HTML Purifier

Przykładowo:
  1. $oConfig = HTMLPurifier_Config::createDefault();
  2. $oConfig->set('HTML.TidyLevel', 'heavy');
  3.  
  4. $oPurifier = new HTMLPurifier($oConfig);
  5. $sHtml = $oPurifier->purify($sHtml);
ano
...Lub użyć wzorca stworzonego do rozwiązywania takich problemów (budowniczy, builder pattern).
Przykład na http://stackoverflow.com/a/1953567/2034900
pyro
Cytat(hind @ 13.02.2013, 10:17:33 ) *
zawsze można potem użyć kilkunastu setterów, ale za sto konstruktor nie będzie musiał przyjmować parametrów.


Po 1. Konstruktor, a setter to dwie różne rzeczy i każde z nich służy do czego innego, co wyjaśnił @mstraczkowski. Rzecz jasna są przypadki, gdzie ludzie używają setterów na siłę. Dość popularny i według mnie dośc bzdurny:

  1. <?php
  2.  
  3. // Źle
  4.  
  5. $db = new DB();
  6. $db->setHost($host);
  7. $db->setUser($user);
  8. $db->setPassword($password);
  9. $db->setDatabase($database);
  10. $db->connect();
  11.  
  12. ?>


Zamiast po prostu:

  1. <?php
  2. // Dobrze
  3. $db = new DB($host, $user, $password, $database);
  4. ?>


Po 2. Dla rzeczy stałych ustawia się ich dane z góry, a nie ustawia w klasie. Przykładowo:

Nie:

  1. // Źle
  2. class QueryManager {
  3.  
  4. private $asc;
  5. private $desc;
  6.  
  7. public function __construct($asc, $desc)
  8. {
  9. $this->asc = $asc;
  10. $this->desc = $desc;
  11. }
  12. }


Tylko przykładowo:

  1. // Dobrze
  2. class QueryManager {
  3. public const ASC = 'ASC';
  4. public const DESC = 'DESC';
  5. }


Trochę bzdurny przypadek, ale kiedyś spotkałem się z czymś takim u początkującego i to chyba na tym forum.

Po 3: Nawet jak już, to niektóre rzeczy warto rozłożyc na parę setterów, niż wszystko pakować w jeden konstruktor dla czytelności. Przykładowo:

  1. // Dobrze
  2.  
  3. // DocumentManager - klasa, która coś robi z dokumentami, nie chce mi się myśleć dokładnie co
  4.  
  5. $doc = new DocumentManager();
  6. $doc->setAPIParams('api1', 'api2', $ipa, $ipa2); // Rzeczy tyczą się samego API (jakiegoś tam)
  7. $doc->setCacheParams('cache/', true, 3600); // Rzeczy tylko do cache
  8. $doc->setDocSite('http://docs.com', $driver, true); // Tyczy się tylko jakiejś strony z dokumentami
  9.  


A nie:

  1.  
  2. // Źle
  3.  
  4. $doc = new DocumentManager('api1', 'api2', $ipa, $ipa2, 'cache/', true, 3600, 'http://docs.com', $driver, true);


I teraz zakładając, że klasa przy każdym działaniu musi korzystać z wszystkich podanych tu parametrów, lepiej skorzystać z opcji oznaczonej jako "Dobrze", bo zwyczajnie jest lepsza.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.