Walidacja ORM [KO3.2]

W tym wpisie chciałbym pokazać jak można używać walidacji ORM do walidowania wprowadzanych treści w formularzu. Załóżmy, że mamy tabelę z artykułami, w której są takie pola jak tytuł, treść, status, data, data modyfikacji, nota. Pole tytuł powinno zawierać 10-100 znaków, pole treść 20-2000 w którym dodatkowo powinny być usuwane znaczniki html, potrójne białe znaki i wielokrotnie powtarzające się znaki, pole status mogłoby przyjąć tylko dwie wartośći (0 -nieaktywny, 1 – aktywny), pole data dodawane byłoby automatycznie.

Model do artykułów z walidacją ORM, może prezentować się tak:

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Model_Article extends ORM{
    
    public function rules()
    {
        return array(
            'title' => array(
                array('not_empty'),
                array('min_length', array(':value', 10)),
                array('max_length', array(':value', 100)),
            ),
            'content' => array(
                array('not_empty'),
                array('min_length', array(':value', 20)),
                array('max_length', array(':value', 2000)),
            ),
            'state' => array(
                array('not_empty'),
                array('in_array', array(':value', array(0,1)) )
            ),
        );
    }
    
    public function labels()
    {
        return array(
            'title' => 'Title',
            'content' => 'Content',
            'state' => 'State',
        );
    }

    public function filters()
    {
        return array(
            'title' => array(
                array('trim'),
                array('strip_tags'),
                array('preg_replace', array('/(\s{3,})/', ' ', ':value')),
                array('preg_replace', array('~(.?)\1{4,}~', '$1', ':value')),
            ),
            'content' => array(
                array('trim'),
                array('strip_tags'),
                array('preg_replace', array('/(\s{3,})/', ' ', ':value')),
                array('preg_replace', array('~(.?)\1{4,}~', '$1', ':value')),
            ),
        );
    }
    
    public function create(Validation $validation = NULL)
    {
        return parent::create($validation);
    }

    public function update(Validation $validation = NULL)
    {
        return parent::update($validation);
    }
}

Przyjrzyjmy się dokładniej co w modelu mamy. Na początku mamy reguły dotyczące tytułu, treści i statusu, np. dla treści:

array('not_empty'),

Oznacza, że nie może ona być pusta.

array('min_length', array(':value', 20)),

Oznacza, że minimalna długość treści to 20 znaków, itd.

Następnie mamy labele. Mówią one nam jaka nazwa ma być wyświetlana w przypadku błędu. Np. jeśli treść jest pusta, to validacja zwróci komunikat zgodnie z plikiem komunikatów:

'not_empty'     => ':field must not be empty',

Zostanie zwrócone
nazwa_inputa must not be empty
Z kolei gdy podamy label, to np.
Content must not be empty

Warto tutaj wspomnieć, że komunikaty te mogą być tłumaczone na inne języki z plików językowych.

Później mamy filtry.

array('trim'),

Spowoduje wywołanie funkcji php trim(treść pola); czyli usunie białe znaki z początku.

array('strip_tags'),

Wywoła sprip_tags(treść) czyli usunie znaczniki html.

array('preg_replace', array('/(\s{3,})/', ' ', ':value')),

Spowoduje wywołanie funkcji preg_replace(‚/(\s{3,})/’, ‚ ’, treść); Czyli zamieni powtarzające się białe znaki na spację.

array('preg_replace', array('~(.?)\1{4,}~', '$1', ':value')),

Wywoła preg_replace, które odpowiada za zamianę powtarzających się znaków na dany znak, np. bbbbb zostanie zamienione na b.

Filtry wykonywane są po przejściu walidacji, przed zapisaniem do bazy.

Kontroler obsługujący dodawanie może prezentować się tak:

<?php defined('SYSPATH') or die('No direct script access.');

class Controller_Article extends Controller_Master {

    public function action_add()
    {
        $this->template->title = __('Article').' - '.__('Add');
        $this->template->content = View::factory('article_save')
            ->set('title', $this->template->title)
            ->bind('data', $data)
            ->bind('msg', $msg)
            ->bind('errors', $errors);

        $article = ORM::factory('article');

        if (isset($_POST['submit']))
        {
            $extra_rules = Validation::factory($_POST)
                ->rule('note', 'not_empty');

            $article
                ->values($_POST, array('title', 'content', 'state', 'note'))
                ->values(array('date' => date('Y-m-d H:i:s')));
            
            try
            {
                $mailing->create($extra_rules);
                $msg = '<div class="box-green">'.__('Successful! You added this data correctly').'</div>';
            }
            catch (ORM_Validation_Exception $e)
            {
                $errors = $e->errors('article');
                $data = $_POST;
                $msg = '<div class="box-red">'.__('Please correct the errors').'</div>';
            }
        }
    }

    public function action_edit()
    {
        $id = $this->request->param('id');
        $this->template->title = __('Article').' - '.__('Edit');
        $this->template->content = View::factory('article_save')
            ->set('title', $this->template->title)
            ->bind('data', $data)
            ->bind('msg', $msg)
            ->bind('errors', $errors);

        $article = ORM::factory('article', $id);

        if (isset($_POST['submit']))
        {
            $extra_rules = Validation::factory($_POST)
                ->rule('note', 'not_empty');

            $article
                ->values($_POST, array('title', 'content', 'state', 'note'))
                ->values(array('date_edit' => date('Y-m-d H:i:s')));
            
            try
            {
                $mailing->update($extra_rules);
                $msg = '<div class="box-green">'.__('Successful! You edited this data correctly').'</div>';
            }
            catch (ORM_Validation_Exception $e)
            {
                $errors = $e->errors('article');
                $data = $_POST;
                $msg = '<div class="box-red">'.__('Please correct the errors').'</div>';
            }
        }
        else
        {
            $data = $article->as_array();
        }
    }
}

Spójrzmy teraz na kontroller i akcję add. Najpierw wysyłamy do szablonowego widoku tytuł i wczytujemy do treści w szablonie widok article_save:

$this->template->title = __('Article').' - '.__('Add');
$this->template->content = View::factory('article_save')

Do tego widoku również przesyłamy tytuł i deklarujemy zmienne data(do wartości z _POST), msg (do komunikatu), errors (do błędów walidacji).

->set('title', $this->template->title)
->bind('data', $data)
->bind('msg', $msg)
->bind('errors', $errors);

Następnie tworzymy obiekt article, którym zapiszemy nowy artykuł

$article = ORM::factory('article');

Później sprawdzanie jest czy wysłano formularz (czy występuje _POST submit)

if (isset($_POST['submit']))
{

Kolejną rzeczą jaka wykonywana jest w akcji add to dodanie dodatkowych reguł (takich co nie występują w modelu, a chcielibyśmy dodatkowo walidować), pole note nie może być puste:

$extra_rules = Validation::factory($_POST)
    ->rule('note', 'not_empty');

Potem deklarujemy co ma być przesłane ze zmiennej _POST do zapisania i ew. inne pola (np. data, address ip)

$article
    ->values($_POST, array('title', 'content', 'state', 'note'))
    ->values(array('date' => date('Y-m-d H:i:s')));

Ostatnim krokiem to próba zapisania danych

try
{
    $mailing->create($extra_rules);
    $msg = '<div class="box-green">'.__('Successful! You added this data correctly').'</div>';
}

Jeśli nie ma błędów walidacji i wszystko pójdzie OK, przesyłamy do zmiennej msg, komunikat sukcesu, w przeciwnym razie, przesyłamy do tablicy błędów: błędy walidacji, do inputów: wysłane dane, do komunikatu: komunikat żebyśmy poprawili błędy.

catch (ORM_Validation_Exception $e)
{
    $errors = $e->errors('article');
    $data = $_POST;
    $msg = '<div class="box-red">'.__('Please correct the errors').'</div>';
}

Czym się różni akcja edit?
Podczas deklaracji artykułu podajemy id artykułu pobrane z parametru id (z linku)

$id = $this->request->param('id');

Tworzymy obiekt konkretnego artykułu:

$article = ORM::factory('article', $id);

I jeśli nie były wysłane dane z formularza, wczytujemy dane z bazy:

if (isset($_POST['submit']))
{
    //...
}
else
{
    $data = $article->as_array();
}

Widok do dodawania/edycji może przedstawiać się tak:

<div class="content-box-header">
    <h3><?php echo $title?></h3>
</div>
<div class="content-box-content">
    <?php echo $msg ?>
    <?php echo  Form::open(Request::current())?>
    <fieldset>
        <legend><?php echo __('Fill the fields')?></legend>
        <?php echo Form::label('title', __('Title').': ').Form::input('title', Arr::get($data, 'title'))?> <span class="error"><?php echo Arr::get($errors, 'title');?></span>
        <?php echo Form::label('content', __('Content').': ', array('class' => 'vat')).Form::textarea('content', Arr::get($data, 'content'))?> <span class="error"><?php echo Arr::get($errors, 'content');?></span>
        <?php echo Form::label('state', __('State').': ').Form::select('state', array(NULL => __('Select'), '0' => __('Unactive'), '1' => __('Active')), Arr::get($data, 'state'))?> <span class="error"><?php echo Arr::get($errors, 'state');?></span>
        <?php echo Form::label('note', __('Note').': ').Form::input('note', Arr::get($data, 'note'))?> <span class="error"><?php echo Arr::get($errors, 'note');?></span>
    </fieldset>
    <?php echo Form::submit('submit', __('Save')).Form::close();?>
</div>

W widoku przyjrzyjmy się jak tworzony jest input:

<?php echo Form::label('title', __('Title').': ').Form::input('title', Arr::get($data, 'title'))?> <span class="error"><?php echo Arr::get($errors, 'title');?></span>

Najpierw tworzony jest Label do inputa, potem input o nazwie title z danymi z tablicy $data o indexie title, obok niego wyświetlane są błędy (jeśli występują).

To by było na tyle odnośnie walidacji ORM. W modelu w funkcjach create, lub update możemy wykonywać jeszcze inne czynności, jak np. robienie miniatur z przesłanego zdjęcia.

PS.
Możemy także tworzyć własne reguły walidacji, np. sprawdzać czy ip jest zbanowany:

->rule('ip', 'Model_Article::ban_ip')

A w tej funkcji pobierać np. z bazy zbanowane ip i zwrócić FALSE jeśli jest zbanowany.
Jeśli funkcja w modelu przyjmuje więcej parametrów, to przy dodawaniu reguły je podajemy

->rule('custom', 'Model_Article::custom', array(':value', $param1, $param2))

:value to wartość z inputa. W komunikatach błędów można wtedy użyć:

'Model_Article::custom'     => ':field must be from :param1 to :param2',

Przy external_rules i własnych regułach (komunikatach), komunikaty błędów dla modelu article powinny być w:
/messages/article/_external.php

7 Odpowiedzi :“Walidacja ORM [KO3.2]”

  1. dlaczego masz

    class Controller_Article extends Controller_Master
    

    zamiast

    class Controller_Article extends Controller
    

    ?
    PS
    opornie idzie ta kohanaphp ;/

    • Mariusz napisał:

      Controller_Master to przykładowy – szablonowy kontroler, który rozszerza Controller_Template:

      class Controller_Master extends Controller_Template {
          public $template;
      ...
      

      Powinien być ładowany jako domyślny w bootstrapie, tak aby po otwarciu strony właśnie on się wywoływał.

      • zrobiłem tak jak pisze ale mam błąd:

        ErrorException [ Strict ]: Creating default object from empty value
        $this->template->content = View::factory('save')
        

        W czym może być problem?
        widok dodałem w aplication/views/article/save.php
        i także w aplication/views/article_save.php
        bo zapomniałem co robi _ :P

        • 8 $this->template->content = View::factory(‚article_save’)
          także

        • Mariusz napisał:

          Hmm nie wiem na jakim jesteś etapie z kohaną.. Widziałeś może Pierwsze starcie? Wpis stary ale pewne podstawy wyjaśnia. Jeśli chcesz przesłać do widoku zmienną, albo kolejny widok do rób to tak:

          $this->template->nazwa_zmiennej = View::factory('article_save');
          

          Widok article_save to plik w folderze /application/views/article_save.php

          PS.
          W tym poradniku nie przesyłamy widoku do szablonu, tylko nazwę pliku i potem w widoku szukamy

          <?php include Kohana::find_file('views', $content);?>
          

          Tak jednak nie polecałbym… :) tylko jak wspomniałem wcześniej.

          • 1) Możesz pod komentarzami dać info o bbcode? nie wiem jak się kod wstawia i pewnie wielu czytelników nie wie.

            2) A co ma robić Controller_Master , dla prostego przykładu może być:

            class Controller_Article extends Controller_Template {
            

            Co robi u Ciebie ten Controller_Master?

            3) Mam w kohana 3.2.2 taki błąd z Twojego przykładu:

            http://127.0.0.1/article/add
            View_Exception [ 0 ]: The requested view template could not be found
            

            A plik jak i wywołanie template jest. Już nie wiem co jest nie tak, a wszystkie tutoriale od Ciebie przeczytałem :/

            template->content = View::factory('article_save')
                        ->set('title', 'tytul')
                        ->bind('data', $data)
                        ->bind('msg', $msg)
                        ->bind('errors', $errors);
            ...
            ?>
            
  2. Mariusz napisał:

    Aby dodać kod umieść w [ code ], albo [ php ] (bez spacji). Kontroler Master to nic innego jak kontroler szablonowy (wczytuje szablon strony, pliki css, js i główną stronę). To to samo co kontroler Default w pierwsze starcie. Zerknij jeszcze raz na spokojnie do tego wpisu. Tak jak pisałem tam jest trochę inaczej przesyłany widok..
    Błąd Ci wywala bo nie ustawiłeś domyślnego szablonu

    public $template = 'default';
    

    Szablon wtedy powinien być w /application/views/default.php

Dodaj komentarz

Dodając kod PHP używaj tagów: [php][/php]

*