пятница, 28 марта 2008 г.

Завершаем строительство Blog

При добалении коментария, мы должны выбирать соответствующий id post. Это неудобно.


Добавим снизу файла modules/post/templates/showSuccess.php :


<?php echo link_to('Add a comment', 'comment/create?post_id='.$post->getId()) ?>


заменим в файле


modules/comment/templates/editSuccess.php строки:


<tr>

<th>Post:</th>

<td><?php echo object_select_tag($comment, 'getPostId', array (

'related_class' => 'Post',

)) ?></td>

</tr>


на:


<?php if ($sf_params->has('post_id')): ?>

<?php echo input_hidden_tag('post_id',$sf_params->get('post_id')) ?>

<?php else: ?>

<tr>

<th>Post*:</th>

<td><?php echo object_select_tag($comment, 'getPostId', array('related_class' => 'Post')) ?></td>

</tr>

<?php endif; ?>


В файле modules/comment/actions/actions.class.php найдем метод executeUpdate(). Изменим его:


public function executeUpdate ()

{

if (!$this->getRequestParameter('id', 0))

{

$comment = new Comment();

}

else

{

$comment = CommentPeer::retrieveByPk($this->getRequestParameter('id'));

$this->forward404Unless($comment);

}



$comment->setId($this->getRequestParameter('id'));

$comment->setPostId($this->getRequestParameter('post_id'));

$comment->setAuthor($this->getRequestParameter('author'));

$comment->setEmail($this->getRequestParameter('email'));

$comment->setBody($this->getRequestParameter('body'));



$comment->save();



return $this->redirect('post/show?id='.$comment->getPostId());

}


Валидпция форм


Создадим update.yml в /modules/comment/validate/ :


 

methods:
post: [author, email, body]
get: [author, email, body]

fillin:
enabled: on



names:
author:
required: Yes
required_msg: The name field cannot be left blank


 email:
required: No
validators: emailValidator

 body:
required: Yes
required_msg: The text field cannot be left blank

emailValidator:
class: sfEmailValidator
param:
email_error: The email address is not valid.

 

Конроллер будет перенаправлять пользователя в случае ошибки на updateError.php . Для реализации этого добавим метод handleErrorUpdate в класс действий modules/comment/actions/actions.class.php:


public function handleErrorUpdate()

{

$this->forward('comment', 'create');

}


Изменим шаблон modules/comment/templates/editSuccess.php, добавив сверху:


<?php if ($sf_request->hasErrors()): ?>

<div id="errors" style="padding:10px;">

Please correct the following errors and resubmit:

<ul>

<?php foreach ($sf_request->getErrors() as $error): ?>

<li><?php echo $error ?></li>

<?php endforeach; ?>

</ul>

</div>

<?php endif; ?>


 


Убираем спецсимволы %20 Редактируем Post.php в /lib/model/ :


public function getStrippedTitle()

{

$result = strtolower($this->getTitle());



// strip all non word chars

$result = preg_replace('/\W/', ' ', $result);



// replace all white space sections with a dash

$result = preg_replace('/\ +/', '-', $result);



// trim dashes

$result = preg_replace('/\-$/', '', $result);

$result = preg_replace('/^\-/', '', $result);



return $result;

}


Создаем действие permalink. Добавим метод в modules/post/actions/actions.class.php:


public function executePermalink()

{

$posts = PostPeer::doSelect(new Criteria());

$title = $this->getRequestParameter('title');

foreach ($posts as $post)

{

if ($post->getStrippedTitle() == $title)

{

$this->getRequest()->setParameter('id', $post->getId());



return $this->forward('post', 'show');

}

}



$this->forward404();

}


Меняем шаблон modules/post/templates/listSuccess.php, удаляя id table header и лоетку, а также заменяя Title с:


<td><?php echo $post->getTitle() ?></td>


на:


<td><?php echo link_to($post->getTitle(), '@post?title='.$post->getStrippedTitle()) ?></td>


Редактируем routing.yml из /config/ добавляя сверзу:


list_of_posts:
url: /latest_posts
param: { module: post, action: list }

post:
url: /weblog/:title
param: { module: post, action: permalink }

Защищаем приложение


В шаблоне modules/post/templates/showSuccess.php удаляем:

<?php echo link_to('edit', 'post/edit?id='.$post->getId()) ?>


Также в modules/post/templates/listSuccess.php удаляем:


<?php echo link_to('create', 'post/create') ?>


Также можно удалить методы из modules/post/actions/actions.class.php:


* executeCreate

* executeEdit

* executeUpdate

* executeDelete


Создаем админку


$ symfony init-app backend

$ symfony propel-init-admin backend post Post

$ symfony propel-init-admin backend comment Comment


Редактируем (apps/backend/templates/layout.php):


<div id="navigation">

<ul style="list-style:none;">

<li><?php echo link_to('Manage posts', 'post/list') ?></li>

<li><?php echo link_to('Manage comments', 'comment/list') ?></li>

</ul>

</div>

<div id="content">

<?php echo $sf_data->getRaw('sf_content') ?>

</div>


Генерирум форму backend/modules/post/config/generator.yml:


generator:
class: sfPropelAdminGenerator
param:
model_class: Post
theme: default
fields:
title: { name: Title }
excerpt: { name: Exerpt }
body: { name: Body }
nb_comments: { name: Comments }
created_at: { name: Creation date }
list:
title: Post list
layout: tabular
display: [=title, excerpt, nb_comments, created_at]
object_actions:
_edit: ~
_delete: ~
max_per_page: 5
filters: [title, created_at]
edit:
title: Post detail
fields:
title: { type: input_tag, params: size=53 }
excerpt: { type: textarea_tag, params: size=50x2 }
body: { type: textarea_tag, params: size=50x10 }
created_at: { type: input_date_tag, params: rich=on }

 


Редактируем /lib/model/Post.php:

public function getNbComments()

{

return count($this->getComments());

}


Защищаем вход в админку


в apps/backend/modules/post/config/security.yml:


all:
is_secure: on

 


$symfony init-module backend security


Редактируем apps/backend/modules/security/templates/indexSuccess.php:


<h2>Authentication</h2>



<?php if ($sf_request->hasErrors()): ?>

Identification failed - please try again

<?php endif; ?>



<?php echo form_tag('security/login') ?>

<label for="login">login:</label>

<?php echo input_tag('login', $sf_params->get('login')) ?>



<label for="password">password:</label>

<?php echo input_password_tag('password') ?>



<?php echo submit_tag('submit', 'class=default') ?>

</form>


Добавляем модуль в apps/backend/modules/security/actions/actions.class.php:


public function executeLogin()

{

if ($this->getRequestParameter('login') == 'admin' && $this->getRequestParameter('password') == 'password')

{

$this->getUser()->setAuthenticated(true);



return $this->redirect('main/index');

}

else

{

$this->getRequest()->setError('login', 'incorrect entry');



return $this->forward('security', 'index');

}

}


Редактируем :


public function executeIndex()

{

}


в apps/backend/config/settings.yml добавляем:


 


all:
.actions:
login_module: security
login_action: index

Проверяем


http://localhost/index.php/

http://localhost/backend.php/


$ symfony cc





пятница, 21 марта 2008 г.

Страница общего доступа

После добавления нового модуля, который мы назвали public


$ symfony init-module frontend public


мы заменили стандартную страницу приветствия


// apps/.../actions/actions.class.php

public function executeIndex()

{

$c = new Criteria();

$c->addDescendingOrderByColumn(PhotoPeer::CREATED_AT);

$this->photos = PhotoPeer::doSelect($c);

}


Оформление нового шаблона :


// apps/.../modules/public/templates/indexSuccess.php

<div id="main">

<h1>Mои фотографии</h1>

<?php foreach($photos as $photo): ?>

<div class="photo">

<?php echo link_to(

image_tag('/uploads/thumbnail/'.$photo->getFilePath()),

'public/photo?id='.$photo->getId(),

'class=image title='.$photo->getDescription()

) ?>

"<?php echo $photo->getDescription() ?>"

on <?php echo $photo->getCreatedAt('d/m') ?>,

tagged <?php echo $photo->getTagsString() ?>

</div>

<?php endforeach; ?>

<div id="footer">

сделано на <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>

</div>

</div>


Подгрузили стили:


// apps/..../actions/actions.class.php

public function preExecute()

{

$this->getResponse()->addStylesheet('frontend');

}


Пример стилей


body

{

font-family: "Trebuchet MS", arial, sans-serif;

background: #edd;

}

#main

{

margin: auto;

width: 680px;

padding:20px;

background: white;

}

#main h1

{

margin-bottom: 20px;

border-bottom: solid 1px lightgrey;

}

#footer

{

clear: left;

text-align:right;

}

.photo

{

width: 160px;

padding-right: 10px;

padding-bottom: 10px;

float:left;

}

a.image:hover img

{

border: solid 4px grey;

}


a.image img

{

border: solid 4px lightgrey;

}


label

{

display: block;

margin-top: 5px;



}


.comment

{

background: #eee;

border: solid 1px #ddd;

margin: 10px;

padding: 5px;

}

.comment .details

{

font-weight: bold;

}


При вызове конкретной фотографии, выполняется действие:


// apps/.../actions/actions.class.php

public function executePhoto()

{

$photo = PhotoPeer::retrieveByPk($this->getRequestParameter('id'));

$this->forward404unless($photo);

$this->photo = $photo;

}


Результат отображается с помощью шаблона:


// apps/..../modules/public/templates/photoSuccess.php

<div id="main">

<?php echo link_to('back to the photo list', 'public/index',

'style=display:block;float:right;') ?>

<h1>Picture details</h1>

<a href="/uploads/<?php echo $photo->getFilePath() ?>" title="click for the full-size version">

<?php echo image_tag('/uploads/'.$photo->getFilePath(), 'width=100%') ?>

</a><br/>

<p>

"<?php echo $photo->getDescription() ?>"

published on <?php echo $photo->getCreatedAt('d/m') ?>,

tagged <?php echo $photo->getTagsString() ?>

</p>

<div id="footer">

powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>

</div>

</div>


Оптимизируем приложение


У двух шаблонов общий код:


// apps/..../modules/public/templates/_photo_description.php

"<?php echo $photo->getDescription() ?>"

published on <?php echo $photo->getCreatedAt('d/m') ?>,

tagged

<?php foreach($photo->getTags() as $tag): ?>

<?php $tag=$tag->getName(); echo link_to($tag, 'public/tag?tag='.$tag) ?>

<?php endforeach; ?>


Редактируем шаблоны indexSuccess.php и photoSuccess.php:


// in apps/frontend/modules/public/templates/photoSuccess.php

<div id="main">

<?php echo link_to('back to the photo list', 'public/index',

'style=display:block;float:right;') ?>

<h1>Picture details</h1>

<a href="/uploads/<?php echo $photo->getFilePath() ?>" title="click for the full-size version">

<?php echo image_tag('/uploads/'.$photo->getFilePath(), 'width=100%') ?>

</a><br/>

<p>

<?php echo include_partial('photo_description', array(

'photo' => $photo

)) ?>

</p>

<div id="footer">

powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>

</div>

</div>


Редактируем главный шаблон:


// in apps/frontend/templates/layout.php

<!DOCTYPE html PUBLIC "-//W3C [17]//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>

<?php echo include_http_metas() ?>

<?php echo include_metas() ?>

<?php echo include_title() ?>

<link rel="shortcut icon" href="/favicon.ico" />

</head>

<body>

<div id="main">

<?php echo $sf_data->getRaw('sf_content') ?>

<div id="footer">

powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>

</div>

</div>

</body>

</html>


Удалим <div id="main"></div> и the <div id="footer"></div> из обеих шаблонов и создадим новый шаблон для действия:


// apps/.../modules/public/actions/actions.class.php

public function executeTag()

{

$this->forward404Unless($tag = $this->getRequestParameter('tag'));

$c = new Criteria();

$c->addJoin(PhotoPeer::ID, TagPeer::PHOTO_ID);

$c->add(TagPeer::NAME, $tag);

$this->photos = PhotoPeer::doSelect($c);



$this->setTemplate('Index');

}


// apps/..../modules/public/templates/indexSuccess.php

<?php if($tag = $sf_params->get('tag')): ?>

<?php echo link_to('back to the photo list', 'public/index', 'style=display:block;float:right;') ?>

<?php endif; ?>

<h1>

My pictures

<?php if($tag): ?>

tagged "<?php echo $tag ?>"

<?php endif; ?>

</h1>

<?php foreach($photos as $photo): ?>

<div class="photo">

<?php echo link_to(

image_tag('/uploads/thumbnail/'.$photo->getFilePath()),

'public/photo?id='.$photo->getId(),

'class=image title='.$photo->getDescription()

) ?>

<?php echo include_partial('photo_description', array(

'photo' => $photo

)) ?>

</div>

<?php endforeach; ?>


//apps/..../modules/public/templates/photoSuccess.php

...

<?php use_helper('Javascript'); ?>

<div id="comments">

<h2>Comments</h2>

<?php foreach($photo->getComments() as $comment): ?>

<?php include_partial('comment', array('comment' => $comment)) ?>

<?php endforeach; ?>

<div id="updateDiv">

<?php echo link_to_function('Add a comment', visual_effect('toggle_blind', 'addComment')) ?>

<?php echo form_remote_tag(array(

'url' => 'public/addComment',

'update' => 'updateDiv',

'complete' => visual_effect('highlight', 'updateDiv'),

), 'id=addComment style=display:none;') ?>

<?php echo input_hidden_tag('photo_id', $photo->getId()) ?>

<?php echo label_for('author', 'Your name') ?>

<?php echo input_tag('author') ?><br />

<?php echo label_for('body', 'Your comment') ?>

<?php echo textarea_tag('body') ?><br />

<?php echo submit_tag('submit') ?>

</form>

</div>

</div>


_comment.php для отображения коментариев:


// apps/.../modules/photo/templates/_comment.php

<?php echo use_helper('Date') ?>

<div class="comment">

<p class="details">

<?php echo $comment->getAuthor() ?> said

<?php echo distance_of_time_in_words($comment->getPhoto()->getCreatedAt('U'), $comment->getCreatedAt('U')) ?> after

</p>

<p class="body"><?php echo $comment->getBody() ?></p>

</div>


Используем Ajax :


// in apps/.../modules/public/actions/actions.class.php

public function executeAddComment()

{

$this->forward404unless($photo = PhotoPeer::retrieveByPk($this->getRequestParameter('photo_id')));

$comment = new Comment();

$comment->setPhoto($photo);

$comment->setAuthor($this->getRequestParameter('author'));

$comment->setBody($this->getRequestParameter('body'));

$comment->save();



$this->comment = $comment;

}


Ajax шаблон:


// in apps/..../modules/public/templates/addCommentSuccess.php

<?php include_partial('comment', array('comment' => $comment)) ?>


 

суббота, 15 марта 2008 г.

Используем плагины

Используем плагины


Плагин sfThumbnail создает значки определенного размера для публикуемых изображений:


symfony plugin-install http://plugins.symfony-project.com/sfThumbnailPlugin


$ php symfony clear-cache


Применим установленный плагин sfThumbnail к нашему классу Photo. Добавим в Photo.php:


// in lib/model/Photo.php

public function setFilePath($value)

{

parent::setFilePath($value);

$this->generateThumbnail($value);

}

public function generateThumbnail($value)

{

parent::setFilePath($value);

$uploadDir = sfConfig::get('sf_upload_dir');

$thumbnail = new sfThumbnail(150, 150);

$thumbnail->loadFile($uploadDir.'/'.$this->getFilePath());

$thumbnail->save($uploadDir.'/thumbnail/'.$this->getFilePath(), 'image/png');

}


Это позволит создавать значки размером 150x150px от оригинального размера файла и сохранять из в каталог uploads/thumbnail/ . В классе generateThumbnail используется класс sfConfig для получения пути к директории uploads.


$ cd web/uploads

$ mkdir thumbnail

$ chmod 777 thumbnail


Также необходимо отредактировать файл _photo.php :


// in apps/prod/modules/photo/templates/_photo.php

<?php echo image_tag('/uploads/thumbnail/'.$photo->getFilePath()) ?>


Теперь можно загрузить изображение.


Для обеспечения безопасности в приложении необходима авторизация. Эту задачу решает плагин sfGuard Установим его:


$symfony plugin-install http://plugins.symfony-project.com/sfGuardPlugin


Разрешим его использование, отредактировав файл apps/prod/config/settings.yml:


// apps/prod/config/settings.yml

all:

.actions:

login_module: sfGuardAuth

login_action: signin

.settings:

enabled_modules: [default, sfGuardAuth, sfGuardUser]


Кроме того, неоюходимо помень наследование в классе myUser, изменив myUser.class.php:


//apps/prod/lib/myUser.class.php

class myUser extends sfGuardSecurityUser

{

}


Сообщим symfony, что все действия в модуле photo теперь требуют авторизации. В modules/photo/config/ :


// apps/prodd/modules/photo/config/security.yml

all:

is_secure: on


В sfGuardPlugin входит модуль управления пользователями, что дает возможность создавать и управлять пользователями. Для его включения тредуется пересоздать все

propel-build-all

Если в базе есть данные, которые нужно сохранить, перед пересборкой их надо бекаптровать:


>symfony propel-dump-data prod testdata.yml

>symfony cc

>symfony propel-build-all-load prod


Смотрим результат

http://localhost/prod_dev.php/photo.


sfGuardPlugin создает бюджет id: admin, password: admin.


Для создания новых пользователей, вызовите URL: http://localhost/prod_dev.php/sfGuardUser.


Для анонимных посетителей, необходимо добавить страницу


$symfony init-module prod public


Получим каталог public/ в apps/prod/modules/, содержащий:


actions/

config/

lib/

template/

validate/


Отредактируем apps/prod/modules/public/actions/actions.class.php.

// actions/actions.class.php

public function executeIndex()

{

$c = new Criteria();

$c->addDescendingOrderByColumn(PhotoPeer::CREATED_AT);

$this->photos = PhotoPeer::doSelect($c);

}


Эти три строки соответствуют SQL запросу:


SELECT * FROM photo ORDER BY created_at;


//в apps/prod/modules/public/templates/indexSuccess.php

<div id="main">

<h1>My pictures</h1>

<?php foreach($photos as $photo): ?>

<div class="photo">

<?php echo link_to(

image_tag('/uploads/thumbnail/'.$photo->getFilePath()),

'public/photo?id='.$photo->getId(),

'class=image title='.$photo->getDescription()

) ?>

"<?php echo $photo->getDescription() ?>"

on <?php echo $photo->getCreatedAt('d/m') ?>,

tagged <?php echo $photo->getTagsString() ?>

</div>

<?php endforeach; ?>

<div id="footer">

powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>

</div>

</div>


Если нелюходимо подключать стили в каждую страницу, необходимо изменить метод preExecute в actions.class.php:


// apps/prod/actions/actions.class.php

public function preExecute()

{

$this->getResponse()->addStylesheet('frontend');

}


http://localhost/prod_dev.php/public/index.


Для запрета вызывать изображения по их идентификатору, изменим executePhoto():


// apps/prod/actions/actions.class.php

public function executePhoto()

{

$photo = PhotoPeer::retrieveByPk($this->getRequestParameter('id'));

$this->forward404unless($photo);

$this->photo = $photo;

}


Создадим шаблон photoSuccess.php в кпталоге templates/ :


// apps/prod/modules/public/templates/photoSuccess.php

<div id="main">

<?php echo link_to('back to the photo list', 'public/index',

'style=display:block;float:right;') ?>

<h1>Picture details</h1>

<a href="/uploads/<?php echo $photo->getFilePath() ?>" title="click for the full-size version">

<?php echo image_tag('/uploads/'.$photo->getFilePath(), 'width=100%') ?>

</a><br/>

<p>

"<?php echo $photo->getDescription() ?>"

published on <?php echo $photo->getCreatedAt('d/m') ?>,

tagged <?php echo $photo->getTagsString() ?>

</p>

<div id="footer">

powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>

</div>

</div>


Создание фотоальбома

Создание фотоальбома


Спроектируем 3 таблицы



propel:

photo:

id: ~

file_path: varchar(50)

description: longvarchar

created_at: ~

tag:

photo_id: ~

name: varchar(50)

photocomment:

photo_id: ~

author: varchar(50)

body: varchar(50)

created_at: ~

Построим их


$symfony propel-build-all


Чистим кеш


$symfony clear-cache


Генерируем панель админа


$symfony propel-init-admin frontend photo Photo


смотрим


http://localhost/prod_dev.php/photo.


Редактируем представление


открываем generator.yml из apps/prod/modules/photo/config/ и добавляем следующий код:



generator:

class: sfPropelAdminGenerator

param:

model_class: Photo

theme: default

list:

display: [file_path, description, created_at]

object_actions:

_edit:

name: Edit picture properties


Для создания возможности выбора файла и навигащии по системе, модифицируем generator.yml:



generator:

class: sfPropelAdminGenerator

param:

model_class: Photo

theme: default

list:

display: [file_path, description, created_at]

object_actions:

_edit:

name: Edit picture properties

edit:

display: [file_path, description]

fields:

file_path:

type: admin_input_file_tag


Создаем механизм обработкм для выбранного файла. Вначале модифицируем схему, добавляя колонку _photo:



generator:

class: sfPropelAdminGenerator

param:

model_class: Photo

theme: default

list:

display: [_photo, description, created_at]

object_actions:

_edit:

name: Edit picture properties

edit:

display: [_photo, file_path, description]

fields:

file_path:

type: admin_input_file_tag


Создаем _photo.php в /apps/prod/modules/photo/templates следующего содержания:


<?php echo image_tag('/uploads/'.$photo->getFilePath()) ?>


Модифицируем модель


выполнив команду propel-build-all, мы генерирум два файла вкаталоге lib/model/ - Photo.php и PhotoPeer.php.Содержимое lib/model/Photo.php:


<?php

/**

* Subclass for representing a row from the 'photo' table.

*

*

*

* @package lib.model

*/

class Photo extends BasePhoto

{

}


Нам необходимо иметь возможность добавлять файлы и удалять файлы. Для этого модифицируем Photo class:


// in lib/model/Photo.php

class Photo extends BasePhoto

{

public function getTagsString()

{

$tags = array [9]();

foreach ($this->getTags() as $tag)

{

$tags[] = $tag->__toString();

}

return implode(' ', $tags);

}


public function setTagsString($tagPhrase)

{

// remove old tags

$this->deleteTags();


// set new tags

$tagNames = explode(' ', $tagPhrase);

foreach($tagNames as $tagName)

{

$tag = new Tag();

$tag->setPhoto($this);

$tag->setName($tagName);

$tag->save();

}

}


public function deleteTags()

{

$c = new Criteria();

$c->add(TagPeer::PHOTO_ID, $this->getId());

TagPeer::doDelete($c);

}

}


Здесь мы использовали метод __toString из класса Tag , который следут модифицировать:


// in lib/model/Tag.php

class Tag extends BaseTag

{

public function __toString()

{

return $this->getName();

}

}


Модифицируем модель :


// apps/prod/modules/photo/config/generator.yml

generator:

class: sfPropelAdminGenerator

param:

model_class: Photo

theme: default

list:

display: [_photo, description, tags_string, created_at]

object_actions:

_edit:

name: Edit picture properties

edit:

display: [_photo, file_path, description, tags_string]

fields:

file_path:

type: admin_input_file_tag

tags_string:

name: Tags

type: input_tag


 


суббота, 1 марта 2008 г.

Продолжаем строить blog

Сообщения и коментарии обычно располагают на одной странице. Это и нужно нам сделать.


Редактируем файл prod/apps/blog/modules/post/actions/actions.class.php и меняем метод executeShow():



public function executeShow()
{
$this->post = PostPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($this->post);

$c = new Criteria();
$c->add(CommentPeer::POST_ID, $this->getRequestParameter('id'));
$c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$this->comments = CommentPeer::doSelect($c);
}


Модифицирум шаблон для показа сообщений и коментариев prod/apps/blog/modules/post/templates/showSuccess.php:



...
<?php use_helper('Text', 'Date') ?>

<hr />
<?php if ($comments) : ?>
<p><?php echo count($comments) ?> comment
<?php if (count($comments) > 1) : ?>s
<?php endif; ?> to this post.</p>
<?php foreach ($comments as $comment): ?>
<p><em>posted by <?php echo $comment->getAuthor() ?> on
<?php echo format_date($comment->getCreatedAt()) ?>
</em></p>
<div class="comment" style="margin-bottom:10px;">
<?php echo simple_format_text($comment->getBody()) ?>
</div>
<?php endforeach; ?>
<?php endif; ?>



Проверяем:


http://localhost/blog_dev.php/post/show?id=1



При добавления коментария мы имеем возможность выбора соответствующего id сообщения, что не очень красиво.



Модифицируем modules/post/templates/showSuccess.php и добавим снизу:



<?php echo link_to('Add a comment', 'comment/create?post_id='.$post->getId()) ?>


Изменим в modules/comment/templates/editSuccess.php:



<tr>
<th>Post:</th>
<td><?php echo object_select_tag($comment, 'getPostId', array (
'related_class' => 'Post',
)) ?></td>
</tr>


на:

<?php if ($sf_params->has('post_id')): ?>
<?php echo input_hidden_tag('post_id',$sf_params->get('post_id')) ?>
<?php else: ?>
<tr>
<th>Post*:</th>
<td><?php echo object_select_tag($comment, 'getPostId',
array('related_class' => 'Post')) ?></td>
</tr>
<?php endif; ?>

Для атоматического редиректа форм при добавлении коментариев изменим в modules/comment/actions/actions.class.php метод executeUpdate():

public function executeUpdate ()
{
if (!$this->getRequestParameter('id', 0))
{
$comment = new Comment();
}
else
{
$comment = CommentPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($comment);
}

$comment->setId($this->getRequestParameter('id'));
$comment->setPostId($this->getRequestParameter('post_id'));
$comment->setAuthor($this->getRequestParameter('author'));
$comment->setEmail($this->getRequestParameter('email'));
$comment->setBody($this->getRequestParameter('body'));

$comment->save();

return $this->redirect('post/show?id='.$comment->getPostId());
}



Это уже похоже на веб дневник.