Development

Documentation/ru_RU/my-first-project (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of Documentation/ru_RU/my-first-project

Show
Ignore:
Author:
Nikitin (IP: 91.76.47.103)
Timestamp:
12/07/08 09:44:40 (9 years ago)
Comment:

Первая редакция

Legend:

Unmodified
Added
Removed
Modified
  • Documentation/ru_RU/my-first-project

    v0 v1  
     1Мой первый проект на Symfony 1.1 
     2======================== 
     3 
     4Ну что ж, приступим? Давайте вместе создадим полнофунциональное веб-приложение за один  
     5час. Что будем делать? Интернет-магазин? Отлично, давайте что-нибудь другое… Блог?  
     6Вот это то, что нужно. Поехали…  
     7 
     8Это руководство предполагает, что вы работаете с веб-сервером Apache, установленном 
     9на локальном компьютере. Также вам нужен PHP 5.1.3 или новее. 
     10 
     11Установка Symfony и инициализация проекта 
     12------------------------------------------ 
     13 
     14Чтобы быстро начать создание веб-приложения, используем Symfony SandBox. Это пустой  
     15проект, в который уже включены все необходимые библиотеки и сделаны основные настройки.  
     16Самое большое преимущество Symfony SandBox перед другими вариантами установки - это то,  
     17что вы можете немедленно начинать экспериментировать с Symfony.  
     18 
     19Скачайте Symfony SandBox здесь: [sf_sandbox_1_2.tgz](http://www.symfony-project.org/get/sf_sandbox_1_2.tgz) 
     20или здесь: [sf_sandbox_1_2.zip](http://www.symfony-project.org/get/sf_sandbox_1_2.zip), 
     21и распакуйте его в корневую директорию вашего веб-сервера. На Linux-системах рекомендуется  
     22сохранять права доступа, указанные в tar-файле (используя команду -p).  
     23См. файл README для получения более подробной информации. В результате этого файловая  
     24структура должна выглядеть таким образом:  
     25 
     26    www/ 
     27      sf_sandbox/ 
     28        apps/ 
     29          frontend/ 
     30        cache/ 
     31        config/ 
     32        data/ 
     33        doc/ 
     34        lib/ 
     35        log/ 
     36        plugins/ 
     37        test/ 
     38        web/ 
     39          css/ 
     40          images/ 
     41          js/ 
     42 
     43По этой структуре видно, что **project** sf_sandbox содержит приложение (**application**)  
     44`frontend`. Протестируем sandbox набрав следующий URL: 
     45 
     46    http://localhost/sf_sandbox/web/index.php/ 
     47 
     48Перед вами появится страница с поздравлением.  
     49 
     50![Congratulations](/images/tutorials/mfp_1_2/first-congrats.png) 
     51 
     52Вы также можете установить symfony в любую директорию и настроить веб-сервер с  
     53помощью Virtual Host или Alias. В «The symfony book» содержится глава с подробной  
     54информацией об [установке Symfony](http://www.symfony-project.org/book/1_2/03-Running-Symfony)  
     55и [структуре директорий Symfony](http://www.symfony-project.org/book/1_2/02-Exploring-Symfony-s-Code). 
     56 
     57Инициализация модели данных 
     58------------------------- 
     59 
     60Итак в блоге можно размещать сообщения и комментировать их. Создадим файл `schema.yml`  
     61в `sf_sandbox/config/` и вставим следующий код модели данных:  
     62 
     63    [yml] 
     64    propel: 
     65      blog_post: 
     66        id:           ~ 
     67        title:        { type: varchar(255), required: true } 
     68        excerpt:      { type: longvarchar } 
     69        body:         { type: longvarchar } 
     70        created_at:   ~ 
     71      blog_comment: 
     72        id:           ~ 
     73        blog_post_id: ~ 
     74        author:       { type: varchar(255) } 
     75        email:        { type: varchar(255) } 
     76        body:         { type: longvarchar } 
     77        created_at:   ~ 
     78 
     79Этот конфигурационный файл использует синтаксис YAML. Это очень простой язык, его  
     80структура очень напоминает древовидную структуру XML. В YAML вложенность обозначается  
     81отступами. YAML легче читается и на нём проще создавать конфигурационные файлы.  
     82Единственное ограничение - для оступов используются только пробелы, табуляция запрещена.  
     83Более подробную информацию о YAML вы сможете найти в главе, посвящённой  
     84[конфигурации Symfony](http://www.symfony-project.org/book/1_2/05-Configuring-Symfony).  
     85 
     86Вышеприведённая схема описывает структуру двух таблиц, необходимых для блога.  
     87`blog_post` и `blog_comment` - имена соответствующих классов, которые будут  
     88сгенерированные. Сохраните файл, откройте командную строку, перейдите в  
     89директорию `sf_sandbox/` и наберите:  
     90 
     91    $ php symfony propel:build-model 
     92 
     93>**Примечание**: Убедитесь, что когда вы вызываете команду `symfony` в командной строке,  
     94>вы находитесь в корне вашего проекта (`sf_sandbox/`). 
     95 
     96Эта команда создаёт несколько классов в директории `sf_sandbox/lib/model/`. Эти классы  
     97являются объектно-реляционной проекцией (ORM, object-relational mapping), которая  
     98позволяет нам получать доступ к реляционным данным из объектно-ориентированного кода  
     99без написания обычных SQL запросов. Для этих целей Symfony по умолчанию использует  
     100библиотеку Propel. Эти классы являются частью **модели** нашего приложения (подробно  
     101обсуждается в [главе посвящённой модели](http://www.symfony-project.org/book/1_2/08-Inside-the-Model-Layer))).  
     102 
     103Теперь нам нужно конвертировать YAML-схему в SQL-запросы создания таблиц в базе данных.  
     104По умолчанию, the symfony sandbox сконфигурирован на работу с простейшей базой SQLite,  
     105для которой не нужно инициализации самой базы данных. Вам нужно убедиться, что расширение  
     106SQLite установлено (для этого проверьте файл `php.ini` - [rtfm](http://fr3.php.net/manual/en/ref.sqlite.php)).  
     107 
     108По умолчанию, проект `sf_sandbox` использует файл базы данных `sandbox.db`,  
     109расположенный в директории `sf_sandbox/data/`.  
     110 
     111Если же вы хотите использовать MySQL, то для этого необходимо внести изменения в конфигурацию `configure:database`:  
     112 
     113    $ php symfony configure:database mysql://root:pa$$word@localhost/symfony_project 
     114 
     115Измените аргумент DSN в соответствии с вашими настройками (имя пользователя, пароль,  
     116хост, и имя базы данных). Далее вы можете создать базу данных либо через командную строку,  
     117либо с помощью веб-интерфейса (как описано в [главе о модели](http://www.symfony-project.org/book/1_2/08-Inside-the-Model-Layer)).  
     118 
     119Затем откройте `sf_sandbox/config/databases.yml` и установите 'phptype' в 'mysql', и в поле 'database' 
     120укажите имя вашей базы данных MySQL. 
     121 
     122>**Внимание** 
     123>Если вы используете SQLite под *nix системой, то вам нужно изменить права файла sandbox.db: 
     124> 
     125>     $ chmod 777 data data/sandbox.db 
     126 
     127Теперь наберите в командной строке: 
     128 
     129    $ php symfony propel:build-sql 
     130 
     131Файл `lib.model.schema.sql` будет создан в директории `sf_sandbox/data/sql/`.  
     132SQL-запросы, находящиеся в этом файле, можно использовать для инициализации любой  
     133базы данных с такой же структурой таблиц.  
     134 
     135Для создания таблиц на основе вышеупомянутого SQL-файла наберите в командной строке: 
     136 
     137    $ php symfony propel:insert-sql 
     138 
     139>**Примечание**: Не волнуйтесь, если в выдаче этого скрипта вы увидите множество  
     140>предупреждений (warnings). Это нормально. Команда `propel:insert-sql` пытается  
     141>удалить таблицы, перед их созданием из файла `lib.model.schema.sql`, а на данный  
     142>момент в базе данных не было создано ни одной таблицы. 
     143 
     144Так как мы хотим создавать и редактировать записи и комментарии, то нам потребуется  
     145создать несколько форм на основе написанной нами схемы БД:  
     146 
     147    $ php symfony propel:build-forms 
     148 
     149Эта команда генерирует классы в директории `sf_sandbox/lib/form/`. Эти классы  
     150используются для управления объектами модели, как формами.  
     151 
     152>**Совет** 
     153>Все вышеперечисленные команды можно выполнить за один запрос, с помощью команды `propel:build-all`. 
     154 
     155Создание приложения 
     156---------------------- 
     157 
     158Основные возможности блога - это создание, получение, обновление и удаление  
     159(Create, Retrieve, Update, Delete - CRUD) сообщений и комментариев. Поскольку вы ещё  
     160новичок в symfony, вы пока не можете создавать код на symfony с нуля.  
     161Но у вас есть возможность сгенерировать код автоматически и в дальнейшем изменять  
     162его под ваши нужды. Symfony может автоматически генерировать CRUD-интерфейс на  
     163основе модели данных:  
     164 
     165    $ php symfony propel:generate-module --non-verbose-templates --with-show frontend post BlogPost 
     166    $ php symfony propel:generate-module --non-verbose-templates frontend comment BlogComment 
     167    $ php symfony cache:clear 
     168 
     169>**Совет** 
     170>В команде `propel:generate-module` мы используем параметр `--non-verbose-templates` 
     171>Если вы хотите узнать значение параметров этой команды, то вы можете вызвать справку следующим образом: 
     172> 
     173>     $ php symfony help propel:generate-module 
     174 
     175Итак, теперь у нас есть два модуля (`post` и `comment`), с помощью которых мы  
     176сможем манипулировать объектами от классов `BlogPost` и `BlogComment`. **Модуль**  
     177обычно предствляет из себя страницу или группу страниц со сходным функционалом.  
     178Новые модули располагаются в директории `sf_sandbox/apps/frontend/modules/`, и  
     179они доступны по ссылке:  
     180 
     181    http://localhost/sf_sandbox/web/frontend_dev.php/post 
     182    http://localhost/sf_sandbox/web/frontend_dev.php/comment 
     183 
     184Если вы попытаетесь создать комментарий, то вы получите ошибку, т.к. symfony ещё  
     185не знает как конвертировать объект post в строку. Откройте класс `BlogPost`  
     186(`lib/model/BlogPost.php`) и добавьте метод `__toString()`:  
     187 
     188    [php] 
     189    class BlogPost extends BaseBlogPost 
     190    { 
     191      public function __toString() 
     192      { 
     193        return $this->getTitle(); 
     194      } 
     195    } 
     196 
     197И ещё, добавим css-свойства в файл `sf_sandbox/web/css/main.css`: 
     198 
     199    body, td 
     200    { 
     201      font-family: Arial, Verdana, sans-serif; 
     202      font-size: 12px; 
     203    } 
     204 
     205    td { margin: 4px; padding: 4px; } 
     206 
     207Теперь вы можете создать несколько сообщений в блоге, чтобы он стал менее пустым.  
     208 
     209![post CRUD](/images/tutorials/mfp_1_2/first-crud.png) 
     210 
     211Мы рекомендуем более подробно узнать о [генераторах](http://www.symfony-project.org/book/1_2/14-Generators)  
     212и об [структуре проекта](http://www.symfony-project.org/book/1_2/04-The-Basics-of-Page-Creation)  
     213в symfony (проект, приложение, модуль).  
     214 
     215>**Примечание**: В ссылках приведённых выше, имя главного скрипта - называемого в symfony 
     216>*front controller* - было изменено с `index.php` на `frontend_dev.php`. 
     217>Эти два скрипта предоставляют доступ к одному и тому же приложению (`frontend`), но в различных средах. 
     218>С помощью файла `frontend_dev.php`, вы получаете доступ к среде разработчика  
     219>(**development environment**), в которой предусмотрены удобные средства отладки, 
     220>вроде debug toolbar справа сверху странички и live configuration engine.  
     221>В этом случае обработка каждой страницы занимает больше времени,  
     222>чем при использовании `index.php`, фронт-контроллера среды 
     223>**production environment**, оптимизированной по скорости. Если вы хотите переключиться  
     224>в среду production, замените `frontend_dev.php/` на `index.php/` 
     225>как показано ниже, но не забывайте очищать кэш, чтобы увидеть изменения: 
     226> 
     227>     $ php symfony cache:clear 
     228> 
     229>     http://localhost/sf_sandbox/web/index.php/ 
     230 
     231Мы рекомендуем более подробно узнать о различных средах ([environments](http://www.symfony-project.org/book/1_2/05-Configuring-Symfony#Environments)). 
     232 
     233Изменение шаблона отображения 
     234----------------- 
     235 
     236Для переключения между двумя модулями в блоге нужна глобальная навигация.  
     237 
     238Откройте глобальный шаблон `sf_sandbox/apps/frontend/templates/layout.php` и  
     239измените содержимое тега `<body>` на следующее:  
     240 
     241    [php] 
     242    <div id="container" style="width:700px;margin:0 auto;border:1px solid grey;padding:10px"> 
     243      <div id="navigation" style="display:inline;float:right"> 
     244        <ul> 
     245          <li><?php echo link_to('List of posts', 'post/index') ?></li> 
     246          <li><?php echo link_to('List of comments', 'comment/index') ?></li> 
     247        </ul> 
     248      </div> 
     249      <div id="title"> 
     250        <h1><?php echo link_to('My first symfony project', '@homepage') ?></h1> 
     251      </div> 
     252 
     253      <div id="content" style="clear:right"> 
     254        <?php echo $sf_data->getRaw('sf_content') ?> 
     255      </div> 
     256    </div> 
     257 
     258Не обращайте внимания на плохой дизайн и использование встроенных свойств CSS.  
     259Время дорого, а в нашем распоряжении всего лишь час!  
     260 
     261![post CRUD in layout](/images/tutorials/mfp_1_2/first-crud-layout.png) 
     262 
     263Пока мы занимаемся шаблоном, заодно можем и поменять тэг title нашего блога.  
     264Откройте конфигурационный файл (`sf_sandbox/apps/frontend/config/view.yml`),  
     265найдите строку, определяющую тэг `title` и замените её на что-нибудь более 
     266подходящее. Обратите внимание, что некоторые лини закомментированы, вы можете 
     267их раскомментировать, если они вам необходимы 
     268 
     269    default: 
     270      http_metas: 
     271        content-type: text/html 
     272 
     273      metas: 
     274        title:        The best blog ever 
     275        #description:  symfony project 
     276        #keywords:     symfony, project 
     277        #language:     en 
     278        robots:       index, follow 
     279 
     280Теперь нужно изменить главную страницу сайта. Сейчас для неё используется шаблон  
     281по умолчанию из модуля `default`, который находится внутри фрэймворка, а не внутри  
     282директории приложения. Чтобы заменить главную страницу необходимо создать свой  
     283собственный основной (`main`) модуль:  
     284 
     285    $ php symfony generate:module frontend main 
     286 
     287По умолчанию `index` action показывает экран поздравления с установкой.  
     288Для его удаления откройте файл `sf_sandbox/apps/frontend/modules/main/actions/actions.class.php`  
     289и удалите содержимое метода `executeIndex()`, как показано ниже:  
     290 
     291    [php] 
     292    /** 
     293     * Executes index action 
     294     * 
     295     * @param sfRequest $request A request object 
     296     */ 
     297    public function executeIndex($request) 
     298    { 
     299    } 
     300 
     301Отредактируйте файл `sf_sandbox/apps/frontend/modules/main/templates/indexSuccess.php`, 
     302чтобы отобразить привествующее сообщение:  
     303 
     304    [php] 
     305    <h1>Welcome to my new blog</h1> 
     306    <p>You are the <?php echo rand(1000,5000) ?>th visitor today.</p> 
     307 
     308Теперь мы должны сообщить symfony какое действие (action) запускать в случае запроса  
     309главной страницы сайта. Чтобы сделать это, откройте `sf_sandbox/apps/frontend/config/routing.yml`  
     310и отредактируйте правило главной страницы (`homepage`), как показано ниже:  
     311 
     312    [yml] 
     313    homepage: 
     314      url:   / 
     315      param: { module: main, action: index } 
     316 
     317Проверьте результат, снова открыв главную страницу: 
     318 
     319    http://localhost/sf_sandbox/web/frontend_dev.php/ 
     320 
     321![New home page](/images/tutorials/mfp_1_2/first-welcome.png) 
     322 
     323Перед продолжением разработки приложения убедитесь, что вы создали  
     324тестовое сообщение в блоге и тестовый комментарий на это сообщение. 
     325 
     326Более подробно о видах и шаблонах можно посмотреть в [соответствующей главе](http://www.symfony-project.org/book/1_2/07-Inside-the-View-Layer). 
     327 
     328Pass data from the action to the template 
     329----------------------------------------- 
     330 
     331That was fast, wasn't it? Now it is time to mix the `comment` module into the 
     332`post` one to get comments displayed below posts. 
     333 
     334First, you need to make the post comments available for the post display template. 
     335In symfony, this kind of logic is kept in **actions**. Edit the actions file 
     336`sf_sandbox/apps/frontend/modules/post/actions/actions.class.php` and change 
     337the `executeShow()` method by adding the four last lines: 
     338 
     339    [php] 
     340    public function executeShow($request) 
     341    { 
     342      $this->blog_post = BlogPostPeer::retrieveByPk($request->getParameter('id')); 
     343      $this->forward404Unless($this->blog_post); 
     344 
     345      $c = new Criteria(); 
     346      $c->add(BlogCommentPeer::BLOG_POST_ID, $request->getParameter('id')); 
     347      $c->addAscendingOrderByColumn(BlogCommentPeer::CREATED_AT); 
     348      $this->comments = BlogCommentPeer::doSelect($c); 
     349    } 
     350 
     351The `Criteria` and `-Peer` objects are part of Propel's object-relational mapping. 
     352Basically, these four lines will handle a SQL query to the `blog_comment` table to get 
     353the comments related to the current `blog_post`. Now, modify the post display template 
     354`sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php` by adding at the end: 
     355 
     356    [php] 
     357    // ... 
     358    <?php use_helper('Text', 'Date') ?> 
     359 
     360    <hr /> 
     361    <?php if ($comments) : ?> 
     362      <p><?php echo count($comments) ?> comments to this post.</p> 
     363      <?php foreach ($comments as $comment): ?> 
     364        <p><em>posted by <?php echo $comment->getAuthor() ?> on <?php echo format_date($comment->getCreatedAt()) ?></em></p> 
     365        <div class="comment" style="margin-bottom:10px;"> 
     366          <?php echo simple_format_text($comment->getBody()) ?> 
     367        </div> 
     368      <?php endforeach; ?> 
     369    <?php endif; ?> 
     370 
     371This page uses new PHP functions that are called 'helpers', because they do some tasks 
     372for you that would normally require more time and code. Create a new comment for your 
     373first post, then check again the first post, either by clicking on its number 
     374in the list, or by typing directly: 
     375 
     376    http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1 
     377 
     378![Comment under post](/images/tutorials/mfp_1_2/first-comments-under-post.png) 
     379 
     380This is getting good. 
     381 
     382Find more about the [naming conventions](http://www.symfony-project.org/book/1_2/07-Inside-the-View-Layer) 
     383linking an action to a template. 
     384 
     385Add a record relative to another table 
     386-------------------------------------- 
     387 
     388Currently you can't add comments to posts directly; if you edit a post, you 
     389have to go to the comments editing section, create a new one, then select the post you 
     390want to comment on using the drop-down menu. The screen looks like this: 
     391 
     392![Related comment](/images/tutorials/mfp_1_2/related-comment-1.png) 
     393 
     394It would be better if there was a link on each post editing page to go straight to 
     395the comment editing facility, so let's arrange that first. In the 
     396`sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php` template, add this line at the bottom: 
     397 
     398    [php] 
     399    <?php echo link_to('Add a comment', 'comment/edit?post_id='.$blog_post->getId()) ?> 
     400 
     401The `link_to()` helper creates a hyperlink pointing to the 
     402`edit` action of the `comment` module, so you can add a comment directly from the post 
     403details page. At the moment, however, the comments edit page still offers a form element 
     404to select which post to relate a comment to. This would be best 
     405replaced by a hidden field (containing the post primary key) if the comments edit page 
     406URL is called specifying that key. 
     407 
     408In symfony, forms are managed by classes. So, let's edit the `BlogCommentForm` class to 
     409make our changes. The file is located under the `sf_sandbox/lib/form/` directory: 
     410 
     411    [php] 
     412    class BlogCommentForm extends BaseBlogCommentForm 
     413    { 
     414      /** 
     415       * Configure method, called when the form is instantiated 
     416       */ 
     417      public function configure() 
     418      { 
     419        $this->widgetSchema['blog_post_id'] = new sfWidgetFormInputHidden(); 
     420      } 
     421    } 
     422 
     423>**Note**: For more details on the form framework, you are advised to read the 
     424[Forms Book](http://www.symfony-project.org/book/forms/1_2/en/). 
     425 
     426After you have made this change, you will now be able to add a comment directly to a 
     427post without having to explicitly specify the post to attach it to: 
     428 
     429![Related comment](/images/tutorials/mfp_1_2/related-comment-2.png) 
     430 
     431Next, after adding a comment, we want the user to come back to the post it relates to, 
     432rather that remaining on the comment editing page. To accomplish this, we need to edit 
     433`executeEdit` method. Find the following code in `sf_sandbox/apps/frontend/modules/comment/actions/actions.class.php`: 
     434 
     435    [php] 
     436    if ($request->isMethod('post')) 
     437    { 
     438      $this->form->bind($request->getParameter('blog_comment')); 
     439      if ($this->form->isValid()) 
     440      { 
     441        $blog_comment = $this->form->save(); 
     442 
     443        $this->redirect('comment/edit?id='.$blog_comment->getId()); 
     444      } 
     445    } 
     446 
     447And change the redirect line so it reads thus: 
     448 
     449    [php] 
     450    $this->redirect('post/show?id='.$blog_comment->getBlogPostId()); 
     451 
     452This will ensure that when a comment is saved, the user is returned to the post that 
     453the comment is related to. There are two things here that are worthy of note: firstly, 
     454the save is achieved simply by calling the save method on the form object (this is 
     455because the form is associated with the Propel model and therefore knows how to serialize 
     456the object back to the database). Secondly, we redirect immediately 
     457after the save, so that if the page is subsequently refreshed, the user is not asked if 
     458they wish to repeat the POST action again. 
     459 
     460Okay, so that wraps up this part of the tutorial. You wanted a blog? You have a blog. 
     461Incidentally, since we've covered symfony actions a lot here, you may wish to find out 
     462more about them from 
     463[the manual](http://www.symfony-project.org/book/1_2/06-Inside-the-Controller-Layer). 
     464 
     465Form Validation 
     466--------------- 
     467 
     468Visitors can enter comments, but what if they submit the form without any data 
     469in it, or data that is obviously wrong? You would end up with a database containing 
     470invalid rows. To avoid that, we need to set up some validation rules to specify what 
     471data is allowed. 
     472 
     473When symfony has created the form classes for us, if has generated the form elements 
     474to render on the screen, but it has also added some default validation rules by introspecting 
     475the schema. As the `title` is required in the `blog_post` table, you won't be able to 
     476submit a form without a title. You also won't be able to submit a post with a title 
     477longer than 255 character. 
     478 
     479Let's override some of these rules now in the `BlogCommentForm` class. 
     480So, open the file, and add in the following PHP code at the end of the configure() method: 
     481 
     482    [php] 
     483    $this->validatorSchema['email'] = new sfValidatorEmail( 
     484      array('required' => false), 
     485      array('invalid' => 'The email address is not valid')); 
     486 
     487By redefining the validator for the `email` column, we have overridden the default behavior. 
     488 
     489Once this new rule is in place, try saving a comment with a bad email 
     490address - you now have a robust form! You will notice a number of 
     491things: first of all, where the form contains data, that data will automatically be 
     492preserved during the form submission. This saves the user having to type it back in 
     493(and normally is something in that the programmer has to arrange manually). Also, errors 
     494(in this case) are placed next to the fields that failed their associated validation 
     495tests. 
     496 
     497Now would be a good time to explain a little about how the form save process works. It 
     498uses the following action code, which you edited earlier in 
     499`/sf_sandbox/apps/frontend/modules/comment/actions/actions.class.php`: 
     500 
     501    [php] 
     502    $this->form = new BlogCommentForm(BlogCommentPeer::retrieveByPk($request->getParameter('id'))); 
     503 
     504    if ($request->isMethod('post')) 
     505    { 
     506      $this->form->bind($request->getParameter('blog_comment')); 
     507      if ($this->form->isValid()) 
     508      { 
     509        $blog_comment = $this->form->save(); 
     510 
     511        $this->redirect('post/show?id='.$blog_comment->getBlogPostId()); 
     512      } 
     513    } 
     514 
     515After the form object is instantiated, the following happens: 
     516 
     517 * The code checks that the HTTP method is a POST 
     518 * The parameter array `blog_comment` is retrieved. The `getParameter()` method detects 
     519that this name is an array of values in the form, not a single value, and returns them 
     520as an associative array (e.g. form element `blog_comment[author]` is returned in an 
     521array having a key of `author`) 
     522 * This associative array is then fed into the form using a process called **binding**, 
     523in which the values are used to fill form elements in the form object. After this, the 
     524values are determined to have either passed or failed the validation checks 
     525 * Only if the form is valid does the save go ahead, after which the page redirects 
     526immediately to the show action. 
     527 
     528![Form validation](/images/tutorials/mfp_1_2/first-form-validation.png) 
     529 
     530Find more about 
     531[form validation](http://www.symfony-project.org/book/forms/1_2/en/02-Form-Validation). 
     532 
     533Change the URL format 
     534--------------------- 
     535 
     536Did you notice the way the URLs are rendered? You can make them more user and 
     537search engine-friendly. Let's use the post title as a URL for posts. 
     538 
     539The problem is that post titles can contain special characters like spaces. 
     540If you just escape them, the URL will contain some ugly `%20` strings, 
     541so the model needs to be extended with a new method in the `BlogPost` object, 
     542to get a clean, stripped title. To do that, edit the file `BlogPost.php` located 
     543in the `sf_sandbox/lib/model/` directory, and add the following method: 
     544 
     545    [php] 
     546    public function getStrippedTitle() 
     547    { 
     548      $result = strtolower($this->getTitle()); 
     549 
     550      // strip all non word chars 
     551      $result = preg_replace('/\W/', ' ', $result); 
     552 
     553      // replace all white space sections with a dash 
     554      $result = preg_replace('/\ +/', '-', $result); 
     555 
     556      // trim dashes 
     557      $result = preg_replace('/\-$/', '', $result); 
     558      $result = preg_replace('/^\-/', '', $result); 
     559 
     560      return $result; 
     561    } 
     562 
     563Now you can create a `permalink` action for the `post` module. Add the following 
     564method to the `sf_sandbox/apps/frontend/modules/post/actions/actions.class.php`: 
     565 
     566    [php] 
     567    public function executePermalink($request) 
     568    { 
     569      $posts = BlogPostPeer::doSelect(new Criteria()); 
     570      $title = $request->getParameter('title'); 
     571      foreach ($posts as $post) 
     572      { 
     573        if ($post->getStrippedTitle() == $title) 
     574        { 
     575          $request->setParameter('id', $post->getId()); 
     576 
     577          return $this->forward('post', 'show'); 
     578        } 
     579      } 
     580 
     581      $this->forward404(); 
     582    } 
     583 
     584The post list can call this `permalink` action instead of the `show` one for 
     585each post. In `sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php`, delete the `id` table 
     586header and cell, and change the `Title` cell from this: 
     587 
     588    [php] 
     589    <td><?php echo $blog_post->getTitle() ?></td> 
     590 
     591to this, which uses a named rule we will create in a second: 
     592 
     593    [php] 
     594    <td><?php echo link_to($blog_post->getTitle(), '@post?title='.$blog_post->getStrippedTitle()) ?></td> 
     595 
     596Just one more step: edit the `routing.yml` located in the 
     597`sf_sandbox/apps/frontend/config/` directory and add these rules at the top: 
     598 
     599    list_of_posts: 
     600      url:   /latest_posts 
     601      param: { module: post, action: index } 
     602 
     603    post: 
     604      url:   /blog/:title 
     605      param: { module: post, action: permalink } 
     606 
     607Now navigate again in your application to see your new URLs in action. If you get an 
     608error, it may be because the routing cache needs to be cleared. To do that, type the 
     609following at the command line while in your `sf_sandbox` folder: 
     610 
     611    $ php symfony cc 
     612 
     613![Routed URLs](/images/tutorials/mfp_1_2/first-routing.png) 
     614 
     615Find more about [smart URLs](http://www.symfony-project.org/book/1_2/09-Links-and-the-Routing-System). 
     616 
     617Cleaning up the frontend 
     618------------------------ 
     619 
     620Well, if this is meant to be a blog, then it is perhaps a little strange that everybody 
     621is allowed to post! This isn't generally how blogs are meant to work, so let's clean up our 
     622templates a bit. 
     623 
     624In the template `sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php`, get rid of the 
     625'edit' link by removing the line: 
     626 
     627    <a href="<?php echo url_for('post/edit?id='.$blog_post->getId()) ?>">Edit</a> 
     628    &nbsp; 
     629 
     630Do the same for the `sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php` template and remove: 
     631 
     632    <a href="<?php echo url_for('post/edit') ?>">Create</a> 
     633 
     634You should also remove the following methods from `sf_sandbox/apps/frontend/modules/post/actions/actions.class.php`: 
     635 
     636 * `executeEdit` 
     637 * `executeDelete` 
     638 
     639This means that readers cannot post anymore, which is what is required. 
     640 
     641Generation of the backend 
     642------------------------- 
     643 
     644To allow you to write posts, let's create a backend application by typing in the 
     645command line (still from the `sf_sandbox` project directory): 
     646 
     647    $ php symfony generate:app backend 
     648    $ php symfony propel:init-admin backend post BlogPost 
     649    $ php symfony propel:init-admin backend comment BlogComment 
     650 
     651This time, we use the [admin generator](http://www.symfony-project.org/book/1_2/14-Generators). 
     652It offers much more features and customization than the basic CRUD generator. 
     653 
     654Just like you did for the `frontend` application, edit the layout (`apps/backend/templates/layout.php`) 
     655to add global navigation: 
     656 
     657    <div id="navigation"> 
     658      <ul style="list-style:none;"> 
     659        <li><?php echo link_to('Manage posts', 'post/index') ?></li> 
     660        <li><?php echo link_to('Manage comments', 'comment/index') ?></li> 
     661      </ul> 
     662    </div> 
     663    <div id="content"> 
     664      <?php echo $sf_data->getRaw('sf_content') ?> 
     665    </div> 
     666 
     667You can access your new back-office application in the development environment by calling: 
     668 
     669    http://localhost/sf_sandbox/web/backend_dev.php/post 
     670 
     671![basic generated admin](/images/tutorials/mfp_1_2/first-basic-admin.png) 
     672 
     673The great advantage of the generated admin is that you can easily customize it by 
     674editing a configuration file. 
     675 
     676Change the `sf_sandbox/apps/backend/modules/post/config/generator.yml` to: 
     677 
     678    generator: 
     679      class:              sfPropelAdminGenerator 
     680      param: 
     681        model_class:      BlogPost 
     682        theme:            default 
     683        fields: 
     684          title:          { name: Title } 
     685          excerpt:        { name: Excerpt } 
     686          body:           { name: Body } 
     687          nb_comments:    { name: Comments } 
     688          created_at:     { name: Creation date } 
     689        list: 
     690          title:          Post list 
     691          layout:         tabular 
     692          display:        [=title, excerpt, nb_comments, created_at] 
     693          object_actions: 
     694            _edit:        ~ 
     695            _delete:      ~ 
     696          max_per_page:   5 
     697          filters:        [title, created_at] 
     698        edit: 
     699          title:          Post detail 
     700          fields: 
     701            title:        { type: input_tag, params: size=53 } 
     702            excerpt:      { type: textarea_tag, params: size=50x2 } 
     703            body:         { type: textarea_tag, params: size=50x10 } 
     704            created_at:   { type: input_date_tag, params: rich=on } 
     705 
     706Note that among the existing columns of the `blog_post` table, the admin will look 
     707for a column (or getter method) for `nb_comments`. Since this is a generated value and 
     708not a column, we can add a getter to our model (`sf_sandbox/lib/model/BlogPost.php`): 
     709 
     710    [php] 
     711    public function getNbComments() 
     712    { 
     713      return count($this->getBlogComments()); 
     714    } 
     715 
     716Now refresh the Post administration screen to see the changes: 
     717 
     718![customized generated admin](/images/tutorials/mfp_1_2/first-custom-admin.png) 
     719 
     720Restrict access to the backend 
     721------------------------------ 
     722 
     723Currently the backend application can be accessed by everybody. We therefore need to 
     724add some access restrictions. In `apps/backend/config/`, edit the file called 
     725`security.yml` and reset the content to: 
     726 
     727    all: 
     728      is_secure: on 
     729 
     730Now you can no longer access these modules, unless you are logged in. However, 
     731currently the login action doesn't exist! Of course, we can easily add it. 
     732To do so, let's install a suitable plugin from the symfony website - sfGuardPlugin. 
     733Type the following at the command line: 
     734 
     735    $ php symfony plugin:install sfGuardPlugin 
     736 
     737This will download the plugin from the symfony plugin repository. At this point the 
     738command line should give you an indication that the installation was successful: 
     739 
     740    $ php symfony plugin:install sfGuardPlugin 
     741    >> plugin    installing plugin "sfGuardPlugin" 
     742    >> sfPearFrontendPlugin Attempting to discover channel "pear.symfony-project.com"... 
     743    >> sfPearFrontendPlugin downloading channel.xml ... 
     744    >> sfPearFrontendPlugin Starting to download channel.xml (663 bytes) 
     745    >> sfPearFrontendPlugin . 
     746    >> sfPearFrontendPlugin ...done: 663 bytes 
     747    >> sfPearFrontendPlugin Auto-discovered channel "pear.symfony-project.com", alias 
     748    >> sfPearFrontendPlugin "symfony", adding to registry 
     749    >> sfPearFrontendPlugin Attempting to discover channel 
     750    >> sfPearFrontendPlugin "plugins.symfony-project.org"... 
     751    >> sfPearFrontendPlugin downloading channel.xml ... 
     752    >> sfPearFrontendPlugin Starting to download channel.xml (639 bytes) 
     753    >> sfPearFrontendPlugin ...done: 639 bytes 
     754    >> sfPearFrontendPlugin Auto-discovered channel "plugins.symfony-project.org", alias 
     755    >> sfPearFrontendPlugin "symfony-plugins", adding to registry 
     756    >> sfPearFrontendPlugin downloading sfGuardPlugin-2.2.0.tgz ... 
     757    >> sfPearFrontendPlugin Starting to download sfGuardPlugin-2.2.0.tgz (18,589 bytes) 
     758    >> sfPearFrontendPlugin ...done: 18,589 bytes 
     759    >> sfSymfonyPluginManager Installation successful for plugin "sfGuardPlugin" 
     760 
     761Next, the plugin needs to be enabled. Edit `sf_sandbox/apps/backend/config/settings.yml` 
     762to enable the login system, as follows. Uncomment the `all:` key and add the following: 
     763 
     764    # (Some stuff here) 
     765    all: 
     766      .actions: 
     767        login_module:    sfGuardAuth   # To be called when a non-authenticated user 
     768        login_action:    signin     # Tries to access a secure page 
     769 
     770        secure_module:   sfGuardAuth   # To be called when a user doesn't have 
     771        secure_action:   secure    # The credentials required for an action 
     772 
     773      .settings 
     774        enabled_modules: [default, sfGuardAuth, sfGuardGroup, sfGuardPermission, sfGuardUser] 
     775 
     776Now, enable the new user system by editing `sf_sandbox/apps/backend/lib/myUser.class.php`, 
     777so that it reads: 
     778 
     779    [php] 
     780    class myUser extends sfGuardSecurityUser 
     781    { 
     782    } 
     783 
     784Now, the model, forms and filters need to be built, the new SQL needs to be generated, and the database tables 
     785need to be updated: 
     786 
     787    $ php symfony propel:build-model 
     788    $ php symfony propel:build-forms 
     789    $ php symfony propel:build-filters 
     790    $ php symfony propel:build-sql 
     791    $ php symfony propel:insert-sql 
     792 
     793>**Note** 
     794>When launching the `propel:insert-sql` task, symfony will drop all tables to re-create them. 
     795>As during the development this happens a lot, symfony can store initial data in fixtures 
     796>(see [populating a database](http://www.symfony-project.org/book/1_2/16-Application-Management-Tools#Populating%20a%20Database) for more information). 
     797 
     798Now, clear your cache again. Finally, create a user by running: 
     799 
     800    $ symfony guard:create-user frontend jon SoMePasSwOrD 
     801 
     802Let's now make the post administration module the default one in our backend system. To 
     803do this, open up the `apps/backend/config/routing.yml` file and locate the 
     804`homepage` key. Change the module from `default` to `post`. 
     805 
     806At that point, if you try to access the posts management 
     807(http://localhost/sf_sandbox/web/backend_dev.php/post), you will have to enter a valid username 
     808and password: 
     809 
     810![login form](/images/tutorials/mfp_1_2/logon.png) 
     811 
     812Find more about [security](http://www.symfony-project.org/book/1_2/06-Inside-the-Controller-Layer#Action%20Security). 
     813 
     814Conclusion 
     815---------- 
     816 
     817Ok, the hour is out. You made it. Now you can use both applications in the production 
     818environment and play with them: 
     819 
     820    frontend:   http://localhost/sf_sandbox/web/index.php/ 
     821    backend:    http://localhost/sf_sandbox/web/backend.php/ 
     822 
     823At this point, if you meet an error, it might be because you changed the model after 
     824some actions were put in cache (cache isn't activated in the development environment). 
     825To clear the cache, simply type: 
     826 
     827    $ php symfony cc 
     828 
     829See, the application is fast and runs smoothly. Pretty cool, isn't it? 
     830Feel free to explore the code, add new modules, and change the design of pages. 
     831 
     832And don't forget to mention your working symfony applications in the 
     833[symfony Wiki](http://trac.symfony-project.org/wiki/ApplicationsDevelopedWithSymfony)!