Development

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

You must first sign up to be able to contribute.

Changes between Version 1 and Version 2 of Documentation/ru_RU/my-first-project

Show
Ignore:
Author:
Nikitin (IP: 91.76.47.103)
Timestamp:
12/07/08 09:45:29 (8 years ago)
Comment:

--

Legend:

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

    v1 v2  
    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 для которой не нужно инициализации самой базы данных. Вам нужно убедиться, что расширение  
    106 SQLite установлено (для этого проверьте файл `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/`.  
    132 SQL-запросы, находящиеся в этом файле, можно использовать для инициализации любой  
    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  
    328 Pass data from the action to the template 
    329 ----------------------------------------- 
    330  
    331 That 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  
    334 First, you need to make the post comments available for the post display template. 
    335 In 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 
    337 the `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  
    351 The `Criteria` and `-Peer` objects are part of Propel's object-relational mapping. 
    352 Basically, these four lines will handle a SQL query to the `blog_comment` table to get 
    353 the 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  
    371 This page uses new PHP functions that are called 'helpers', because they do some tasks 
    372 for you that would normally require more time and code. Create a new comment for your 
    373 first post, then check again the first post, either by clicking on its number 
    374 in 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  
    380 This is getting good. 
    381  
    382 Find more about the [naming conventions](http://www.symfony-project.org/book/1_2/07-Inside-the-View-Layer) 
    383 linking an action to a template. 
    384  
    385 Add a record relative to another table 
    386 -------------------------------------- 
    387  
    388 Currently you can't add comments to posts directly; if you edit a post, you 
    389 have to go to the comments editing section, create a new one, then select the post you 
    390 want 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  
    394 It would be better if there was a link on each post editing page to go straight to 
    395 the 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  
    401 The `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 
    403 details page. At the moment, however, the comments edit page still offers a form element 
    404 to select which post to relate a comment to. This would be best 
    405 replaced by a hidden field (containing the post primary key) if the comments edit page 
    406 URL is called specifying that key. 
    407  
    408 In symfony, forms are managed by classes. So, let's edit the `BlogCommentForm` class to 
    409 make 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  
    426 After you have made this change, you will now be able to add a comment directly to a 
    427 post 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  
    431 Next, after adding a comment, we want the user to come back to the post it relates to, 
    432 rather 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  
    447 And change the redirect line so it reads thus: 
    448  
    449     [php] 
    450     $this->redirect('post/show?id='.$blog_comment->getBlogPostId()); 
    451  
    452 This will ensure that when a comment is saved, the user is returned to the post that 
    453 the comment is related to. There are two things here that are worthy of note: firstly, 
    454 the save is achieved simply by calling the save method on the form object (this is 
    455 because the form is associated with the Propel model and therefore knows how to serialize 
    456 the object back to the database). Secondly, we redirect immediately 
    457 after the save, so that if the page is subsequently refreshed, the user is not asked if 
    458 they wish to repeat the POST action again. 
    459  
    460 Okay, so that wraps up this part of the tutorial. You wanted a blog? You have a blog. 
    461 Incidentally, since we've covered symfony actions a lot here, you may wish to find out 
    462 more about them from 
    463 [the manual](http://www.symfony-project.org/book/1_2/06-Inside-the-Controller-Layer). 
    464  
    465 Form Validation 
    466 --------------- 
    467  
    468 Visitors can enter comments, but what if they submit the form without any data 
    469 in it, or data that is obviously wrong? You would end up with a database containing 
    470 invalid rows. To avoid that, we need to set up some validation rules to specify what 
    471 data is allowed. 
    472  
    473 When symfony has created the form classes for us, if has generated the form elements 
    474 to render on the screen, but it has also added some default validation rules by introspecting 
    475 the schema. As the `title` is required in the `blog_post` table, you won't be able to 
    476 submit a form without a title. You also won't be able to submit a post with a title 
    477 longer than 255 character. 
    478  
    479 Let's override some of these rules now in the `BlogCommentForm` class. 
    480 So, 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  
    487 By redefining the validator for the `email` column, we have overridden the default behavior. 
    488  
    489 Once this new rule is in place, try saving a comment with a bad email 
    490 address - you now have a robust form! You will notice a number of 
    491 things: first of all, where the form contains data, that data will automatically be 
    492 preserved 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 
    495 tests. 
    496  
    497 Now would be a good time to explain a little about how the form save process works. It 
    498 uses 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  
    515 After 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 
    519 that this name is an array of values in the form, not a single value, and returns them 
    520 as an associative array (e.g. form element `blog_comment[author]` is returned in an 
    521 array having a key of `author`) 
    522  * This associative array is then fed into the form using a process called **binding**, 
    523 in which the values are used to fill form elements in the form object. After this, the 
    524 values 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 
    526 immediately to the show action. 
    527  
    528 ![Form validation](/images/tutorials/mfp_1_2/first-form-validation.png) 
    529  
    530 Find more about 
    531 [form validation](http://www.symfony-project.org/book/forms/1_2/en/02-Form-Validation). 
    532  
    533 Change the URL format 
    534 --------------------- 
    535  
    536 Did you notice the way the URLs are rendered? You can make them more user and 
    537 search engine-friendly. Let's use the post title as a URL for posts. 
    538  
    539 The problem is that post titles can contain special characters like spaces. 
    540 If you just escape them, the URL will contain some ugly `%20` strings, 
    541 so the model needs to be extended with a new method in the `BlogPost` object, 
    542 to get a clean, stripped title. To do that, edit the file `BlogPost.php` located 
    543 in 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  
    563 Now you can create a `permalink` action for the `post` module. Add the following 
    564 method 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  
    584 The post list can call this `permalink` action instead of the `show` one for 
    585 each post. In `sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php`, delete the `id` table 
    586 header and cell, and change the `Title` cell from this: 
    587  
    588     [php] 
    589     <td><?php echo $blog_post->getTitle() ?></td> 
    590  
    591 to 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  
    596 Just 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  
    607 Now navigate again in your application to see your new URLs in action. If you get an 
    608 error, it may be because the routing cache needs to be cleared. To do that, type the 
    609 following 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  
    615 Find more about [smart URLs](http://www.symfony-project.org/book/1_2/09-Links-and-the-Routing-System). 
    616  
    617 Cleaning up the frontend 
    618 ------------------------ 
    619  
    620 Well, if this is meant to be a blog, then it is perhaps a little strange that everybody 
    621 is allowed to post! This isn't generally how blogs are meant to work, so let's clean up our 
    622 templates a bit. 
    623  
    624 In 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  
    630 Do 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  
    634 You should also remove the following methods from `sf_sandbox/apps/frontend/modules/post/actions/actions.class.php`: 
    635  
    636  * `executeEdit` 
    637  * `executeDelete` 
    638  
    639 This means that readers cannot post anymore, which is what is required. 
    640  
    641 Generation of the backend 
    642 ------------------------- 
    643  
    644 To allow you to write posts, let's create a backend application by typing in the 
    645 command 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  
    651 This time, we use the [admin generator](http://www.symfony-project.org/book/1_2/14-Generators). 
    652 It offers much more features and customization than the basic CRUD generator. 
    653  
    654 Just like you did for the `frontend` application, edit the layout (`apps/backend/templates/layout.php`) 
    655 to 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  
    667 You 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  
    673 The great advantage of the generated admin is that you can easily customize it by 
    674 editing a configuration file. 
    675  
    676 Change 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  
    706 Note that among the existing columns of the `blog_post` table, the admin will look 
    707 for a column (or getter method) for `nb_comments`. Since this is a generated value and 
    708 not 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  
    716 Now refresh the Post administration screen to see the changes: 
    717  
    718 ![customized generated admin](/images/tutorials/mfp_1_2/first-custom-admin.png) 
    719  
    720 Restrict access to the backend 
    721 ------------------------------ 
    722  
    723 Currently the backend application can be accessed by everybody. We therefore need to 
    724 add 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  
    730 Now you can no longer access these modules, unless you are logged in. However, 
    731 currently the login action doesn't exist! Of course, we can easily add it. 
    732 To do so, let's install a suitable plugin from the symfony website - sfGuardPlugin. 
    733 Type the following at the command line: 
    734  
    735     $ php symfony plugin:install sfGuardPlugin 
    736  
    737 This will download the plugin from the symfony plugin repository. At this point the 
    738 command 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  
    761 Next, the plugin needs to be enabled. Edit `sf_sandbox/apps/backend/config/settings.yml` 
    762 to 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  
    776 Now, enable the new user system by editing `sf_sandbox/apps/backend/lib/myUser.class.php`, 
    777 so that it reads: 
    778  
    779     [php] 
    780     class myUser extends sfGuardSecurityUser 
    781     { 
    782     } 
    783  
    784 Now, the model, forms and filters need to be built, the new SQL needs to be generated, and the database tables 
    785 need 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  
    798 Now, clear your cache again. Finally, create a user by running: 
    799  
    800     $ symfony guard:create-user frontend jon SoMePasSwOrD 
    801  
    802 Let's now make the post administration module the default one in our backend system. To 
    803 do this, open up the `apps/backend/config/routing.yml` file and locate the 
    804 `homepage` key. Change the module from `default` to `post`. 
    805  
    806 At 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 
    808 and password: 
    809  
    810 ![login form](/images/tutorials/mfp_1_2/logon.png) 
    811  
    812 Find more about [security](http://www.symfony-project.org/book/1_2/06-Inside-the-Controller-Layer#Action%20Security). 
    813  
    814 Conclusion 
    815 ---------- 
    816  
    817 Ok, the hour is out. You made it. Now you can use both applications in the production 
    818 environment and play with them: 
    819  
    820     frontend:   http://localhost/sf_sandbox/web/index.php/ 
    821     backend:    http://localhost/sf_sandbox/web/backend.php/ 
    822  
    823 At this point, if you meet an error, it might be because you changed the model after 
    824 some actions were put in cache (cache isn't activated in the development environment). 
    825 To clear the cache, simply type: 
    826  
    827     $ php symfony cc 
    828  
    829 See, the application is fast and runs smoothly. Pretty cool, isn't it? 
    830 Feel free to explore the code, add new modules, and change the design of pages. 
    831  
    832 And don't forget to mention your working symfony applications in the 
    833 [symfony Wiki](http://trac.symfony-project.org/wiki/ApplicationsDevelopedWithSymfony)!