Реализация действий контроллеров не обязательно должна находиться в самом контроллере. Limb3 содержит также специальный набор классов, который позволяет создавать отдельные классы для каждого действия. Такие классы называются команды, и для них существует специальный интерфейс lmbCommand (limb/web_app/src/command/lmbCommand.interface.php).
Классы команд можно найти в папке limb/web_app/src/command/.
Те или иные команды «умеют» практически все, что умеет lmbController. Например, есть класс lmbActionCommand, который используется для создания команд, реализующих действия. Также есть lmbFormCommand, который используется для обработки форм (он отнаследован от lmbActionCommmand).
Команды на исполнение запускаются при помощи метода perform() без параметров. Обычно вызов команд производится из соответсвующего метода контроллера.
Команды удобно использовать в случае, когда есть какой-либо код, который необходимо использовать повторно в различных контроллерах, однако выделять для этого общего предка не слишком удобно.
Для демонстрации использования команд, мы реализуем действияе checkout контроллера CartController в виде команды CartCheckoutCommand. Потом мы реализуем вызов этой команды из контроллера CartController.
Приведем код класса сначала целиком, а потом поясним все моменты.
Файл shop/src/controller/cart/CartCheckoutCommand.class.php:
<?php lmb_require('limb/web_app/src/command/lmbFormCommand.class.php'); lmb_require('src/model/Order.class.php'); class CartCheckoutCommand extends lmbFormCommand { protected $cart; protected $user; function __construct($cart) { parent :: __construct(null, 'checkout_form'); $this->cart = $cart; $this->user = $this->toolkit->getUser(); } function _onBefore() { if(!$this->cart->getItemsCount()) { $this->flashMessage('Your cart is empty! Nothing to checkout!'); $this->redirect('/'); $this->halt(); } if(!$this->user->getIsLoggedIn()) { $this->flashMessage('Your are not logged in yet! Please login or register to checkout!'); $this->redirect(); $this->halt(); } } function _onShow() { $this->view->set('cart', $this->cart); $this->setFormDatasource($this->user); } function _onValid() { $order = Order :: createForCart($this->cart); $order->setAddress($this->request->get('address')); $order->setUser($this->user); if($order->trySave($this->error_list)) { $this->cart->removeAll(); $this->flashMessage('Your order has been sent. Your cart is now empty.'); $this->redirect('/'); $this->halt(); } } } ?>
Итак, начнем наши пояснения.
lmb_require('limb/web_app/src/command/lmbFormCommand.class.php'); lmb_require('src/model/Order.class.php'); class CartCheckoutCommand extends lmbFormCommand { protected $cart; protected $user; function __construct($cart) { parent :: __construct(null, 'checkout_form'); $this->cart = $cart; $this->user = $this->toolkit->getUser(); } [...]
В качестве базового класса для нашего CartCheckoutCommand мы выбрали lmbFormCommand, так как оформление заказа связано с заполнением формы.
Конструктор lmbFormCommand принимает 3 параметра:
В нашем случае мы указали только 2-й параметр. Имя шаблона lmbFormCommand определит точно также как и lmbController - по текущему действию и контроллеру. Строго говоря, имя шаблона будет угадано классом lmbActionCommand, от которого отнаследован lmbFormCommand. Валидатор нам не потребуется, так как мы собираемся использовать валидатор, встроенный в класс Order.
В некоторых случаях название формы в шаблоне можно также не указывать - lmbFormCommand попробует его угадать, приблизительно так же, как и название шаблона. Угадывание производится по имени текущего контроллера. Например, если текущий контроллер NewsController, то имя формы будет угадано как news_form.
Конструктор нашего класса CartCheckoutCommand принимает объект корзины $cart.
Внутри lmbFormCommand доступны следующие уже знакомые нам объекты:
Передача ошибок валидации в шаблон производится автоматически.
Движемся дальше…
class CartCheckoutCommand extends lmbFormCommand { [...] function _onBefore() { [...] } function _onShow() { [...] } function _onValid() { [...] } }
Класс lmbFormCommand уже содержит реализацию метода perform(), и по ходу выполнения он вызывает некоторые другие методы, которые дочерние классы могут перекрывать. Назовем кратко эти методы:
В нашем случае достаточно перекрыть методы _onBefore(), _onShow() и _onValid().
class CartCheckoutCommand extends lmbFormCommand { [...] function _onBefore() { if(!$this->cart->getItemsCount()) { $this->flashMessage('Your cart is empty! Nothing to checkout!'); $this->redirect('/'); $this->halt(); } if(!$this->user->getIsLoggedIn()) { $this->flashMessage('Your are not logged in yet! Please login or register to checkout!'); $this->redirect(); $this->halt(); } } [...] }
В методе _onBefore() мы делаем 2 проверки:
Если какое-либо из этих условий не выполняется, мы даем знать пользователю об этом при помощи метода flashMessage($message) и перебрасываем его на главную страницу сайта при помощи метода redirect(). Эти оба метода имеют полностью аналогичный смысл как и в классе lmbController.
Теперь о вызове метода halt(). Методы вида _onBefore() и т.д. не возвращают из себя ничего, поэтому прервать ход выполнения команды можно только сгенерировав исключение. Именно для этого и существует метод halt() - он генерирует иключение специального типа, которое тут же ловится в lmbFormCommand, при этом выполенения метода perform() завершается.
class CartCheckoutCommand extends lmbFormCommand { [...] function _onShow() { $this->view->set('cart', $this->cart); $this->setFormDatasource($this->user); } [...]
Если форма будет отображена в первый раз (не вообще в первый раз, имеется ввиду отображение начального состояния формы, а не тогда когда она была отправлена), мы должны заполнить ее содержимое данными текущего пользователя.
Плюс мы передаем в шаблон объект корзины для того, чтобы иметь возможность вывести содержимое корзины на страницы.
И последнее…
class CartCheckoutCommand extends lmbFormCommand { [...] function _onValid() { $order = Order :: createForCart($this->cart); $order->setAddress($this->request->get('address')); $order->setUser($this->user); if($order->trySave($this->error_list)) { $this->cart->removeAll(); $this->flashMessage('Your order has been sent. Your cart is now empty.'); $this->redirect('/'); $this->halt(); } }
Мы создали новый объект класса Order при помощи статического метода createForCart($cart), заполнили поле адреса доступки address и указали пользователя. Если заказ был успешно сохранен, мы ошищаем корзину мне помощи метода removeAll(), даем знать пользователю, что операция прошла успешно и перебрасываем его на главную страницу.
Теперь нам необходимо вызвать только что созданную команду из контроллера CartController
Файл shop/src/controller/CartController.class.php:
<?php class CartController extends lmbController { [...] function doCheckout() { $cart = $this->_getCart(); $this->performCommand('src/controller/cart/CartCheckoutCommand', $cart); } [...] } ?>
Метод lmbController :: performCommand() принимает в качестве агрументов любое количество параметров (один или больше). Первый параметр указывает на путь до команды, второй и другие - будут переданы в том же порядке в конструктор команды. В нашем случае мы передали в команду объект корзины $cart.
Наконец, приведем код шаблона по оформлению заказа cart/checkout.html
Файл shop/template/cart/checkout.html:
<core:set title='Checkout'/> <core:WRAP file="page.html" as="content"> <core:datasource from='#cart'> Your cart contains {$items_count} items. <list:LIST from="items"> <table cellpadding="0" cellspacing="0" class='list'> <thead> <tr> <th>Title</th> <th>Price</th> <th>Quantity</th> <th>Summ</th> </tr> </thead> <list:item> <tr class='{$Parity}'> <td>{$product.title}</td> <td>${$price|number:2, '.'}</td> <td>{$quantity}</td> <td>${$summ|number:2, '.'}</td> </tr> </list:item> </table> </list:LIST> Total summ is : <b>${$total_summ|number:2, '.'}</b> <br/> <form name='checkout_form' id='checkout_form' method='POST' runat='server'> <label for='address'>Delivery address:</label><br/> <textarea type="text" name="address" id="address" title="Delivery address"></textarea><br/> <input type='submit' class='button' name='submitted' value="Finish order" class='button'/><br/> </form> </core:datasource> </core:wrap>
Шаблон содержит отображение списка товарных позиций корзины, также как и в шаблоне /cart/display.html.
Ниже находится форма, которая позволяет ввести адрес доставки. Помните, что мы передали в эту форму данные текущего пользователя, поэтому если пользователь ранее ввел адрес доставки при регистрации или на странице профайла, то это значение будет отображено в соответствующем поле.
Ссылка на действие по оформлению заказа уже должны была у нас быть на странице /cart.
Вот так должна выглядеть страница /cart/checkout, если мы предварительно положим что-нибудь в корзину:
Наши пользователи могут теперь добавлять новые заказы. Осталость совсем немного из того, что мы запланировали:
Итак, следующий шаг: Шаг9. Работа с заказами.
Обсуждение