Development

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

You must first sign up to be able to contribute.

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

Revision 13080, 32.6 kB (checked in by fabien, 5 years ago)

[doc] [1.1, 1.2] changed \ to / in MFP tuto (closes #4974)

Line 
1 My first symfony project
2 ========================
3
4 So, you want to give it a go? Let's build together a fully-functional web app in
5 one hour. You name it. A bookseller application? Okay, another idea. A blog!
6 That's a good one. Let's go.
7
8 This tutorial assumes that you are working with Apache installed and running on your
9 local machine. You will also need PHP 5.1.3 or newer.
10
11 Install symfony and initialize the project
12 ------------------------------------------
13
14 To go fast, the symfony sandbox will be used. This is an empty symfony project where all
15 the required libraries are already included,
16 and where the basic configuration is already done. The great advantage of the sandbox
17 over other types of installation is that you can start experimenting with symfony
18 immediately.
19
20 Get it here: [sf_sandbox_1_2.tgz](http://www.symfony-project.org/get/sf_sandbox_1_2.tgz)
21 or here: [sf_sandbox_1_2.zip](http://www.symfony-project.org/get/sf_sandbox_1_2.zip),
22 and unpack it in your root web directory. On Linux systems, it is recommended to keep the
23 permissions as they are in the tar file (for example by using `-p` with `tar` command).
24 Refer to the included `README` file for more information. The resulting file structure
25 should look like this:
26
27     www/
28       sf_sandbox/
29         apps/
30           frontend/
31         cache/
32         config/
33         data/
34         doc/
35         lib/
36         log/
37         plugins/
38         test/
39         web/
40           css/
41           images/
42           js/
43
44 This shows a `sf_sandbox` **project** containing a `frontend` **application**.
45 Test the sandbox by requesting the following URL:
46
47     http://localhost/sf_sandbox/web/index.php/
48
49 You should see a congratulations page.
50
51 ![Congratulations](/images/tutorials/mfp_1_2/first-congrats.png)
52
53 You can also install symfony in a custom folder and setup your web server with
54 a Virtual Host or an Alias. The symfony book contains detailed chapters about
55 [symfony installation](http://www.symfony-project.org/book/1_2/03-Running-Symfony)
56 and the [symfony directory structure](http://www.symfony-project.org/book/1_2/02-Exploring-Symfony-s-Code).
57
58 Initialize the data model
59 -------------------------
60
61 So, the blog will handle posts, and you will enable comments on them. Create a
62 `schema.yml` file in `sf_sandbox/config/` and paste the following data model:
63
64     [yml]
65     propel:
66       blog_post:
67         id:           ~
68         title:        { type: varchar(255), required: true }
69         excerpt:      { type: longvarchar }
70         body:         { type: longvarchar }
71         created_at:   ~
72       blog_comment:
73         id:           ~
74         blog_post_id: ~
75         author:       { type: varchar(255) }
76         email:        { type: varchar(255) }
77         body:         { type: longvarchar }
78         created_at:   ~
79
80 This configuration file uses the YAML syntax. It's a very simple language that
81 allows XML-like tree structures described by indentation. Furthermore, it is
82 faster to read and write than XML. The only thing is, the indentation has a
83 meaning and tabulations are forbidden, so remember to use spaces for indentation.
84 You will find more about YAML and the symfony configuration in the
85 [configuration chapter](http://www.symfony-project.org/book/1_2/05-Configuring-Symfony).
86
87 This schema describes the structure of two of the tables needed for the blog.
88 `blog_post` and `blog_comment` are the names of the related classes to be generated.
89 Save the file, open a command line, browse to the `sf_sandbox/` directory and type:
90
91     $ php symfony propel:build-model
92
93 >**Note**: Make sure your command line folder is set to the root of your project
94 >(`sf_sandbox/`) when you call the `symfony` command.
95
96 A few classes are created in the `sf_sandbox/lib/model/` directory. These are the
97 classes of the object-relational mapping system, which allows us to have access to a relational
98 database from within an object-oriented code without writing a single SQL query.
99 By default, symfony uses the Propel library for this purpose. These classes are
100 part of the **model** of our application
101 (find more in the [model chapter](http://www.symfony-project.org/book/1_2/08-Inside-the-Model-Layer)).
102
103 Now, we need to convert the schema to SQL statements to initialize the database tables.
104 By default, the symfony sandbox is configured to work out of the box with a simple
105 SQLite file, so no database initialization is required.
106 You still need to check that the SQLite extension is installed and enabled correctly
107 (you can check this in `php.ini` - see how
108 [in the PHP documentation](http://fr3.php.net/manual/en/ref.sqlite.php)).
109
110 By default, the `sf_sandbox` project will use a database file called `sandbox.db`
111 located in `sf_sandbox/data/`.
112
113 If you want to switch to MySQL for this project, use the `configure:database` task:
114
115     $ php symfony configure:database mysql://root:pa$$word@localhost/symfony_project
116
117 Change the DSN argument to match your settings (username, password, host, and database name)
118 and then create the database with the command line or a web interface (as described in
119 the [model chapter](http://www.symfony-project.org/book/1_2/08-Inside-the-Model-Layer)).
120 Then, open `sf_sandbox/config/databases.yml` and set 'phptype' to 'mysql', and 'database'
121 to the name of your MySQL database.
122
123 >**Caution**
124 >If you use SQLite as your database engine, you will have to change some rights on *nix systems:
125 >
126 >     $ chmod 777 data data/sandbox.db
127
128 Now type in the command line:
129
130     $ php symfony propel:build-sql
131
132 A `lib.model.schema.sql` file is created in `sf_sandbox/data/sql/`.
133 The SQL statements found is this file can be used to initialize a database with
134 the same table structure.
135
136 To build the table structure based on the the SQL file, type:
137
138     $ php symfony propel:insert-sql
139
140 >**Note**: Don't worry if there is a warning at that point, it is normal.
141 >The `propel:insert-sql` command removes existing tables before adding the ones
142 >from your `lib.model.schema.sql`, and there are no tables to remove at the moment.
143
144 As you want to be able to create and edit blog posts and comments, you also need to
145 generate some forms based on the model schema:
146
147     $ php symfony propel:build-forms
148
149 This task generates classes in the `sf_sandbox/lib/form/` directory.
150 These classes are used to manage your model objects as forms.
151
152 >**Tip**
153 >All the above commands can be done in a single one by using `propel:build-all`.
154
155 Create the application
156 ----------------------
157
158 The basic features of a blog are to be able to Create, Retrieve, Update and
159 Delete (CRUD) posts and comments. As you are new to symfony, you will not create
160 symfony code from scratch, but rather let it generate the code that you may use
161 and modify as needed. Symfony can interpret the data model to generate the CRUD
162 interface automatically:
163
164     $ php symfony propel:generate-module --non-verbose-templates --with-show frontend post BlogPost
165     $ php symfony propel:generate-module --non-verbose-templates frontend comment BlogComment
166     $ php symfony cache:clear
167
168 >**Tip**
169 >When using the `propel:generate-module` task, you have used the `--non-verbose-templates`
170 >option. If you want to learn the meaning of the available
171 >arguments and options for a given task, you can use the special `help` task:
172 >
173 >     $ php symfony help propel:generate-module
174
175 You now have two modules (`post` and `comment`) that will let you manipulate
176 objects of the `BlogPost` and `BlogComment` classes. A **module** usually represents a
177 page or a group of pages with a similar purpose. Your new modules are located
178 in the `sf_sandbox/apps/frontend/modules/` directory, and they are accessible
179 by the URLs:
180
181     http://localhost/sf_sandbox/web/frontend_dev.php/post
182     http://localhost/sf_sandbox/web/frontend_dev.php/comment
183
184 If you try to create a comment, you will have an error because symfony doesn't yet
185 know how to convert a post object to a string. Edit the `BlogPost` class
186 (`lib/model/BlogPost.php`) and add the `__toString()` method:
187
188     [php]
189     class BlogPost extends BaseBlogPost
190     {
191       public function __toString()
192       {
193         return $this->getTitle();
194       }
195     }
196
197 Lastly, add the following CSS to `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 Now, feel free to create some new posts to make the blog look less empty.
208
209 ![post CRUD](/images/tutorials/mfp_1_2/first-crud.png)
210
211 Find more about [generators](http://www.symfony-project.org/book/1_2/14-Generators)
212 and the explanation of symfony projects
213 [structure](http://www.symfony-project.org/book/1_2/04-The-Basics-of-Page-Creation)
214 (project, application, module).
215
216 >**Note**: In the URLs above, the name of the main script - called the
217 >*front controller* in symfony - was changed from `index.php` to `frontend_dev.php`.
218 >The two scripts access the same application (`frontend`), but in different environments.
219 >With `frontend_dev.php`, you access the application in the **development environment**,
220 >which provides handy development tools like the debug toolbar on the top right
221 >of the screen and the live configuration engine. That's why the processing of
222 >each page is slower than when using `index.php`, which is the front controller
223 >of the **production environment**, optimized for speed. If you want to keep on
224 >using the production environment, replace `frontend_dev.php/` by `index.php/`
225 >in the following URLs, but don't forget to clear the cache before watching the
226 >changes:
227 >
228 >     $ php symfony cache:clear
229 >
230 >     http://localhost/sf_sandbox/web/index.php/
231
232 Find more about [environments](http://www.symfony-project.org/book/1_2/05-Configuring-Symfony#Environments).
233
234 Modify the layout
235 -----------------
236
237 In order to navigate between the two new modules, the blog needs some global navigation.
238
239 Edit the global template `sf_sandbox/apps/frontend/templates/layout.php` and
240 change the content of the `<body>` tag to:
241
242     [php]
243     <div id="container" style="width:700px;margin:0 auto;border:1px solid grey;padding:10px">
244       <div id="navigation" style="display:inline;float:right">
245         <ul>
246           <li><?php echo link_to('List of posts', 'post/index') ?></li>
247           <li><?php echo link_to('List of comments', 'comment/index') ?></li>
248         </ul>
249       </div>
250       <div id="title">
251         <h1><?php echo link_to('My first symfony project', '@homepage') ?></h1>
252       </div>
253
254       <div id="content" style="clear:right">
255         <?php echo $sf_data->getRaw('sf_content') ?>
256       </div>
257     </div>
258
259 Please forgive the poor design and the use of inner-tag css, but
260 one hour is rather a short amount of time!
261
262 ![post CRUD in layout](/images/tutorials/mfp_1_2/first-crud-layout.png)
263
264 While you are at it, you can change the title of your pages.
265 Edit the view configuration file of the application (`sf_sandbox/apps/frontend/config/view.yml`),
266 locate the line showing the `title` key and change it something appropriate. Note that
267 several lines here are commented out with a hash symbol - you can uncomment these if
268 you wish.
269
270     default:
271       http_metas:
272         content-type: text/html
273
274       metas:
275         title:        The best blog ever
276         #description:  symfony project
277         #keywords:     symfony, project
278         #language:     en
279         robots:       index, follow
280
281 The home page itself needs to be changed. It uses the default template of the
282 `default` module, which is kept in the framework but not in your application
283 directory. To override it, you can create a custom `main` module:
284
285     $ php symfony generate:module frontend main
286
287 By default, the `index` action shows a default congratulations screen.
288 To remove it, edit the `sf_sandbox/apps/frontend/modules/main/actions/actions.class.php`
289 and remove the content of the `executeIndex()` method as follows:
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 Edit the `sf_sandbox/apps/frontend/modules/main/templates/indexSuccess.php` file
302 to show a nice welcome message:
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 Now, you must tell symfony which action to execute when the homepage is requested.
309 To do so, edit the `sf_sandbox/apps/frontend/config/routing.yml` and change
310 the `homepage` rule as follows:
311
312     [yml]
313     homepage:
314       url:   /
315       param: { module: main, action: index }
316
317 Check the result by requesting the home page again:
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 Go ahead, start using your new web app. Make sure you've created a test post, and
324 also create a test comment against your post.
325
326 Find more about [views and templates](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 `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/post/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/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 `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 `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 $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 `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 `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 `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 needs 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-sql
789     $ php symfony propel:insert-sql
790
791 >**Note**
792 >When launching the `propel:insert-sql` task, symfony will drop all tables to re-create them.
793 >As during the development this happens a lot, symfony can store initial data in fixtures
794 >(see [populating a database](http://www.symfony-project.org/book/1_2/16-Application-Management-Tools#Populating%20a%20Database) for more information).
795
796 Now, clear your cache again. Finally, create a user by running:
797
798     $ symfony guard:create-user frontend jon SoMePasSwOrD
799
800 Let's now make the post administration module the default one in our backend system. To
801 do this, open up the `apps/backend/config/routing.yml` file and locate the
802 `homepage` key. Change the module from `default` to `post`.
803
804 At that point, if you try to access the posts management
805 (http://localhost/sf_sandbox/web/backend_dev.php/post), you will have to enter a valid username
806 and password:
807
808 ![login form](/images/tutorials/mfp_1_2/logon.png)
809
810 Find more about [security](http://www.symfony-project.org/book/1_2/06-Inside-the-Controller-Layer#Action%20Security).
811
812 Conclusion
813 ----------
814
815 Ok, the hour is out. You made it. Now you can use both applications in the production
816 environment and play with them:
817
818     frontend:   http://localhost/sf_sandbox/web/index.php/
819     backend:    http://localhost/sf_sandbox/web/backend.php/
820
821 At this point, if you meet an error, it might be because you changed the model after
822 some actions were put in cache (cache isn't activated in the development environment).
823 To clear the cache, simply type:
824
825     $ php symfony cc
826
827 See, the application is fast and runs smoothly. Pretty cool, isn't it?
828 Feel free to explore the code, add new modules, and change the design of pages.
829
830 And don't forget to mention your working symfony applications in the
831 [symfony Wiki](http://trac.symfony-project.org/wiki/ApplicationsDevelopedWithSymfony)!
Note: See TracBrowser for help on using the browser.