Callbacki w kontrolerach (beforeFilter, beforeRender lub afterFilter) to bardzo użyteczne narzędzia, pozwalają zaoszczędzić sporo czasu, czynią kod bardziej czytelnym i bardziej DRY. Jednym słowem miód… chyba, że zrobią się zbyt duże.
Sprawa najczęściej wygląda tak:
- chcemy, żeby jakiś kod wykonał się przed każdym requestem w kontrolerze, więc wrzucamy go do beforeFilter
- z kolei inne rzeczy mają się wykonywać tylko przed niektórymi akcjami, więc je też wrzucamy do beforeFilter dodając jeszcze blok if-then-else, który zdecyduje kiedy je wykonać
- mamy jeszcze trochę kodu, który chcemy wykonać przed requestem, ale tylko gdy użytkownik jest zalogowany, kolejny if-then-else ląduje w beforeFilter
- i tak dalej, aż przestajemy nad wszystkim panować
Można by zrobić to lepiej? No cóż, spójrzmy na poniższy kawałek kodu z Ruby on Rails.
class UsersController < ApplicationController
before_filter :do_something, :do_always
before_filter :do_something_else, :only => [:show, :new]
before_filter :do_something_different, :except => :edit
# method definitions commented out
end
Oto co tu się dzieje:
- metody do_something i do_always zostaną wykonane przed każdym requestem
- do_something_else tylko przed akcjami show i new
- do_something_different przed wszystkimi akcjami oprócz edit
Prosto i czysto. Każdy kawałek kodu, który chcemy wykonać w before_filter (niezależnie od warunków) ląduje w osobnej metodzie kontrolera. Kontroler sam decyduje co i kiedy wykonać.
Jest to łatwiejsze w zarządzaniu i testowaniu niż podejście zastosowane w CakePHP. Więc… przenieśmy je do Cake’a.
Kod AppControllera do pobrania z pastie.
Teraz możemy już robić takie rzeczy:
var $beforeFilter = array(
array(
'methods' => array('do_something', 'do_always')
),
array(
'methods' => array('do_something_else'),
'only' => array('show', 'new')
),
array(
'methods' => array('do_something_different'),
'except' => array('edit')
)
);
function _do_something() { }
function _do_always() { }
function _do_something_else() { }
function _do_something_different { }
Powyższy kod jest ekwiwalentem kodu kontrolera w Ruby on Rails z początku postu. Znaki podkreślenia przed nazwami metod pozwalają je odróżnić od regularnych akcji zdefiniowanych w kontrolerze.
Dodatkowo możemy także określić inne warunki wykonania metod przez callback.
var $beforeFilter = array(
array(
'methods' => array('do_something'),
'if' => array('is_admin')
),
array(
'methods' => array('do_something_else'),
'unless' => array('is_logged_in')
)
);
function _logged_in() { }
function _is_admin() { }
Teraz metoda do_something zostanie wywołana tylko gdy _is_admin zwróci true, a do_something_else gdy _is_logged_in zwróci false. Zresztą kod mówi sam za siebie.
Oczywiście wszystkie opcje (only, except, if, unless) mogą być używane razem lub w dowolnych konfiuracjach.
Definicje w tabeli $beforeFilter (lub $beforeRender, lub $afterFilter) będą analizowane w kolejności w jakiej się się w niej pojawią (o ile oczywiście ich warunki zostaną spełnione).
Dodatkowo dzięki rozszerzeniu metody __mergeVars, która jest automatycznie wywoływana przy inicjalizacji kontrolera, możliwe jest definiowanie tablic również w AppControllerze i PluginAppControllerze. Wtedy pierwszeństwo mają te z AppControllera, następnie PluginAppControllera i na końcu ze zwykłego kontrolera.
Enjoy.