Добавим в папку template/news файл шаблона create.phtml следующего содержимого:
<html>
<head>
<title>Limb3 tutorial</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body bgcolor="#FFFFFF" text="#000000" >
<h1>Create news</h1>
{{form id='news_form' name='news_form' method='post'}}
{{label for="title"}}Title{{/label}} :
{{input name='title' type='text' size='60' title='Title'/}}<br/>
{{label for="date"}}Date{{/label}} :
{{input name='date' type='text' size='15' title='Date'/}}<br/>
{{label for="annotation"}}Annotation{{/label}} :
{{textarea name='annotation' rows='2' cols='40' title='Annotation'/}}<br/>
{{label for="content"}}Content{{/label}} :
{{textarea name='content' rows='5' cols='40'/}}<br/>
{{input type='submit' value='Create'/}}
{{/form}}
</body>
</html>
По-сути это обычная hmtl-страница, однако есть кое-какие детали:
- {{form}} — не просто тег , а компонент шаблона с каким-либо активным поведением (какое именно — будет сказано потом). Обычно любым активным компонентам шаблона даются свои идентификаторы, то есть указывается атрибут id.
- Обратите внимание, что элементы формы имеют идентификаторы, совпадающие по названию с названиями полей таблицы news.
Теперь расширим наш контроллер NewsController чтобы он мог принимать данные с формы и добавлять новые записи в базу данных:
<?php
lmb_require('limb/web_app/src/controller/lmbController.class.php');
lmb_require('src/model/News.class.php');
class NewsController extends lmbController
{
function doCreate()
{
if(!$this->request->hasPost())
return;
$news = new News();
$news->import($this->request);
$news->save();
$this->redirect();
}
}
Мы добавили метод doCreate, который будет вызываться при получении нашим приложением запросов вида /news/create. Шаблон будет выбираться аналогично, то есть template/news/create.phtml, как в случае и с действием по-умолчанию display.
Разберем тело метода doCreate().
if(!$this->request->hasPost())
return;
lmbController для удобства по-умолчанию содержит в себе объекты Запроса request и Ответа response в качестве своих атрибутов и сокращения записи. Здесь мы проверяем, был ли POST запрос с нашей формы. Если нет — тогда мы просто в первый раз зашли на данную страницу и нужно просто отобразить форму.
$news = new News();
$news->import($this->request);
Мы создали экземпляр класс News и поместили в него все данные из запроса. News автоматически выберет из запроса только те поля, которые есть в таблице news (немного упрощенное описание, но пока оно нас устраивает). Именно поэтому мы дали полям формы в шаблоне create.phtml имена, совпадающие с названиями полей таблицы news.
$news->save();
Мы сохраняем новую записи в базу данных и …
$this->redirect();
перебрасываем пользователя на страницу действия по-умолчанию, то есть на /news
Попробуем набрать в адресной строке /news/create. Должно получиться следующее:
Нам хотелось бы, чтобы к полям новостей применялись следующие правила:
- поля title, date и annotation были бы обязательными для заполнения,
- title был меньше 75 символов,
- content — заполнялся бы по желанию пользователя.
Для этого нам необходимо будет внести некоторые изменения в классы News, NewsController и расширить шаблон create.phtml.
Итак, теперь класс News будет выглядеть следующим образом:
<?php
lmb_require('limb/active_record/src/lmbActiveRecord.class.php');
class News extends lmbActiveRecord
{
protected function _createValidator()
{
$validator = new lmbValidator();
$validator->addRequiredRule('title');
$validator->addRequiredRule('annotation');
$validator->addRequiredRule('date');
lmb_require('limb/validation/src/rule/lmbSizeRangeRule.class.php');
$validator->addRule(new lmbSizeRangeRule('title', 75));
return $validator;
}
}
?>
lmbActiveRecord при вызове метода save() может проверять данные, которые в нее добавили при помощи валидатора. Валидатор создается при помощи метода _createValidator(). По-умолчанию, lmbActiveRecord :: _createValidator() возвращает пустой валидатор. Наша задача — создать свой собственный.
Валидатор — объект класса lmbValidator, который располагается в пакете VALIDATION. Валидатор состоит из набора правил валидации, которые мы в него и добавляем в методе _createValidator().
Обратите внимание, что строку
$validator->addRequiredRule('title');
можно переписать как
lmb_require('limb/validation/src/rule/lmbRequiredRule.class.php');
$validator->addRule(new lmbRequiredRule('title'));
по своей сути эквивалентны, а второй вариант приведен для ясности — в большинстве случаев используется именно сокращеннй первый вариант.
Итого мы использовали 2 правила:
- lmbRequiredRule — обязывает поле присутствовать в проверяемом объекте
- lmbSizeRangeRule — обязывает поле содержать определенное количество символов. В нашем случае — не больше 75.
В действие doCreate() контроллера NewsController нам нужно будет внести некоторые изменения:
<?php
lmb_require('limb/web_app/src/controller/lmbController.class.php');
lmb_require('src/model/News.class.php');
class NewsController extends lmbController
{
function doCreate()
{
if(!$this->request->hasPost())
return;
$news = new News();
$news->import($this->request);
$this->useForm('news_form');
$this->setFormDatasource($news);
if($news->trySave($this->error_list))
$this->redirect();
}
}
?>
Обратите внимание на следующие строки:
$this->useForm('news_form');
$this->setFormDatasource($news);
if($news->trySave($this->error_list))
$this->redirect();
При помощи первых двух строк мы связываем активный компонент формы, находящийся в шаблоне с объектом новости. Это позволит нам не терять данные в полях формы, если часть данных была введена неверно.
Метод lmbActiveRecord :: trySave($error_list) возвращает true, если все данные были введены верно и false, если существовали ошибки. trySave() является лишь «оберткой» для метода save(): метод save() при обнаружении ошибок валидации генерирует исключение класса lmbValidationException, которое ловится в методе trySave().
Теперь добавим в шаблон template/news/create.phtml код для отображения ошибок валидации:
[...]
<h1>Create news</h1>
{{form id='news_form' name='news_form' method='post'}}
{{form:errors to='$fields_errors'/}}
{{list using='$fields_errors' as="$error"}}
<div class="message_error">
<b class='title'>This fields contained errors</b>
<ol>
{{list:item}}
<li><span style="color:red;">{$error.message}</span></li>
{{/list:item}}
</ol>
</div>
{{/list}}
{{label for="title"}}Title{{/label}} : {{input name='title' type='text' size='60' title='Title'/}}<br/>
[...]
Для вывода ошибок мы использовали тег {{form:errors}}, который передает список ошибок валидации из формы в компонент, указанный атрибутом to. Ошибки будут выводится при помощи тегов {{list}} и {{list:item}}.
Ошибки валидации будут попадать в шаблон автоматически, так как мы связали объект класса News с активной формой в методе doCreate() контроллера NewsController.
Теперь попробуем ввести неверные данные и проверить систему валидации:
Если вы увидели нечто подобное, значит вы все делаете правильно.
Шаблон по редактированию новости template/news/edit.phtml выглядит почти точно также как и template/news/create.phtml:
<html>
<head>
<title>Limb3 tutorial</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body bgcolor="#FFFFFF" text="#000000" >
<h1>Edit news</h1>
{{form id='news_form' name='news_form' method='post' }}
{{form:errors to='$fields_errors'/}}
{{list using='$fields_errors' as="$error"}}
<div class="message_error">
<b class='title'>These fields contained errors</b>
<ol>
{{list:item}}
<li><span style="color:red;">{$error.message}</span></li>
{{/list:item}}
</ol>
</div>
{{/list}}
{{label for="title"}}Title{{/label}} : {{input name='title' type='text' size='60' title='Title'/}}<br/>
{{label for="date"}}Date{{/label}} : {{input name='date' type='text' size='15' title='Date'/}}<br/>
{{label for="annotation"}}Annotation{{/label}} : {{textarea name='annotation' rows='2' cols='40' title='Annotation'/}}<br/>
{{label for="content"}}Content{{/label}} : {{textarea name='content' rows='5' cols='40'/}}<br/>
{{input type='submit' value='Edit'/}}
{{/form}}
</body>
</html>
Налицо большое дублирование, которое может быть легко устранено. Мы займемся оптимизацией шаблонов чуть позже.
Теперь добавим метод doEdit в класс NewsController, чтобы наш контроллер мог правильно отрабатывать действие edit:
<?php
[...]
class NewsController extends lmbController
{
[...]
function doEdit()
{
$news = new News((int)$this->request->get('id'));
$this->useForm('news_form');
$this->setFormDatasource($news);
if(!$this->request->hasPost())
return;
$news->import($this->request);
if($news->trySave($this->error_list))
$this->redirect();
}
?>
Строкой
$news = new News((int)$this->request->get('id'));
мы загружаем объект новости с определенным идентификатором, который мы получаем из запроса.
Обратите внимание, что мы связали форму в шаблоне с новостью до проверки $this→request→hasPost(), так как нам нужно передавать данные в шаблон в любом случае. При первоначальном отображении формы поля уже будут заполнены полями из загруженной новости.
Теперь изменим шаблон template/news/display.phtml чтобы вывести ссылку на страницу редактирования новостей. Добавим новую колонку для вывода доступных действий:
<html>
<head>
<title>Newsline</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
</head>
<body>
<h1>Newsline.</h1>
<a href='{{route_url params="action:create"/}}'>Create news</a>
<p/>
{{list using="$#news" as="$item"}}
<table border="1">
<tr>
<th>ID</th>
<th>Date</th>
<th>Title</th>
<th>Actions</th>
</tr>
{{list:item}}
<tr>
<td>{$item.id}</td>
<td>{$item.date}</td>
<td>{$item.title}</td>
<td>
<a href='{{route_url params="action:edit,id:{$item.id}"/}}'>Edit</a>
<a href='{{route_url params="action:delete,id:{$item.id}"/}}'>Delete</a>
</td>
</tr>
<tr>
<td colspan='4'>
{$item.annotation}
</td>
</tr>
{{/list:item}}
</table>
{{/list}}
</body>
</html>
Здесь мы использовали тег {{route_url}}, который формирует URL на основе параметров, которые ему указаны в атрибуте params. По сути route_url осуществляет процесс разбора строки запроса вида /news/create в обратном порядке.
Все, что касается деталей разбора запроса выходит за рамки данного туториала, однако если вам интересны правила, на основе которых осуществляется определение текущего контроллера, посмотрите файл limb/web_app/settings/routes.conf.php.
Поясним работу тега {{route_url}}:
{{route_url params="action:create"/}}
Этот код сформирует строку вида: news/create. news будет взят приложением автоматически, так как мы находимся на странице, за которую отвечает контроллер news. create будет взято из пары action:create, где action - это название параметра, а create - значение параметра.
Точно также формируются ссылки на действия edit и delete. Параметры в атрибуте params тега {{route_url}} разделяются через запятую. То есть тег вида {{route_url params="action:edit,id:{$id}"/}} сформирует ссылку вида: /news/edit/4».
Если вы все сделали правильно, тогда вы должны иметь теперь возможность создавать и редактировать новости.
Для удаления объектов lmbActiveRecord используется метод destroy(). Добавим метод NewsController :: doDelete():
<?php
[...]
class NewsController extends lmbController
{
[...]
function doDelete()
{
$news = new News((int)$this->request->get('id'));
$news->destroy();
$this->redirect();
}
}
?>
Пояснять код нет необходимости.
Далее: Шаг 4. Оптимизация шаблонов. Добавление постраничного вывода