Symfony 1.4 的部份讀書筆記


第 07 章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
模版以外使用 Helper
- sfProjectConfiguration::getActive()->loadHelpers($helpers)
- 如 TextHelper 則 $helpers = Text

模版中使用不是預設加載的幫助類
- <?php echo use_helper('Text', 'JavaScript') ?>

自己寫的 Helper 可以放於
- app/frontend/lib/helper
- lib/helper

默認佈局(Layout)
- templates/
- apps/frontend/templates/layout.php

模版中的變數
- $sf_context: 環境對象 (context object) (sfContext 物件)
- $sf_request: 請求對象 (sfRequest 物件)
- $sf_params : 請求的參數
- $sf_user : 當前用戶的 Session 對象 (sfUser 物件)

- $sf_request->getParameter('xxx')
- $sf_params->get('xxx')
- $this->getParameter('xxx') [用於 action 上]

局部文件 (Partials)
- 以 _ 開頭
- 載入用 include_partial 函數,省略 _ 和 .php
- 如在同一 Module 直接輸入模板名稱則可
- 否則需要使用 module_name/template_file_name
- 如果在全域中 global/template_file_name

- 傳送參數給局部文件可以通過
- include_partial(template_name, array('param1'=>value1, …))

- action 中調用
- $this->renderPartial('my_module/my_template_name');

組件 (Components)
- 存放於 action/components.class.php
- 和 action 一樣以 execute 開頭
- View 以 _my_component_execute_name.php 命名
- 即 executeHeadlines -> _headlines.php
- 模板中使用
- include_component(component_name, action_name)
- include_component(component_name, action_name, array('param1'=>value1,….))
- action 內 $this->param1
- 模版內 $param1

槽 (Slots)
- 用 include_slot(slot_name) 檢查是否定義了這個槽
- 可用 slot('slot_name') , end_slot() 定義 slot 區塊(block)
- 如
a.php
- include_slot('title')
b.php
- slot('title', 'this is a test')

View.yml
- apps/my_app/modules/my_modul/config/view.yml 只作用於自身模組的個別 action 覆蓋
- apps/my_app/modules/my_modul/config/view.yml 的 all 會對所有動作進行覆蓋
- 默認視圖文件
- apps/frontend/conf/view.yml
- action 中設置 meta, head 頭
- $this->getResponse()
- setContentType, setHttpHeader, setStatusCode(code, message)
- addMeta, setTitle, addStyleSheet, addJavaScript
- setCookie(name, content, expire, path, domain)

- view.yml
- set 是字串 ""
- add 是數組 [1, 2, 3]

- 標題 view.yml
- indeSuccess
- metas
- title: xxxxx
- stylesheet: [1, 2, 3]
- javascript: [script]

* [-main.js, main2.js] 則刪掉 main.js
* [-*] 則刪掉所掉
* [special: { position: first }] 則優先加載
* [http://main.js, paper:{ raw_name: true }] 直接使用 url

- action 中
- $this->getResponse()->addStylesheet('mystyle1');
- $this->getResponse()->addJavascript('myscript');
- $this->getResponse()->addJavascript('myscript', 'first'); 優先加載
- $this->getResponse()->addJavascript('myscript', '', array('raw_name' => true)); 直接使用 url
- 模版中
- use_stylesheet('xxx')
- use_javascript('xxx')
- use_javascript('xxx', 'first') 優先加載
- use_javascript('xxx', '', array('raw_name' => true)) 直接使用 url

- 指定布局文件
- indexSuccess
- layout: abc
- has_layout: false // 去掉布局

- $this->setLayout('abc') // action 中
- $this->setLayout(false) // 去掉布局

- decorate_with('abc') // 模板 中
- decorate_with(false) // 去掉布局

- XSS 安全
- frontend/config/settings.yml
- all:
- .settings:
- escaping_strategy: true
- escaping_method: ESC_SPECIALCHARS ****
- 取得過濾的資料
- $sf_data->get('test');
- $sf_data['test'];
- 未過濾資料
- $sf_data->getRaw('test');

****
- ESC_RAW: 不轉義
- ESC_SPECIALCHARS: htmlspecialchars()。
- ESC_ENTITIES: htmlentites() ENT_QUOTES
- ESC_JS: 將 HTML 轉義,使其可被放於 JavaScript 字串中
- ESC_JS_NO_ENTITIES:轉變成 JavaScript 中可使用的字串,如 alert

****
轉義後 $sf_user,$sf_request,$sf_param, $sf_context 也會被轉義
如果需要使用原數據,則取用 ESC_RAW 作傳遞

第 08 章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
生成模型
- php symfony cc (如找不到就先清理)
- php symfony doctrine:build-model

基礎: lib/model/doctrine/base (每次 build 將自動生成)
實際: lib/model/doctrine (改動則於此,build 不影響)

取數據
$article = new Article();
$article->setTitle('this is a test');
$acticle->getTitle();

$article->fromArray(array(
'title' => "this is a test"
'content' => "this is a content"
));

$article->save();

$article->getId(); // 取得 last_insert_id
$article->isNew();
$article->isModified();

$article = Doctrine_Core::getTable("article")->find(123);

--
$comment = new Comment();
$comment->setTitle("this is test");
$comment->setContent("this is content");
$comment->setArticle($article);

$comment->setArticleId($article->getId());
$comment->getArticle()->getTitle();

$article->getComments(); // 一篇文章對多篇回應

$comment->delete();

---
一般查詢
$q = Doctrine_Core::getTable('Article')->createQuery();
$articles = $q->execute();

$q = Doctrine_Core::getTable('Comment')->createQuery('c')
->where('c.author = ?', 'Steve')
->leftJoin('c.Article a')
->andWhere('a.content LIKE ?', '%enjoy%')
->orderBy('c.created_at ASC');
$comments = $q->execute();

Table 類的提供: findAll(), findBy*(), 和findOneBy*(), fetchOne()

---
PDO 的查詢

connection = Doctrine_Manager::connection();
$query = 'SELECT MAX(created_at) AS max FROM blog_article';
$statement = $connection->execute($query);
$statement->execute();
$resultset = $statement->fetch(PDO::FETCH_OBJ);
$max = $resultset->max;

---
Timestampable 自動設置 created_at 和 updated_at

schema.yml 可以分拆成多個文件
- config/doctrine/business-schema.yml
- config/doctrine/stats-schema.yml

分表.不同表用不同的 connection
1. 在 database.yml 中定義 doctrine 和 doctrine_bis
2. business-schema.yml 加入 connections: doctrine
3. stats-schema.yml 加入 connections: doctrine_bis

由資料庫中生成 schema.yml
php symfony doctrine:build-schema

第 9 章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
路由規則
- routing.yml

routing.yml
- article_by_title:
- url: articles/:subject/:year/:title.html
- param: { module: article, action: permalink }

請求: http://www.example.com/articles/finance/2010/activity-breakdown.html
得出:
'module' => 'article'
'action' => 'permalink'
'subject' => 'finance'
'year' => '2010'
'title' => 'activity-breakdown'

url_for(url) 函數會將內部 URL 轉化為外部 URL
link_to(label, url, array(class=>'', target='')) 會自動呼叫 url_for 轉換和處理
button_to(label, url)
from_tag(url)

apps/frontend/settings.yml 中的 no_script_name 控制 URL 顯示
- false 時則顯示 index.php/ 或 myapp_dev.php/
- true 則會隱藏
- true 時,則代表其不在測試/開發環境而是在 prod 生產/正式環境 ***

mod_rewrite
- 文件位於 myproject/web/.htaccess

檢查 post, get
- $this->request->isMethod('post')

link_to 為 post
- link_to(label, url, 'post=true')

除了 post 還有 encode 和 absolute
- encode 輸出無法識別的東西,主要用來加密 email
- absolute 輸出完整網址

取得/解釋 Rout URL
- http://myapp.example.com/article/21

- $routing = $this->getContext()->getRouting();

- $uri = $routing->getCurrentInternalUri();
> article/read?id=21

- $uri = $routing->getCurrentInternalUri(true);
> @article_by_id?id=21

- $rule = $routing->getCurrentRouteName();
> article_by_id

- $module = $this->getParameter('module');
- $action = $this->getParameter('action');

由外部 URL 轉換成內部 URL
- $uri = 'article/read?id=21';

- $url = $this->getController()->genUrl($uri);
> /article/21

- $url = $this->getController()->genUrl($uri, true);
> http://myapp.example.com/article/21

第 10 章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
action 中
---
$this->form = new sfForm();
$this->form->setWidgets(array(
'name' => new sfWidgetFormInputText(),
'email' => new sfWidgetFormInputText(array('default' => '[email protected]')),
'subject' => new sfWidgetFormChoice(array('choices' => array('Subject A', 'Subject B', 'Subject C'))),
'message' => new sfWidgetFormTextarea(),
));

模版中
<?php echo $form; ?>

如果要自定義
<?php echo $from['name']->renderRow(); ?>

如果需要改變 HTML 屬性
- 第一個參數是屬性
- 第二個參數是可選,替換標籤
<?php echo $form['name']->renderRow(array('size' => 25, 'class' => 'foo'), 'Your Name') ?>

可分開來顯示 (則不用 table 結構,自定義)
- render() (for the widget), renderError(), renderLabel(), and renderHelp()
- 即
<ul>
<?php foreach ($form as $field): ?>
<li>
<?php echo $field->renderLabel() ?>
<?php echo $field->render() ?>
</li>
<?php endforeach; ?>
</ul>
- 或
<ul>
<?php echo $form->renderUsing('list') ?>
</ul>

setWidget 設置
- $form->setWidget('full_name', new sfWidgetFormInput(array('default' => 'John Doe')));
>
<label for="full_name">Full Name</label>
<input type="text" name="full_name" id="full_name" value="John Doe" />

- $form->setWidget('address', new sfWidgetFormTextarea(array('default' => 'Enter your address here'), array('cols' => 20, 'rows' => 5)));
>
<label for="address">Address</label>
<textarea name="address" id="address" cols="20" rows="5">Enter your address here</textarea>

- $form->setWidget('pwd', new sfWidgetFormInputPassword());
>
<label for="pwd">Pwd</label>
<input type="password" name="pwd" id="pwd" />

- $form->setWidget('id', new sfWidgetFormInputHidden(array('default' => 1234)));
>
<input type="hidden" name="id" id="id" value="1234" />

- $form->setWidget('single', new sfWidgetFormInputCheckbox(array('value_attribute_value' => 'single', 'default' => true)));
>
<label for="single">Single</label>
<input type="checkbox" name="single" id="single" value="true" checked="checked" />


上傳文件組件和預覽
- $form->setWidget('picture', new sfWidgetFormInputFile());
>
<label for="picture">Picture</label>
<input id="picture" type="file" name="picture"/>

- $form->setWidget('picture', new sfWidgetFormInputFileEditable(array('default' => '/images/foo.png')));

驗證表單
$this->form->setValidators(array(
'name' => new sfValidatorString(),
'email' => new sfValidatorEmail(),
'message' => new sfValidatorString(array('min_length' => 4))
));

if ($request->isMethod('post')) {
$this->from->bind(/* 用戶提交數據 */);
if ($this->form->isValid()) {
xxx
}
}

用戶提交數據解決
- 使用 $this->form->setNameFormat('contact[%s]'); 定義表單
- 之後表單的 key value 就會被 contact 包裹成為 $contact 的陣列
- 則可以通過以下方法 bind,從而只取得表單而不是其他資料
$this->form->bind($request->getParameter('contact'));

顯示全局域錯誤
<?php if ($form->hasErrors()): ?>
The form has some errors you need to fix.
<?php endif; ?>

使用驗證器必須每個欄位都定義,如果是可選,可設置 required => false
$this->form->setValidators(array(
'name' => new sfValidatorString(),
'email' => new sfValidatorEmail(array('required' => false)),
'message' => new sfValidatorString(array('min_length' => 4))
));

驗證兩個條件以上,可用 sfValidatorAnd / sfValidatorOr
$this->form->setValidators(array(
'name' => new sfValidatorString(),
'email' => new sfValidatorAnd(
new sfValidatorEmail(),
new sfValidatorString(array('min_length' => 4)),
),
'message' => new sfValidatorString(array('min_length' => 4))
));

自定義錯誤信息
'email' => new sfValidatorEmail(
array(),
array(
'required' => "Please enter email",
'invalid' => "Please enter a valid email address"
)
),
'message' => new sfValidaorString(
array('min_length' => 4),
array(
'required' => "Please enter message",
'min_length' => "Please enter a longer message at least 4 char"
)
),

驗證兩次輸入的密碼
$this->form = new sfForm();
$this->form->setWidgets(array(
'login' => new sfWidgetFormInputText(),
'password1' => new sfWidgetFormInputText(),
'password2' => new sfWidgetFormInputText()
);
$this->form->setValidators(array(
'login' => new sfValidatorString(), // login is required
'password1' => new sfValidatorString(), // password1 is required
'password2' => new sfValidatorString(), // password2 is required
));
$this->form->setPostValidators(new sfValidatorSchemaCompare('password1', '==', 'password2'));

驗證下拉式選單中的資料是否存在於資料庫中
$form->setValidator('section_id', new sfValidatorDoctrineChoice(array(
'model' => 'Section',
'column' => 'name'
)));

驗證此用戶名是否已經存在
$form->setValidator('nickname', new sfValidatorDoctrineUnique(array(
'model' => 'User',
'column' => 'login'
)));

CSRF 保護
- 設置密碼
$form->addCSRFProtection('flkd445rvvrGV34G');

- settings.yml 中設置整個網站保護
all
.settings:
csrf_secret: ##…##

*** 最好是將 from 儲存為 lib/form/ContactForm.class.php
*** 這樣就可以簡單的讀取和改變內裡的資料 (如: setWidget / setValidator)

生成表單
php symfony doctrine:build-forms

第 11 章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
調用 mailer
- $mailer = sfContext::getInstance()->getMailer();

發送郵件
$this->getMailer()->composeAndSend(from_email, to_email, subject, content);

發送群組電郵
$to_email = array(
'email' => 'name',
'email' => 'name',
);
$this->getMailer()->composeAndSend(from_email, $to_email, subject, content);

建立消息對象,發送郵件
$message = $this->getMailer()
->compose(from_email, to_email, subject, content)
->attach(Swift_Attachment::fromPath('/path/to/a/file.zip'));
$this->getMailer()->send($message);

利用視圖
$message->setBody($this->getPartial('partial_name', $arguments));

配置文件
factories.yml

改變下次發送情況為即刻發送
$this->getMailer()->sendNextImmediately()->send($message);

transport 的 class 類別
- Swift_SmtpTransport
- Swift_SendmailTransport
- Swift_MailTransport
* http://swiftmailer.org/docs/transport-types

使用 Gmail
transport:
class: Swift_SmtpTransport
param:
host: smtp.gmail.com
port: 465
encryption: ssl
username: your_gmail_username_goes_here
password: your_gmail_password_goes_here

第 12 章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
清除緩存
$cacheManager = sfContext::getInstance()->getViewCacheManager();
$cacheManager->remove('publication/list');

取得 template 路徑
sfConfig::get(’sf_template_cache_dir’)

後台不能夠刪除前台的緩存
$frontend_cache_dir = sfConfig::get('sf_cache_dir').DIRECTORY_SEPARATOR.'frontend'. DIRECTORY_SEPARATOR.sfConfig::get('sf_environment').DIRECTORY_SEPARATOR.'template';
$cache = new sfFileCache(array('cache_dir' => $frontend_cache_dir)); // 使用前台factories.yml文件中相同的设置。
$cache->removePattern('user/show?id=12');

如果是 memcache 的情況
$cache = new sfMemcacheCache(array('prefix' => 'frontend'));
$cache->removePattern('user/show?id=12');

第 13 章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
設置語系
frontend/config/i18n.yml
all:
default_culture: fr_FR

$this->getUser()->setCulture('en_US');
$this->getUser()->getCulture();

$sf_user->setCulture('en_US');

取得預設語系
$languages = $request->getLanguages();

取得結構化時間
list($d, $m, $y) = $this->getContext()->getI18N()->getDateForCulture($date, $user_culture);

按模板分割
- modules/[my_module]/i18n/message.xx.xml
- php symfony i18n:extract frontend en

在原字典上追加字符串
php symfony i18n:extract --auto-save frontend en

自動刪除舊的字符串
php symfony i18n:extract --auto-save --auto-delete frontend en

拆分較好方法
<?php echo __('There are %1% persons logged', array('%1%' => count_logged())) ?>

在 action 中呼叫
$this->getContext()->getI18N()->__($text, $args, 'messages');

第 14 章

1
2
3
4
5
初始化管理界面
php symfony propel:generate-admin backend BlogArticle --module=article

如沒看見圖片
php symfony plugin:publish-assets