Development

/doc/branches/1.1/tutorial/my-first-project.txt

You must first sign up to be able to contribute.

root/doc/branches/1.1/tutorial/my-first-project.txt

Revision 10189, 27.9 kB (checked in by fabien, 2 weeks ago)

doc: changed trac links from trac.symfony-project.com to trac.symfony-project.org

Line 
1 My first symfony project
2 ========================
3
4 So, you want to try it on? Let's build together a fully-functional web app in
5 one hour. You name it. A bookseller application? Ok, another idea. A blog!
6 That's a good one. Let's go.
7
8 We'll assume that you are working with Apache installed and launched on your
9 localhost. You will also need PHP 5.1.3 or newer.
10
11 Install symfony and initialize the project
12 ------------------------------------------
13
14 To go fast, we will use the symfony sandbox.
15
16 It is an empty symfony project where all the required libraries are already included,
17 and where the basic configuration is already done. The great advantage of the sandbox
18 over other types of installation is that you can start experimenting with symfony
19 immediately.
20
21 Get it here: [sf_sandbox_1_1.tgz](http://www.symfony-project.org/get/sf_sandbox_1_1.tgz),
22 and unpack it in your root web directory. It is recommended to keep the permissions as
23 they are in the tar file (for example by using -p with tar command). Refer to the
24 included `README` file for more information. The resulting file structure should look like:
25
26     www/
27       sf_sandbox/
28         apps/
29           frontend/
30         batch/
31         cache/
32         config/
33         data/
34           sql/
35         doc/
36         lib/
37           model/
38         log/
39         plugins/
40         test/
41         web/
42           css/
43           images/
44           js/
45
46 This shows a `sf_sandbox` **project** containing a `frontend` **application**.
47 Test the sandbox by requesting the following URL:
48
49     http://localhost/sf_sandbox/web/index.php/
50
51 You should see a congratulations page.
52
53 ![Congratulations](/images/tutorials/first_congrats.gif)
54
55 You can also install symfony in a custom folder and setup your web server with
56 a Virtual Host or an Alias. The symfony book contains detailed chapters about
57 [symfony installation](http://www.symfony-project.org/book/1_1/03-Running-Symfony)
58 and the [symfony directory structure](http://www.symfony-project.org/book/1_1/02-Exploring-Symfony-s-Code).
59
60 Initialize the data model
61 -------------------------
62
63 So, the blog will handle posts, and you will enable comments on them. Create a
64 `schema.yml` file in `sf_sandbox/config/` and paste the following data model:
65
66     propel:
67       post:
68         id:          ~
69         title:       varchar(255)
70         excerpt:     longvarchar
71         body:        longvarchar
72         created_at:  ~
73       comment:
74         id:          ~
75         post_id:     ~
76         author:      varchar(255)
77         email:       varchar(255)
78         body:        longvarchar
79         created_at:  ~
80
81 This configuration file uses the YAML syntax. It's a very simple language that
82 allows XML-like tree structures described by indentation. Furthermore, it is
83 faster to read and write than XML. The only thing is, the indentation has a
84 meaning and tabulations are forbidden, so remember to use spaces for indentation.
85 You will find more about YAML and the symfony configuration in the
86 [configuration chapter](http://www.symfony-project.org/book/1_1/05-Configuring-Symfony).
87
88 This schema describes the structure of two of the tables needed for the blog.
89 `Post` and `Comment` are the names of the related classes to be generated.
90 Save the file, open a command line, browse to the `sf_sandbox/` directory and type:
91
92     $ php symfony propel:build-model
93
94 >**Note**: Make sure to be at the root of your project (`sf_sandbox/`) when you
95 >call the `symfony` command.
96
97 A few classes are created in the `sf_sandbox/lib/model/` directory. These are the
98 classes of the object-relational mapping, who allow us to have access to a relational
99 database from within an object-oriented code without writing a single SQL query.
100 By default, symfony uses the Propel library for this purpose. Theses classes are
101 part of the **model** of our application
102 (find more in the [model chapter](http://www.symfony-project.org/book/1_1/08-Inside-the-Model-Layer)).
103
104 Now, we need to convert the schema to SQL statements to initialize the database tables.
105 By default, the symfony sandbox is configured to work out of the box with a simple
106 SQLite file, so no database initialization is required.
107 You still need to check that the SQLite extension is installed and enabled correctly
108 (you can check this in `php.ini` - see how
109 [in the PHP documentation](http://fr3.php.net/manual/en/ref.sqlite.php)).
110
111 By default, the `sf_sandbox` project will use a database file called `sandbox.db`
112 located in `sf_sandbox/data/`.
113
114 If you want to switch to MySQL for this project, just type the following command line:
115
116     $ php symfony configure:database mysql://root:pa$$word@localhost/symfony_project
117
118 Change the DSN argument to match your settings (username, password, host, and database name)
119 and then create the database with the command line or a web interface (as described in
120 the [model chapter](http://www.symfony-project.org/book/1_1/08-Inside-the-Model-Layer)).
121
122 Now type in the command line:
123
124     $ php symfony propel:build-sql
125
126 A `lib.model.schema.sql` file is created in `sf_sandbox/data/sql/`.
127 The SQL statements found is this file can be used to initialize a database with
128 the same table structure.
129
130 To build the table structure based on the the SQL file, type:
131
132     $ php symfony propel:insert-sql
133
134 >**Note**: Don't worry if there is a warning at that point, it is normal.
135 >The `propel:insert-sql` command removes existing tables before adding the ones
136 >of your `lib.model.schema.sql`, and there is no table to remove at that time.
137
138 As we want to be able to create and edit the blog posts and comments, we also need to
139 generate some forms based on our model schema:
140
141     $ php symfony propel:build-form
142
143 This task generates classes in the `sf_sandbox/lib/form/` directory.
144 These classes are used to manage our model objects as forms.
145
146 Create the application
147 ----------------------
148
149 The basic features of a blog are to be able to Create, Retrieve, Update and
150 Delete (CRUD) posts and comments. As you are new to symfony, you will not create
151 symfony code from scratch, but rather let it generate the code that you may use
152 and modify as needed. Symfony can interpret the data model to generate the CRUD
153 interface automatically:
154
155     $ php symfony propel:generate-crud frontend post Post
156     $ php symfony propel:generate-crud frontend comment Comment
157     $ php symfony cache:clear
158
159     On *nix systems, you will have to change some rights:
160     $ chmod 777 data
161     $ chmod 777 data/sandbox.db
162
163 You now have two modules (`post` and `comment`) that will let you manipulate
164 objects of the `Post` and `Comment` classes. A **module** usually represents a
165 page or a group of pages with a similar purpose. Your new modules are located
166 in the `sf_sandbox/apps/frontend/modules/` directory, and they are accessible
167 by the URLs:
168
169     http://localhost/sf_sandbox/web/frontend_dev.php/post
170     http://localhost/sf_sandbox/web/frontend_dev.php/comment
171
172 Feel free to create a new post to make the blog look less empty.
173
174 ![post CRUD](/images/tutorials/first_crud.gif)
175
176 Find more about [generators](http://www.symfony-project.org/book/1_1/14-Generators)
177 and the explanation of symfony projects
178 [structure](http://www.symfony-project.org/book/1_1/04-The-Basics-of-Page-Creation)
179 (project, application, module).
180
181 >**Note**: In the URLs above, the name of the main script - called
182 >*front controller* in symfony - was changed from `index.php` to `frontend_dev.php`.
183 >The two scripts access the same application (`frontend`), but in different environments.
184 >With `frontend_dev.php`, you access the application in the **development environment**,
185 >which provides handy development tools like the debug toolbar on the top right
186 >of the screen and the live configuration engine. That's why the processing of
187 >each page is slower than when using `index.php`, which is the front controller
188 >of the **production environment**, optimized for speed. If you want to keep on
189 >using the production environment, replace `frontend_dev.php/` by `index.php/`
190 >in the following URLs, but don't forget to clear the cache before watching the
191 >changes:
192 >
193 >     $ php symfony cache:clear
194 >
195 >     http://localhost/sf_sandbox/web/index.php/
196
197 Find more about [environments](http://www.symfony-project.org/book/1_1/05-Configuring-Symfony#Environments).
198
199 Modify the layout
200 -----------------
201
202 In order to navigate between the two new modules, the blog needs some global navigation.
203
204 Edit the global template `sf_sandbox/apps/frontend/templates/layout.php` and
205 change the content of the `<body>` tag to:
206
207     [php]
208     <div id="container" style="width:600px;margin:0 auto;border:1px solid grey;padding:10px">
209       <div id="navigation" style="display:inline;float:right">
210         <ul>
211           <li><?php echo link_to('List of posts', 'post/index') ?></li>
212           <li><?php echo link_to('List of comments', 'comment/index') ?></li>
213         </ul>
214       </div>
215       <div id="title">
216         <h1><?php echo link_to('My first symfony project', '@homepage') ?></h1>
217       </div>
218
219       <div id="content" style="clear:right">
220         <?php echo $sf_data->getRaw('sf_content') ?>
221       </div>
222     </div>
223
224 Please be forgiving for the poor design and the use of inner-tag css, but
225 one hour is a short time.
226
227 ![post CRUD in layout](/images/tutorials/first_crud_layout.gif)
228
229 While you are at it, you can change the title of your pages.
230 Edit the view configuration file of the application (`sf_sandbox/apps/frontend/config/view.yml`),
231 locate the line showing the `title` key and change it to:
232
233     default:
234       http_metas:
235         content-type: text/html
236
237     metas:
238       title:        The best blog ever
239       robots:       index, follow
240       description:  symfony project
241       keywords:     symfony, project
242       language:     en
243
244 The home page itself needs to be changed. It uses the default template of the
245 `default` module, which is kept in the framework but not in your application
246 directory. To override it, you have to create a custom `main` module:
247
248     $ php symfony generate:module frontend main
249
250 By default, the `index` action shows a default congratulations screen.
251 To remove it, edit the `sf_sandbox/apps/frontend/modules/main/actions/actions.class.php`
252 and remove the content of the `executeIndex()` method as follows:
253
254     [php]
255     public function executeIndex()
256     {
257     }
258
259 Edit the `sf_sandbox/apps/frontend/modules/main/templates/indexSuccess.php` file
260 to show a nice welcome message:
261
262     [php]
263     <h1>Welcome to my swell blog</h1>
264     <p>You are the <?php echo rand(1000,5000) ?>th visitor today.</p>
265
266 Now, you must tell symfony which action to execute when the homepage is requested.
267 To that extend, edit the `sf_sandbox/apps/frontend/config/routing.yml` and change
268 the `homepage` rule as follows:
269
270     [yml]
271     homepage:
272       url:   /
273       param: { module: main, action: index }
274
275 Check the result by requesting the home page again:
276
277     http://localhost/sf_sandbox/web/frontend_dev.php/
278
279 ![New home page](/images/tutorials/first_welcome.gif)
280
281 Go ahead, start using your new web app: Create a new test post, and a test
282 comment for your this post.
283
284 Find more about [views and templates](http://www.symfony-project.org/book/1_1/07-Inside-the-View-Layer).
285
286 Pass data from the action to the template
287 -----------------------------------------
288
289 That was fast, wasn't it? Now it is time to mix the `comment` module into the
290 `post` one to get comments displayed below posts.
291
292 First, you need to make the post comments available for the post display template.
293 In symfony, this kind of logic is kept in **actions**. Edit the actions file
294 `sf_sandbox/apps/frontend/modules/post/actions/actions.class.php` and change
295 the `executeShow()` method by adding the 4 last lines:
296
297     [php]
298     public function executeShow($request)
299     {
300       $this->post = PostPeer::retrieveByPk($request->getParameter('id'));
301       $this->forward404Unless($this->post);
302
303       $c = new Criteria();
304       $c->add(CommentPeer::POST_ID, $request->getParameter('id'));
305       $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
306       $this->comments = CommentPeer::doSelect($c);
307     }
308
309 The `Criteria` and `-Peer` objects are part of Propel's object-relational mapping.
310 Basically, these four lines will handle a SQL query to the `Comment` table to get
311 the comments related to the current `Post` (the one designated by the URL parameter `id`).
312 The `$this->comments` line in the action will give access to a `$comments` variable
313 in the corresponding template. Now, modify the post display template
314 `sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php` by adding at the end:
315
316     [php]
317     ...
318     <?php use_helper('Text', 'Date') ?>
319
320     <hr />
321     <?php if ($comments) : ?>
322       <p><?php echo count($comments) ?> comment<?php if (count($comments) > 1) : ?>s<?php endif; ?> to this post.</p>
323       <?php foreach ($comments as $comment): ?>
324         <p><em>posted by <?php echo $comment->getAuthor() ?> on <?php echo format_date($comment->getCreatedAt()) ?></em></p>
325         <div class="comment" style="margin-bottom:10px;">
326           <?php echo simple_format_text($comment->getBody()) ?>
327         </div>
328       <?php endforeach; ?>
329     <?php endif; ?>
330
331 This page uses new PHP functions (`format_date()` and `simple_format_text()`)
332 provided by symfony, and called 'helpers' because they do some tasks for you
333 that would normally require more time and code. Create a new comment for your
334 first post, then check again the first post, either by clicking on its number
335 in the list, or by typing directly:
336
337     http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1
338
339 ![Comment under post](/images/tutorials/first_comments_under_post.gif)
340
341 This is getting good.
342
343 Find more about the [naming conventions](http://www.symfony-project.org/book/1_1/07-Inside-the-View-Layer)
344 linking an action to a template.
345
346 Add a record relative to another table
347 --------------------------------------
348
349 When adding a comment, you can choose the `id` of the related post.
350 That's not very user-friendly. Let's change this, and make sure that the user
351 comes back to the post he was looking at after adding a comment.
352
353 First, in the still fresh `modules/post/templates/showSuccess.php` template,
354 add a line at the bottom:
355
356     [php]
357     <?php echo link_to('Add a comment', 'comment/create?post_id='.$post->getId()) ?>
358
359 The `link_to()` helper creates a hyperlink pointing to the `create` action of
360 the `comment` module, so you can add a comment directly from the post details page.
361 Next, open the `modules/comment/templates/editSuccess.php` and replace the
362 following lines:
363
364     [php]
365     <tr>
366       <th>Post:</th>
367       <td><?php echo object_select_tag($comment, 'getPostId', array (
368       'related_class' => 'Post',
369     )) ?></td>
370     </tr>
371
372 By:
373
374     [php]
375     <?php if ($sf_params->has('post_id')): ?>
376       <?php echo input_hidden_tag('post_id',$sf_params->get('post_id')) ?>
377     <?php else: ?>
378       <tr>
379         <th>Post*:</th>
380         <td><?php echo object_select_tag($comment, 'getPostId', array('related_class' => 'Post')) ?></td>
381       </tr>
382     <?php endif; ?>
383
384 The form in the `comment/create` page points to a `comment/update` action, which
385 redirects to `comment/show` when submitted (this is the default behaviour in generated CRUDs).
386 For the blog, that means that after adding a comment to a post, the detail of the
387 comment is displayed. It is better to display the post with the comments at that point.
388 So open the `modules/comment/actions/actions.class.php` and look for the `executeUpdate()`
389 method. Note that the `created_at` field is not defined by the action: symfony knows
390 that a field named `created_at` has to be set to the system time when a record is created.
391 The final redirect of the action has to be modified to point to the correct action.
392 Change it to:
393
394     [php]
395     public function executeUpdate($request)
396     {
397       if (!$request->getParameter('id'))
398       {
399         $comment = new Comment();
400       }
401       else
402       {
403         $comment = CommentPeer::retrieveByPk($request->getParameter('id'));
404         $this->forward404Unless($comment);
405       }
406
407       $comment->setId($request->getParameter('id'));
408       $comment->setPostId($request->getParameter('post_id'));
409       $comment->setAuthor($request->getParameter('author'));
410       $comment->setEmail($request->getParameter('email'));
411       $comment->setBody($request->getParameter('body'));
412
413       $comment->save();
414
415       return $this->redirect('post/show?id='.$comment->getPostId());
416     }
417
418 Users can now add comments to a post and come back to the post afterwards.
419 You wanted a blog? You have a blog.
420
421 Find more about [actions](http://www.symfony-project.org/book/1_1/06-Inside-the-Controller-Layer).
422
423 Form Validation
424 ---------------
425
426 Visitors can enter comments, but what if they submit the form without any data
427 in it? You will have a dirty database. To avoid that, create a file called
428 `update.yml` in the `sf_sandbox/apps/frontend/modules/comment/validate/`
429 directory (you also have to create the directory) and write in:
430
431     methods:
432       post:           [author, email, body]
433       get:            [author, email, body]
434
435     fillin:
436       enabled:       on
437
438     names:
439       author:
440         required:     Yes
441         required_msg: The name field cannot be left blank
442
443       email:
444         required:     No
445         validators:   emailValidator
446
447       body:
448         required:     Yes
449         required_msg: The text field cannot be left blank
450
451     emailValidator:
452       class:          sfEmailValidator
453       param:
454         email_error:  The email address is not valid.
455
456 >**Note**: Beware that you don't copy 4 extra spaces at the beginning of each
457 >line, since the YAML parser would fail in that case. The first letter of this
458 >file must be the 'm' of 'methods'.
459
460 The `fillin` activation enables the repopulation of the form with the value
461 previously entered by the user in case of failed validation. The `names` declarations
462 set the validation rules for each input of the form.
463
464 By itself, the controller will redirect the user to a `updateError.php` template
465 if an error is detected. It would be better to display the form again with an
466 error message. To do that, add a `handleErrorUpdate` method to the action class
467 of the `modules/comment/actions/actions.class.php` file:
468
469     [php]
470     public function handleErrorUpdate()
471     {
472       // forward it to edit if the id exists
473       if (!$this->getRequestParameter('id'))
474       {
475         $this->forward('comment', 'create');
476       }
477       else
478       {
479         $this->forward('comment', 'edit');
480       }
481     }
482
483 Now to finish, open again the `modules/comment/templates/editSuccess.php` template
484 and insert at the top:
485
486     [php]
487     <?php if ($sf_request->hasErrors()): ?>
488       <div id="errors" style="padding:10px;">
489         Please correct the following errors and resubmit:
490         <ul>
491         <?php foreach ($sf_request->getErrors() as $error): ?>
492           <li><?php echo $error ?></li>
493         <?php endforeach; ?>
494         </ul>
495       </div>
496     <?php endif; ?>
497
498 You now have a robust form.
499
500 ![Form validation](/images/tutorials/first_form_validation.gif)
501
502 Find more about [form validation](http://www.symfony-project.org/book/1_1/10-Forms).
503
504 Change the URL aspect
505 ---------------------
506
507 Did you notice the way the URLs are rendered? You can make them more user and
508 search engine-friendly. Let's use the post title as an URL for posts.
509
510 The problem is that post titles can contain special characters like spaces.
511 If you just escape them, the URL will show some ugly `%20` kind of things,
512 so you'd better extend the model to add a new method to the `Post` object
513 to get a clean, stripped title. To do that, edit the file `Post.php` located
514 in the `sf_sandbox/lib/model/` directory, and add the following method:
515
516     [php]
517     public function getStrippedTitle()
518     {
519       $result = strtolower($this->getTitle());
520
521       // strip all non word chars
522       $result = preg_replace('/\W/', ' ', $result);
523
524       // replace all white space sections with a dash
525       $result = preg_replace('/\ +/', '-', $result);
526
527       // trim dashes
528       $result = preg_replace('/\-$/', '', $result);
529       $result = preg_replace('/^\-/', '', $result);
530
531       return $result;
532     }
533
534 Now you can create a `permalink` action for the `post` module. Add the following
535 method to the `modules/post/actions/actions.class.php`:
536
537     [php]
538     public function executePermalink($request)
539     {
540       $posts = PostPeer::doSelect(new Criteria());
541       $title = $request->getParameter('title');
542       foreach ($posts as $post)
543       {
544         if ($post->getStrippedTitle() == $title)
545         {
546           $request->setParameter('id', $post->getId());
547
548           return $this->forward('post', 'show');
549         }
550       }
551
552       $this->forward404();
553     }
554
555 The post list can call this `permalink` action instead of the `show` one for
556 each post. In `modules/post/templates/listSuccess.php`, delete the `id` table
557 header and cell, and change the `Title` cell from:
558
559     [php]
560     <td><?php echo $post->getTitle() ?></td>
561
562 To a link using a named rule we will create in a second:
563
564     [php]
565     <td><?php echo link_to($post->getTitle(), '@post?title='.$post->getStrippedTitle()) ?></td>
566
567 Just one more step: Edit the `routing.yml` located in the
568 `sf_sandbox/apps/frontend/config/` directory and add these rules at the top:
569
570     list_of_posts:
571       url:   /latest_posts
572       param: { module: post, action: list }
573
574     post:
575       url:   /blog/:title
576       param: { module: post, action: permalink }
577
578 Now navigate again in your application and watch the URLs.
579
580 ![Routed URLs](/images/tutorials/first_routing.gif)
581
582 Find more about [smart URLs](http://www.symfony-project.org/book/1_1/09-Links-and-the-Routing-System).
583
584 Cleanup in the frontend
585 -----------------------
586
587 Well, if this is a blog, then everybody has the right to post.
588 This isn't exactly what you thought about, right? Ok, let's clean up our
589 templates a bit.
590
591 In the template `modules/post/templates/showSuccess.php`, get rid of the
592 'edit' link by removing the line:
593
594     [php]
595     <?php echo link_to('edit', 'post/edit?id='.$post->getId()) ?>
596
597 Do the same for the `modules/post/templates/listSuccess.php` template and remove:
598
599     [php]
600     <?php echo link_to('create', 'post/create') ?>
601
602 You also have to remove the following methods from the `modules/post/actions/actions.class.php`:
603
604     * executeCreate
605     * executeEdit
606     * executeUpdate
607     * executeDelete
608
609 All right, readers cannot post anymore.
610
611 Generation of the backend
612 -------------------------
613
614 For you to write posts, let's create a backend application by typing in the
615 command line (still from the `sf_sandbox` project directory):
616
617     $ php symfony generate:app backend
618     $ php symfony propel:init-admin backend post Post
619     $ php symfony propel:init-admin backend comment Comment
620
621 This time, we use the [admin generator](http://www.symfony-project.org/book/1_1/14-Generators).
622 It offers much more features and customization than the basic CRUD generator.
623
624 Just like you did for the `frontend` application, edit the layout (`apps/backend/templates/layout.php`)
625 to add global navigation:
626
627     <div id="navigation">
628       <ul style="list-style:none;">
629         <li><?php echo link_to('Manage posts', 'post/list') ?></li>
630         <li><?php echo link_to('Manage comments', 'comment/list') ?></li>
631       </ul>
632     </div>
633     <div id="content">
634       <?php echo $sf_data->getRaw('sf_content') ?>
635     </div>
636
637 You can access your new back-office application in the development environment by calling:
638
639     http://localhost/sf_sandbox/web/backend_dev.php/post
640
641 ![basic generated admin](/images/tutorials/first_basic_admin.gif)
642
643 The great advantage of the generated admin is that you can easily customize it by
644 editing a configuration file.
645
646 Change the `backend/modules/post/config/generator.yml` to:
647
648     generator:
649       class:              sfPropelAdminGenerator
650       param:
651         model_class:      Post
652         theme:            default
653         fields:
654           title:          { name: Title }
655           excerpt:        { name: Exerpt }
656           body:           { name: Body }
657           nb_comments:    { name: Comments }
658           created_at:     { name: Creation date }
659         list:
660           title:          Post list
661           layout:         tabular
662           display:        [=title, excerpt, nb_comments, created_at]
663           object_actions:
664             _edit:        ~
665             _delete:      ~
666           max_per_page:   5
667           filters:        [title, created_at]
668         edit:
669           title:          Post detail
670           fields:
671             title:        { type: input_tag, params: size=53 }
672             excerpt:      { type: textarea_tag, params: size=50x2 }
673             body:         { type: textarea_tag, params: size=50x10 }
674             created_at:   { type: input_date_tag, params: rich=on }
675
676 Note that among the existing columns of the `Post` table, the admin will look
677 for a `nb_comments`. There is no associated getter yet, but it is simple to
678 add to the `sf_sandbox/lib/model/Post.php`:
679
680     [php]
681     public function getNbComments()
682     {
683       return count($this->getComments());
684     }
685
686 Now refresh the Post administration an see the changes:
687
688 ![customized generated admin](/images/tutorials/first_custom_admin.gif)
689
690 Restrict access to the backend
691 ------------------------------
692
693 The backend can be accessed by everybody. You have to add access restriction.
694
695 In `apps/backend/modules/post/config/`, add a `security.yml` with the following content:
696
697     all:
698       is_secure: on
699
700 Repeat the operation for the `comment` module. Now you can't access these modules
701 anymore unless you are logged.
702
703 But the login action doesn't exist! Ok, so you can easily add it.
704 First, create the `security` module skeleton:
705
706     $ php symfony generate:module backend security
707
708 This new module will be used to handle the login form and the request.
709 Edit the `apps/backend/modules/security/templates/indexSuccess.php` to
710 create the login form:
711
712     [php]
713     <h2>Authentication</h2>
714
715     <?php if ($sf_request->hasErrors()): ?>
716       Identification failed - please try again
717     <?php endif; ?>
718
719     <?php echo form_tag('security/login') ?>
720       <label for="login">login:</label>
721       <?php echo input_tag('login', $sf_params->get('login')) ?>
722
723       <label for="password">password:</label>
724       <?php echo input_password_tag('password') ?>
725
726       <?php echo submit_tag('submit', 'class=default') ?>
727     </form>
728
729 Add the `login` action that is called by the form to the `security` module
730 (in the `apps/backend/modules/security/actions/actions.class.php` file):
731
732     [php]
733     public function executeLogin($request)
734     {
735       if ($request->getParameter('login') == 'admin' && $request->getParameter('password') == 'password')
736       {
737         $this->getUser()->setAuthenticated(true);
738
739         return $this->redirect('post/list');
740       }
741       else
742       {
743         $request->setError('login', 'incorrect entry');
744
745         return $this->forward('security', 'index');
746       }
747     }
748
749 Like for the `main` module, remove the default code in the `index` action:
750
751     [php]
752     public function executeIndex()
753     {
754     }
755
756 The last thing to do is to set the `security` module as the default module to
757 handle login actions. To do that, open the `apps/backend/config/settings.yml`
758 configuration file and add:
759
760     all:
761       .actions:
762         login_module:           security
763         login_action:           index
764
765 At that point, if you try to access the Posts management, you will have to enter
766 a login and a password:
767
768 ![login form](/images/tutorials/first_login.gif)
769
770 Find more about [security](http://www.symfony-project.org/book/1_1/06-Inside-the-Controller-Layer#Action%20Security).
771
772 Conclusion
773 ----------
774
775 Ok, the hour is out. You made it. Now you can use both applications in the production
776 environment and play with them:
777
778     frontend:   http://localhost/sf_sandbox/web/index.php/
779     backend:    http://localhost/sf_sandbox/web/backend.php/
780
781 At this point, if you meet an error, it might be because you changed the model after
782 some actions were put in cache (cache isn't activated in the development environment).
783 To clear the cache, simply type:
784
785     $ php symfony cc
786
787 See, the application is fast and runs smoothly. Pretty darn cool, isn't it?
788 Feel free to explore the code, add new modules, and change the design of pages.
789
790 And don't forget to mention your working symfony applications in the
791 [symfony Wiki](http://trac.symfony-project.org/wiki/ApplicationsDevelopedWithSymfony)!
Note: See TracBrowser for help on using the browser.