Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Klasa generująca formularz - dobre praktyki
Forum PHP.pl > Forum > PHP > Object-oriented programming
Turson
Hej,
mam małą zagwozdkę logiczną w tworzeniu klasy generującej formularz. Teoretycznie takową klasę napisałem, ale mam kilka wątpliwości.
Od razu dodam, że aplikacja oparta jest o MVC.
Skrócony kod klasy:
  1. <?php
  2. class MyForm {
  3.  
  4. protected $_action;
  5. protected $_method = 'GET';
  6. protected $_html;
  7.  
  8. public function setAction($value) {
  9. $this->_action = $value;
  10. }
  11.  
  12. public function setMethod($value) {
  13. $this->_method = $value;
  14. }
  15.  
  16. public function startForm(array $options = array()) {
  17. $optionsHtml = '';
  18. if (count($options)) {
  19. foreach ($options as $key => $value) {
  20. $optionsHtml .= ' ' . $key . '="' . $value . '"';
  21. }
  22. }
  23. $this->_html = '<form action="' . $this->_action . '" method="' . $this->_method . '"' . $optionsHtml . '>';
  24. }
  25.  
  26. public function endForm() {
  27. $this->_html .= '</form>';
  28. }
  29.  
  30. public function createElement($type, $name, $label = null, $value = null, array $values = array(), array $options = array()) {
  31. // wygenerowanie kodu HTML input/textarea/select itd.
  32. $this->_html .= $html;
  33. }
  34.  
  35. public function render() {
  36. return $this->_html;
  37. }
  38.  
  39. }


Formularz generuję w widoku, np.
  1. <?php
  2. $form = new MyForm();
  3. $form->setMethod('POST');
  4. $form->startForm();
  5. $form->createElement('text', 'login', 'Nickname');
  6. $form->createElement('password', 'password', 'Password');
  7. $form->createElement('submit', 'submit', null, 'Register new account');
  8. $form->endForm();
  9. echo $form->render();
  10. ?>
  11. Jakiś dalszy tekst


Tutaj mam taką rozkminę, czy formularz lepiej generować w widoku jak powyżej, czy w osobnej klasie, np. application/forms/RegisterForm.php
Pierwsze rozwiązanie widziałem w Yii, a drugie w Zendzie.

A teraz sprawa najważniejsza. Posiadam osobną klasę walidującą pola formularza wg. zasad ustalonych w modelu. Tego nie będę pokazywał, bo nie ma związku. Chcę mieć dostęp do informacji o wszystkich walidujących polach w klasie walidacyjnej.
Kontroler:
Jeżeli naciśnięto przycisk, zbieram dane z formularza, pobieram reguły walidacji i przekazuję to do klasy walidującej np. $this->validate($data, $rules)
$data zawiera tablicę $_POST. W klasie walidacyjnej następuje sprawdzenie poprawności przesłanych danych wg. $rules. W tejże klasie muszę mieć dostęp do tego jaki dane pola ma $label - i to jest cały problem. Nie mam za bardzo pomysłu jak się do tego dostać. Podejrzewam, że w klasie MyForm i metodzie createElement() muszę zapisywać dane każdego tworzonego pola i zapisywać np. w statycznej własności klasy $formData. To nie jest problem. Tylko odwołanie się do np. MyForm::$formData z klasy walidacyjnej przecież nie pobierze tych danych, bo już ich tam nie "będzie".
A po co mi wiedzieć jaki label ma dane pole? Ano po to, że jak nie przejdzie walidacji to do tablicy KlasaWalidacyjna->errors mogę dodać $label.' can not be empty'
Pyton_000
Wypluwasz errory w tablicy z kluczami z name pola i w widoku robisz coś na zasadzie
  1. if($errors->has('email') {
  2. echo $errors->get('email)->first();
  3. }


IHMO startForm powinno zbierać wszystkie opcje włącznie z method i action, skoro już robisz przypisanie po tablicy.
Do tego robisz małe sprawdzanie: jeżeli brak to nic nie ustawiasz w action, a method domyślnie POST
Turson
Z walidacją i wyświetleniem komunikatów nie mam problemu, tylko klasa walidacyjna otrzymuje tablicę $_POST nazwa_pola=>wartość, a potrzebuję dodatkowo na podstawie nazwa_pola pobrać jego label.
Czyli np. w MyFrom->createElement()
  1. $labels[$name] = $label;

gdzieś to przechować, żebym mógł z poziomu klasy walidacyjnej się do tego odwołać <- i to nie wiem jak.
Pyton_000
A na co Ci to? Do reguł validujących dodajesz sobie tekst w przypadku błędu i po problemie.
Turson
To jest jakieś rozwiązanie, ale chciałbym, żeby w aplikacji były defaultowe komunikaty, a jak się doda w regułach customowy komunikat to wtedy on się wyświetli.
Pyton_000
No to dodajesz sobie w klasie Validującej odwołanie do jakiejś tablicy z tekstami (klucz nazwa pola) i sprawdzasz czy jest customowy tekst w rules a jak nie to z tłumaczeń.
Turson
Tablicę rules mam zbudowaną w sposób:
  1. $rules = array(
  2. 'register' => array(
  3. 'NAME POLA' => array(
  4. 'required' => true
  5. ),
  6. 'NAME POLA' => array(
  7. 'min_length' => 3
  8. ),
  9. )
  10. );

czyli namespace formularza (tak sobie wszystko definiuję takim słowem kluczem, ale mniejsza o to) -> name pola -> reguły
Tutaj nie mam żadnego problemu jak już wspomniałem. Chcę gdzieś zapisać do każdego nowego elementu tablicę $tablica[name_pola]=>[label_pola]
I nie wiem gdzie to zapisać, żeby potem z klasy walidacyjnej odwołać się do tablicy, jeżeli mam regułę z name_pola w paremetrze, to odwołam się do $tablica[name_pola] i mam jego label. Chodzi tylko i wyłącznie o to.
Pyton_000
No to jedno rozwiązanie z ... :

- Robisz sobie Interface Input
- Implementujsz każdy z inputów nadając im atrybuty publiczne

Klasa Form tworzy nowe obiekty typu Input i wrzuca do jakiejś kolekcji.

W klasie validującej robisz coś na zasadzie:

$label = Form::get('email')->label;
lub coś w ten deseń.
Turson
Dobrze rozumiem?
  1. <?php
  2. class Form{
  3.  
  4. protected static $_data = array();
  5.  
  6. function createElement($label,$name, $value = null){
  7. // something
  8. $data = new stdClass();
  9. $data->label = $label;
  10. $data->value = $value;
  11. self::_putData($name,$data);
  12. }
  13.  
  14. protected static function _putData($name, $data){
  15. self::$_data[$name] = $data;
  16. }
  17.  
  18. public static function get($name){
  19. $data = self::$_data;
  20. return isset($data[$name]) ? $data[$name] : false;
  21. }
  22.  
  23. }

  1. <?php
  2. $form = new Form();
  3. $form->createElement('Nickname', 'login');

  1. <?php
  2. class Validate{
  3.  
  4. function doSth(){
  5. $input = Form::get('login');
  6. var_dump($input);
  7. }
  8.  
  9. }

  1. <?php
  2. $validate = new Validate();
  3. $validate->doSth();
Pyton_000
Cos w ten deseń wink.gif Tylko zamiast false zwracaj albo null albo array() jeżeli oczekujesz tablicy.
Turson
Już próbowałem już takiego rozwiązania i jak z klasy Validation odwoływałem się do Form::get(), to zwracało mi zawsze pustą tablicę. Dlaczego?
Wydaje mi się, że przy odwołaniu statycznym tablica $data klasy Form się czyściła. Ba, kombinowałem z Singletonem, bo myślałem że to kwestia instancji.
Pyton_000
Czysta bo to kolejny request a więc dane nie są znane.
Przy odbieraniu danych z $_POST wrzuć to w Form, wygeneruj formularz na nowo ale wstawiając value dla inputów i zadziała.

Chociaż i tak uważam że to już kombinowania. Najprościej jak mówiłem, olej label i podziałaj z tekstami validacji
Turson
Pisanie własnego komunikatu o błędzie dla każdej reguły jest jak dla mnie średnim pomysłem, dlatego wolałbym sobie ułatwić życie i wprowadzić automatyczne teksty, jeżeli nie podano własnego. Jak będę miał czas to wrócę do tego kodu i napiszę w tym temacie, gdyby jeszcze coś było smile.gif
memory
Może coś w ten deseń

  1. $rules = array(
  2. 'name' => array( 'required' => true,'min' =>3),
  3. 'link' => 'active_url',
  4. );
  5.  
  6. //Tworzysz sobie np tablice domyślnych nazw:
  7.  
  8. $validation =
  9. array("required" => "The {label} must be required.",
  10. "active_url" => "The {label} is not a valid URL.",);
  11.  
  12. W klasie walidującej
  13.  
  14. //tablica validation
  15. protected $validation = array();
  16. protected $messages = array();
  17.  
  18. function validate($attribute,$rule){
  19.  
  20. //sprawdzanie czy poprawnie itd ($attribute = name, $rule = 'required ')
  21. $this->addError($attribute, $rule);
  22.  
  23. }
  24.  
  25. function addError($attribute, $rule)
  26. $this->message[$attribute] = $this->getMessage($attribute, $rule,$this->validation);
  27. }
  28.  
  29. function getMessage($attribute, $rule, $source){
  30.  
  31. if (isset($source[$rule])) return $source[$rule];
  32.  
  33. }
Turson
@memory, ale co z "{label}" ? Nie stosujesz tutaj żadnej podmiany {label}=>$label
RiE
Może zamiast metody z 6 argumentami lepiej będzie przekazać do niej 1 argument w postaci tablicy w której będą się znajdowały wszystkie niezbędne informacje. Bo co w przypadku jeśli chce dodać klasę albo ID do pola tekstowego? Który parametr za to odpowiada? Ostatni? A jak będę chciał dodać klasę do label to też ostatni?
  1. public function createElement($type, $name, $label = null, $value = null, array $values = array(), array $options = array()) {
  2. // wygenerowanie kodu HTML input/textarea/select itd.
  3. $this->_html .= $html;
  4. }


Lepszym wyjściem moim zdaniem byłoby:

  1. protected $inputs = array();
  2.  
  3. public function createElement($input)
  4. {
  5. $this->inputs[$input['input']['name']] = $input;
  6. }
  7.  
  8. public function getElement($name)
  9. {
  10. return $this->inputs[$name];
  11. }
  12.  


  1. $form = new MyForm();
  2. $form->createElement(array(
  3. 'input' => array(
  4. 'name' => 'pole_1',
  5. 'type' => 'text'
  6. 'options' => array(
  7. 'value' => 'Domyślna wartość',
  8. ...
  9. ),
  10. 'attributes' => array(
  11. 'id' => 'pole_1'
  12. 'class' => 'form-input',
  13. 'placeholder' => 'Tekst...',
  14. ...
  15. )
  16. ),
  17. 'label' => array(),
  18. //'errors' => array()
  19. ));


Następnie przekazujesz ten formularz do widoku i w widoku Dekorator odpowiada za poprawne wyświetlenie.

  1. <div class="my-form">
  2. <?php echo $this->renderLabel($form->getElement('pole_1)); ?>
  3. <?php echo $this->renderInput($form->getElement('pole_1)); ?>
  4. <?php echo $this->renderErrors($form->getElement('pole_1)); ?>
  5. </div>


W renderLabel() dajesz input i tworzysz label na podstawie klucza label. W renderInput() tworzysz input na podstawie klucza input. A podczas walidacji dodajesz klucz error i jeżeli nie jest pusty to wyświetlasz błedy pod polem w którym się pojawiły.

W $rules przydałoby się jeszcze przekazać informację z jakiego validatora chcesz skorzystać bo może się zdarzyć tak że dwa validatory będą miały dwa takie same klucze np. min_length albo pattern w którym będzie wyrażenie Regexp i co wtedy? Skąd wiadomo z jakiego validatora skorzystać?
memory
  1. miało być {attribute}; :).
  2.  
  3. function addError($attribute, $rule){
  4. $message = $this->getMessage($attribute, $rule,$this->validation);
  5. $message = str_replace('{attribute}', $this->getAttribute($attribute), $message);
  6. $this->message[$attribute] = $message;
  7. }
  8.  
  9. function getAttribute($attribute){
  10.  
  11. // tutaj porownujesz z danymi z Post czy atrybut posiada label
  12.  
  13. }
  14.  
  15.  

kayman
imo klasa generująca formularz to w ogóle zła praktyka, od tego są widoki w Twigu, Smarty etc. ale ..... oto mój burdelik smile.gif

  1.  
  2. // formularz kontaktowy
  3.  
  4. if ($error = Main::getSessionValue('error')) {
  5. Main::unsetSessionValue('error');
  6. }
  7.  
  8. $form = new Form();
  9. $form->addFile(array('type' => 'text', 'name' => 'name', 'value' => $error['name'], 'id' => 'name', 'description' => 'Imię i nazwisko lub nazwa firmy:', 'validation' => 'vname'));
  10. $form->addFile(array('type' => 'text', 'name' => 'mail', 'value' => $error['mail'], 'id' => 'mail', 'description' => 'Adres email:', 'validation' => 'vmail'));
  11. $form->addFile(array('type' => 'textarea', 'name' => 'message', 'value' => $error['message'], 'id' => 'message', 'description' => 'Treść wiadomości:', 'validation' => 'vmesage'));
  12. $form->addFile(array('type' => 'text', 'name' => 'captcha', 'id' => 'captcha', 'description' => $captcha->getCaptcha(), 'validation' => 'vcaptcha'));
  13. $form->addFile(array('type' => 'button', 'class' => 'art-button', 'value' => 'Wyślij'));
  14. $form->addFile(array('type' => 'hidden', 'name' => 'action', 'value' => 'email'));
  15. $this->content = $form->getForm();
  16.  


jak widać dobra praktyka to raczej unikać takich leniwców biggrin.gif
Pyton_000
A wystarczyłoby
  1. new InputText('name', val|null, array('params'=>'sss'));
Turson
Co myślicie o rozwiązaniu: w $form->createElement() w argumencie przekazuję tablicę $rules = array('required' => true), w klasie Form następuje zapisanie w sesji reguł walidujących przy konkretnych polach. Z sesji odczytam, sprawdzę i czyszczę sesję.
Zapiszę w sesji: reguły walidujące i dane pola (label, name, value itd)
aniolekx
moim zdaniem możesz sobie tworzyć formularze dynamicznie w widoku, ale jeżeli chcesz do nich bindować dane to już nie tedy droga.
Turson
To jakie rozwiązanie, żeby budować formularz obiektem mniej więcej jak podałem, a dodatkowo po wysłaniu formularza mieć dostęp m.in. do labela podając np. jego 'name' ?
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.