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 20150, 33.7 kB (checked in by Russ, 4 years ago)

[doc][1.1,1.2] Refs #5350 - Inconsistent Key Names in "My first symfony project"

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