propel:
_attributes: { noXsd: false, defaultIdMethod: none, package: lib.model }
ask_question:
_attributes: { phpName: Question, idMethod: native }
id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
user_id: { type: integer, foreignTable: ask_user, foreignReference: id }
title: { type: longvarchar }
body: { type: longvarchar }
created_at: ~
updated_at: ~
ask_answer:
_attributes: { phpName: Answer, idMethod: native }
id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
question_id: { type: integer, foreignTable: ask_question, foreignReference: id }
user_id: { type: integer, foreignTable: ask_user, foreignReference: id }
body: { type: longvarchar }
created_at: ~
ask_user:
_attributes: { phpName: User, idMethod: native }
id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
nickname: { type: varchar(50), required: true, index: true }
first_name: varchar(100)
last_name: varchar(100)
created_at: ~
ask_interest:
_attributes: { phpName: Interest, idMethod: native }
question_id: { type: integer, foreignTable: ask_question, foreignReference: id, primaryKey: true }
user_id: { type: integer, foreignTable: ask_user, foreignReference: id, primaryKey: true }
created_at: ~
ask_relevancy:
_attributes: { phpName: Relevancy, idMethod: native }
answer_id: { type: integer, foreignTable: ask_answer, foreignReference: id, primaryKey: true }
user_id: { type: integer, foreignTable: ask_user, foreignReference: id, primaryKey: true }
score: { type: integer }
created_at: ~
symfony propel-build-model
symfony propel-build-sql
mysql -u youruser -p prod < data/sql/lib.model.schema.sql
symfony propel-insert-sql
symfony propel-generate-crud frontend question Question
symfony cc frontend config
http://prod/question
Шаблон
В apps/frontend/templates/layout.php) меняем:
<!DOCTYPE html PUBLIC "-//W3C//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="header">
<ul>
<li><?php echo link_to('about', '@homepage') ?></li>
</ul>
<h1><?php echo link_to(image_tag('prod_logo.gif', 'alt=prod'), '@homepage') ?></h1>
</div>
<div id="content">
<div id="content_main">
<?php echo $sf_data->getRaw('sf_content') ?>
<div class="verticalalign"></div>
</div>
<div id="content_bar">
<!-- Nothing for the moment -->
<div class="verticalalign"></div>
</div>
</div>
</body>
</html>
Добавим стили в frontend/config/view.yml:
stylesheets: [main, layout]
Переопределим default homepage
prod/apps/frontend/config/routing.yml:
homepage:
url: /
param: { module: question, action: list }
Определим данные для теста
Создать prod/data/fixtures/
Создать test_data.yml:
User:
anonymous:
nickname: anonymous
first_name: Anonymous
last_name: Coward
fabien:
nickname: fabpot
first_name: Fabien
last_name: Potencier
francois:
nickname: francoisz
first_name: François
last_name: Zaninotto
Question:
q1:
title: What shall I do tonight with my girlfriend?
user_id: fabien
body: |
We shall meet in front of the Dunkin'Donuts before dinner,
and I haven't the slightest idea of what I can do with her.
She's not interested in programming, space opera movies nor insects.
She's kinda cute, so I really need to find something
that will keep her to my side for another evening.
q2:
title: What can I offer to my step mother?
user_id: anonymous
body: |
My stepmother has everything a stepmother is usually offered
(watch, vacuum cleaner, earrings, del.icio.us account).
Her birthday comes next week, I am broke, and I know that
if I don't offer her something sweet, my girlfriend
won't look at me in the eyes for another month.
q3:
title: How can I generate traffic to my blog?
user_id: francois
body: |
I have a very swell blog that talks
about my class and mates and pets and favorite movies.
Interest:
i1: { user_id: fabien, question_id: q1 }
i2: { user_id: francois, question_id: q1 }
i3: { user_id: francois, question_id: q2 }
i4: { user_id: fabien, question_id: q2 }
Batch skeleton
Создать файл load_data.php в prod/batch/:
<?php
define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..'));
define('SF_APP', 'frontend');
define('SF_ENVIRONMENT', 'dev');
define('SF_DEBUG', true);
require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
// initialize database manager
$databaseManager = new sfDatabaseManager();
$databaseManager->initialize();
?>
Импорт данных
Добавить код до конца?> в prod/batch/load_data.php :
$data = new sfPropelData();
$data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures');
$ cd /home/sfprojects/prod/batch
$ php load_data.php
Проверяем:
http://prod/frontend_dev.php
Доступ к данным
Метод executeList() в prod/apps/frontend/modules/question/actions/action.class.php и шаблон
prod/apps/frontend/modules/question/templates/listSuccess.php:
actions.class.php:
public function executeList ()
{
$this->questions = QuestionPeer::doSelect(new Criteria());
}
listSuccess.php:
...
<?php foreach ($questions as $question): ?>
<tr>
<td><?php echo link_to($question->getId(), 'question/show?id='.$question->getId()) ?></td>
<td><?php echo $question->getTitle() ?></td>
<td><?php echo $question->getBody() ?></td>
<td><?php echo $question->getCreatedAt() ?></td>
<td><?php echo $question->getUpdatedAt() ?></td>
</tr>
<?php endforeach; ?>
Изменим шаблон question/list
В файле listSuccess.php из prod/apps/frontend/modules/question/templates/ изменим:
<?php use_helper('Text') ?>
<h1>popular questions</h1>
<?php foreach($questions as $question): ?>
<div class="question">
<div class="interested_block">
<div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
<?php echo count($question->getInterests()) ?>
</div>
</div>
<h2><?php echo link_to($question->getTitle(), 'question/show?id='.$question->getId()) ?></h2>
<div class="question_body">
<?php echo truncate_text($question->getBody(), 200) ?>
</div>
</div>
<?php endforeach; ?>
http://prod/frontend_dev.php/
Просмотр
http://prod/frontend_dev.php/question/show/id/2
prod/apps/frontend/modules/question/actions/actions.class.php :
public function executeShow()
{
$this->question = QuestionPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404Unless($this->question);
}
Модификация showSuccess.php
frontend/modules/question/templates/showSuccess.php:
<?php use_helper('Date') ?>
<div class="interested_block">
<div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
<?php echo count($question->getInterests()) ?>
</div>
</div>
<h2><?php echo $question->getTitle() ?></h2>
<div class="question_body">
<?php echo $question->getBody() ?>
</div>
<div id="answers">
<?php foreach ($question->getAnswers() as $answer): ?>
<div class="answer">
posted by <?php echo $answer->getUser()->getFirstName().' '.$answer->getUser()->getLastName() ?>
on <?php echo format_date($answer->getCreatedAt(), 'p') ?>
<div>
<?php echo $answer->getBody() ?>
</div>
</div>
<?php endforeach; ?>
</div>
Добавим данные answer и relevancy в data/fixtures/test_data.yml:
Answer:
a1_q1:
question_id: q1
user_id: francois
body: |
You can try to read her poetry. Chicks love that kind of things.
a2_q1:
question_id: q1
user_id: fabien
body: |
Don't bring her to a donuts shop. Ever. Girls don't like to be
seen eating with their fingers - although it's nice.
a3_q2:
question_id: q2
user_id: fabien
body: |
The answer is in the question: buy her a step, so she can
get some exercise and be grateful for the weight she will
lose.
a4_q3:
question_id: q3
user_id: fabien
body: |
Модификация модели
prod/lib/model/User.php:
public function __toString()
{
return $this->getFirstName().' '.$this->getLastName();
}
prod/apps/frontend/modules/question/templates/showSuccess.php
posted by <?php echo $answer->getUser() ?>
файл showSuccess.php:
<div class="interested_block">
<div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
<?php echo count($question->getInterests()) ?>
</div>
</div>
создадим _interested_user.php файл в prod/apps/frontend/modules/question/templates/:
<div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
<?php echo count($question->getInterests()) ?>
</div>
Изменим код в listSuccess.php и showSuccess.php) на:
<div class="interested_block">
<?php include_partial('interested_user', array('question' => $question)) ?>
</div>
Добавим поле User
$ symfony propel-build-ысруьф
$ symfony propel-сщтмуке-ньд-ысруьф
В prod/config/schema.xml добавим ask_question :
<column name="interested_users" type="integer" default="0" />
propel:
_attributes:
defaultIdMethod: native
noxsd: true
ask_question:
_attributes:
phpName: Question
id:
type: integer
required: true
primaryKey: true
autoIncrement: true
user_id:
type: integer
foreignTable: ask_user
foreignReference: id
title:
type: longvarchar
body:
type: longvarchar
created_at:
type: timestamp
updated_at:
type: timestamp
interested_users:
type: integer
default: 0
stripped_title:
type: varchar
size: 255
_uniques:
unique_stripped_title:
- stripped_title
ask_answer:
_attributes:
phpName: Answer
id:
type: integer
required: true
primaryKey: true
autoIncrement: true
question_id:
type: integer
foreignTable: ask_question
foreignReference: id
user_id:
type: integer
foreignTable: ask_user
foreignReference: id
body:
type: longvarchar
created_at:
type: timestamp
relevancy_up:
type: integer
default: 0
relevancy_down:
type: integer
default: 0
ask_user:
_attributes:
phpName: User
id:
type: integer
required: true
primaryKey: true
autoIncrement: true
nickname:
type: varchar
size: 50
first_name:
type: varchar
size: 100
last_name:
type: varchar
size: 100
created_at:
type: timestamp
ask_interest:
_attributes:
phpName: Interest
question_id:
type: integer
primaryKey: true
foreignTable: ask_question
foreignReference: id
user_id:
type: integer
primaryKey: true
foreignTable: ask_user
foreignReference: id
created_at:
type: timestamp
ask_relevancy:
_attributes:
phpName: Relevancy
answer_id:
type: integer
primaryKey: true
foreignTable: ask_answer
foreignReference: id
user_id:
type: integer
primaryKey: true
foreignTable: ask_user
foreignReference: id
score:
type: integer
created_at:
type: timestamp
Перестроим модель:
$ symfony propel-build-model
$ symfony propel-build-sql
$ mysql -u youruser -p prod < data/sql/lib.model.schema.sql
$ php batch/load_data.php
$ mysql -u youruser -p prod -e "alter table ask_question add interested_users int default '0'"
Модифицируем метод save() объекта Interest
prod/lib/model/Interest.php:
public function save($con = null)
{
$ret = parent::save($con);
// update interested_users in question table
$question = $this->getQuestion();
$interested_users = $question->getInterestedUsers();
$question->setInterestedUsers($interested_users + 1);
$question->save($con);
return $ret;
}
Для зашиты от обновления при транзакциях:
public function save($con = null)
{
$con = Propel::getConnection();
try
{
$con->begin();
$ret = parent::save($con);
// update interested_users in question table
$question = $this->getQuestion();
$interested_users = $question->getInterestedUsers();
$question->setInterestedUsers($interested_users + 1);
$question->save($con);
$con->commit();
return $ret;
}
catch (Exception $e)
{
$con->rollback();
throw $e;
}
}
изменим template
в _interested_user.php заменим:
<?php echo count($question->getInterests()) ?>
на
<?php echo $question->getInterestedUsers() ?>
.
- Добавим answer в schema.xml
<column name="relevancy_up" type="integer" default="0" />
<column name="relevancy_down" type="integer" default="0" />
Перестроим модель
$ symfony propel-build-model
$ symfony propel-build-sql
$ mysql -u youruser -p prod < data/sql/lib.model.schema.sql
- Заменим метод->save()в классе Relevancy в lib/model/Relevancy.php
public function save($con = null)
{
$con = Propel::getConnection();
try
{
$con->begin();
$ret = parent::save();
// update relevancy in answer table
$answer = $this->getAnswer();
if ($this->getScore() == 1)
{
$answer->setRelevancyUp($answer->getRelevancyUp() + 1);
}
else
{
$answer->setRelevancyDown($answer->getRelevancyDown() + 1);
}
$answer->save($con);
$con->commit();
return $ret;
}
catch (Exception $e)
{
$con->rollback();
throw $e;
}
}
Добавим Answer class в модель:
public function getRelevancyUpPercent()
{
$total = $this->getRelevancyUp() + $this->getRelevancyDown();
return $total ? sprintf('%.0f', $this->getRelevancyUp() * 100 / $total) : 0;
}
public function getRelevancyDownPercent()
{
$total = $this->getRelevancyUp() + $this->getRelevancyDown();
return $total ? sprintf('%.0f', $this->getRelevancyDown() * 100 / $total) : 0;
}
Изменим question/templates/showSuccess.php на:
<div id="answers">
<?php foreach ($question->getAnswers() as $answer): ?>
<div class="answer">
<?php echo $answer->getRelevancyUpPercent() ?>% UP <?php echo $answer->getRelevancyDownPercent() ?> % DOWN
posted by <?php echo $answer->getUser()->getFirstName().' '.$answer->getUser()->getLastName() ?>
on <?php echo format_date($answer->getCreatedAt(), 'p') ?>
<div>
<?php echo $answer->getBody() ?>
</div>
</div>
<?php endforeach; ?>
</div>
Добавим данные для тестирования в fixtures
Relevancy:
rel1:
answer_id: a1_q1
user_id: fabien
score: 1
rel2:
answer_id: a1_q1
user_id: francois
score: -1
Альтернативная версия заголовка
в schema.xml добавим:
<column name="stripped_title" type="varchar" size="255" />
<unique name="unique_stripped_title">
<unique-column name="stripped_title" />
</unique>
Перестроим модель:
$ symfony propel-build-model
$ symfony propel-build-sql
$ mysql -u youruser -p prod < data/sql/lib.model.schema.sql
Custom class
создаем myTools.class.php в prod/lib/:
<?php
class myTools
{
public static function stripText($text)
{
$text = strtolower($text);
// strip all non word chars
$text = preg_replace('/\W/', ' ', $text);
// replace all white space sections with a dash
$text = preg_replace('/\ +/', '-', $text);
// trim dashes
$text = preg_replace('/\-$/', '', $text);
$text = preg_replace('/^\-/', '', $text);
return $text;
}
}
в prod/lib/model/Question.php добавим:
public function setTitle($v)
{
parent::setTitle($v);
$this->setStrippedTitle(myTools::stripText($v));
}
Перегружаем изменения:
$ symfony cc
$ php batch/load_data.php
Меняем ссылки на действия show
в listSuccess.php строку
<h2><?php echo link_to($question->getTitle(), 'question/show?id='.$question->getId()) ?></h2>
Меняем на
<h2><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>
в actions.class.php:
public function executeShow()
{
$c = new Criteria();
$c->add(QuestionPeer::STRIPPED_TITLE, $this->getRequestParameter('stripped_title'));
$this->question = QuestionPeer::doSelectOne($c);
$this->forward404Unless($this->question);
}
http://prod/frontend_dev.php/
Меняем routing правила
routing.yml в prod/apps/frontend/config/:
question:
url: /question/:stripped_title
param: { module: question, action: show }
http://prod/frontend_dev.php/question/what-shall-i-do-tonight-with-my-girlfriend