Symfony 3 入門
此為 Symfony 學習筆記,會持續更新
docker-compose 使用 laradock
https://github.com/laradock/laradock
但目前測試 docker 環境頗慢
Apache 2.4 + php7 設定
http://symfony.com/doc/current/setup/web_server_configuration.html
SetHandler proxy:fcgi://127.0.0.1:9000
與 php container 使用 link 方式的話,127.0.0.1 需改成 php container 名稱
SetHandler "proxy:fcgi://php-fpm:9000"
PHPStorm
安裝完 symfony plugin,到 Perference 的 symfony 開啟 Enable plugin in this Project
並修正下方兩個 cache 路徑,symfony 3 的快取路徑為 var/cache
Symfony Folder 搬移
composer.json "name"/var/cache/dev/appDevDebugProjectContainer.php.meta
/var/cache/dev/appDevDebugProjectContainer.xml
app/parameter 常數(含 .env 性質),可使用 %xxx% 引用該值
且 parameter 不會進入 git,若要提示開發者該文件架構,則寫到 yml.dist 中
config 多用 yml,Route、Entity 多用註解
官方 genus 範例 (production env)
http://knpuniversity.com/screencast/symfony/first-page
使用 bin/console debug:router --env=prod 可以看到 /genus 路徑,但卻得到 404
需使用 bin/console cache:clear --env=prod 清除 production 快取
app_dev.php 無法使用
dev預設在本地端使用,但 docker 都是 remote,所以需將 app_dev.php 中過濾 REMOTE_ADDR 那一行 mark 掉,或者加上 local ip
hook
Override twig
Override controller
Override form
系列 package
會有一個入口檔 XxxBundle.php,在要使用此 Bundle 時,須到 AppKernel 中的 registerBundles 註冊。
第三方 Bundle 都在 vendor,而此 Project 預設就有一個 AppBundle 在 src 中,如果該 Project 要做一個以上的 Bundle,可以將 app/ 等打包到 src/{somename}/abcBundle。而 app/ 為 global 性質
除了預設載入的 bundle 需先註冊,不常用的在 Controller 可使用 $this->get()
Symfony簡介
有三種環境(env),dev、prod、test,每個環境各自的設定都在 app/config 中app/parameter 常數(含 .env 性質),可使用 %xxx% 引用該值
且 parameter 不會進入 git,若要提示開發者該文件架構,則寫到 yml.dist 中
定義文件
有提供 yml, xml, php, 註解,四種定義方式,在 symfony 中用途很廣,config 多用 yml,Route、Entity 多用註解
官方 genus 範例 (production env)
http://knpuniversity.com/screencast/symfony/first-page
使用 bin/console debug:router --env=prod 可以看到 /genus 路徑,但卻得到 404
需使用 bin/console cache:clear --env=prod 清除 production 快取
app_dev.php 無法使用
dev預設在本地端使用,但 docker 都是 remote,所以需將 app_dev.php 中過濾 REMOTE_ADDR 那一行 mark 掉,或者加上 local ip
新手常見狀況
- The controller must return a response (null given)
- 須給 method 一個 return new Response('');
- 從 controller 取得 config.yml 參數值
- 似乎只能取設定檔中的 parameters: 內的參數
- $this->getParameter('參數名稱');
FOSUserBundle
安裝- app/config/parameters.yml 確定有填寫 mailer 相關參數
- 先在 記得建立 src/AppBundle/Entity/User.php
- 建立 table 若出現 Nothing to update,清一下 cache
hook
Override twig
- 方法一
- twig 檔案放置在 app/Resources/FOSUserBundle/views/
- 方法二
- twig 檔案放置在 myBundle/Resources/views/
- myBundle.php 加上 getParent()
public function getParent(){
return 'FOSUserBundle';
}
Override controller
- login 邏輯在 UserAuthenticationProvider/authenticate
Override form
- 先撰寫自己的 FormType 並看是否需繼承 parent
- 在 services.yaml 將服務掛載到 form.type 上
- 到 config 中的 fos_user 設定,registration 的 form.type 改用自己寫的
忘記密碼
- 大概流程
- 產生 hashcode
- 帶 hashcode 發 email
- 用網址帶的 HashCode 比對 2個欄位,來找出 User
- User->confirmationToken
- User->passwordRequestedAt
- hashcode 隨意
- request time 現在
- friendsofsymfony/user-bundle
- friendsofsymfony/rest-bundle
- 須 jms/serializer-bundle
- friendsofsymfony/jsrouting-bundle
- 只需要某個區塊的表單畫面 + controller功能
- 在 twig 使用 {{ render(controller()) }}
- 再去 override 該區塊的 twig 檔
- 需調整表單欄位,則繼承並修改該 FormType,並掛載
- 因無法改寫 ctrl 路徑,所以改用 ajax 送資料
- {{ app.user.xxx }} 想增加欄位
- 須在 user ODM 增加 serialize()、unserialize()
- 請參考 fos/user-bundle/Model/User.php
- 判斷有無登入
- PHP
- if($this->isGranted('IS_AUTHENTICATED_REMEMBERED'))
- TWIG
- {% if is_granted('IS_AUTHENTICATED_REMEMBERED') %}
Bundle 包
整個框架使用了很多 Bundle,每個 Bundle 會有自己的 config、controller、route、twig 等..會有一個入口檔 XxxBundle.php,在要使用此 Bundle 時,須到 AppKernel 中的 registerBundles 註冊。
第三方 Bundle 都在 vendor,而此 Project 預設就有一個 AppBundle 在 src 中,如果該 Project 要做一個以上的 Bundle,可以將 app/ 等打包到 src/{somename}/abcBundle。而 app/ 為 global 性質
除了預設載入的 bundle 需先註冊,不常用的在 Controller 可使用 $this->get()
建立 Bundle
php bin/console generate:bundle- 只需填寫 Bundle namespace,Ex. MyCompany/ProjectBundle
- 除了 config 格式要挑選,其他 Enter 跳過
- Symfony 自動建立 Bundle structure、更新 AppKernel、routing
AppBundle folder 更換位置/重新命名
用 Bundle 概念,AppBundle 是一整包,在 app/ 去 kernel 註冊、並注入 router 等...- 調整位置
- 更改 bundle 下,每隻 class 的 namesapce
- 如需要,更改 Bundle class 的檔名、ClassName
- 調整 app/AppKernel.php registerBundles()
- 調整 app/config/routing.yml 內的對應路徑
內部路徑寫法
- FolderMyBundle:Default:home.html.twig
- @FolderMy/Default/home.html.twig
Container 容器
裡面可以加掛很多Bundle(如外掛),並可隨處取用,而每個 Bundle 都有實作 Container,簡單說,App 就是個容器。 Laravel 也是如此。
- 新手常見狀況
- Call to a member function has() on null
- 需使用正規手法建立 controller, bin/console generate:controller
Doctrine
規定只能統一使用一種定義檔 (xml, yml, php, 註解)- 若平常使用 註解定義方式,在使用 generate:entity,選到 config format 如 yml,就會報錯
Entity 定義結構、儲存資料的實體,只能將內容讀出、存入
Repository 類似 MVC 的 M,可以撰寫對 DB 的操作
getManager() 可實際操作 DB
DQL 寫法
$query = $em->createQuery( 'SELECT p FROM AppBundle:Product p WHERE p.price > :price ORDER BY p.price ASC' )->setParameter('price', 19.99);$query->getResult()QueryBuilder,類似 ActiveRecode,簡單的查詢 method
$query = $repository->createQueryBuilder('p') ->where('p.price > :price') ->setParameter('price', '19.99') ->orderBy('p.price', 'ASC') ->getQuery();
$products = $query->getResult();
如同在 Repository 檔定義的 method,都是操作 db 的現成 mehod
Document
相當於 Laravel Model 檔,getter、setter 可用下方指令生成
php bin/console doctrine:mongodb:generate:documents xxxBundle
自訂流水號規則
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#identifier-generation-strategies
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#sequencegenerator
Relation
在真正呼叫 Entity 的 getter 之前,所得到的物件都是 ProxyEntity,這種叫做 Lazy Loading,與 Laravel 同樣。
當你要撈取 1對1 資料,原本寫法需 query 2 次。
在 Repository 加上,當撈取「第一個」Entity 時,就順便 join table2,第二個 Entity 就會因為已經有資料,而不會再 query 第二次。在 Laravel 帶 ->with() 有同等效果
One2Many
在雙邊建立彼此的 Attr (以自己的 Entity Name),並做好關聯
記得執行 schema:update --force
另外再執行 generate:entities AppBundle,
會自動幫雙方加上 getter,而 Many 端會多加 setter
Orm yml
relation + 2PKid:
myid:
type: uuid
id: true
user:
associationKey: true
manyToOne:
user:
targetEntity: User
cascade: { }
fetch: LAZY
joinColumns:
user_id:
referencedColumnName: user_id
orphanRemoval: false
Swiftmailer
寄信專用套件spool 為 mail Queue,有兩種 type
- memory 會阻塞並當下發送
- file 會先存到 %kernel.cache_dir%/swiftmailer/spool/*/xxx.message,
再觸發 swiftmailer:spool:send --message-limit=10 --env=dev 來發送,
所以需要在 crontab 設定 trigger
$ php bin/console swiftmailer:email:send
Controller
$message = (new \Swift_Message('your title'))
->setFrom('from email')
//->setFrom('$this->getParameter('mailer_user')', 'name')
->setTo('target email')
//->setTo(['[email protected]' => 'user1', '[email protected]' => 'user2',.. ])
->setBody('content');
$this->get('swiftmailer.mailer')->send($message);
Session
config.yml 定義1~2個參數後,若使用服務需另外定義 service 即可使用- handler_id
- Symfony\Component\HttpFoundation\Session\Storage\Handler
- 官方提供 Memcache(d)、mongodb、NativeFile、Pdo
- save_path
- file形式才要定義,通常在 /var/session
redis handler,使用 sncRedisBundle 套件,設定方法如下
http://stackoverflow.com/questions/30299091/sncredisbundle-not-working-what-am-i-doing-wrong
登入無法記住
- config.yml
- php.ini
framework:
session:
# handler_id set to null will use default session handler from php.ini
handler_id: ~
# save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
cookie_lifetime: 86400
gc_maxlifetime: 86400
; After this number of seconds, stored data will be seen as 'garbage' and
; cleaned up by the garbage collection process.
; http://php.net/session.gc-maxlifetime
session.gc_maxlifetime = 86400
constants
在 config.yamlparameters:
key:value
service container
將 物件 + config 包起來,就是服務,服務彼此又可互相注入如 Mailer 物件每次發送前都必須先給 config 才能發。
config 這邊指 arguments 參數,可以指定常數、其他服務,並從 construct or setter 注入,
產生一個容器物件
#定義服務
my_service_name: #隨意,也可以是 xxx.abc_def
class: MyBundle\MyClass
public: true
arguments: #MyClass 的建構子需傳入哪些參數
- '%env_arg_1%' #從常數拿
- '@my_service_name2' #定義第二個參數為某個 service
- '' #或者給空值
#arguments: ['aaa','bbb',['ccc']] #也可用這種寫法
calls:
- method: setfunction #還可指定對某個 setter function 注入
arguments:
- '@my_other_service'
#- [my_function, ['@my_other_service']] 或這樣寫
tags: #幫這個 service 貼標籤,同一個 name 就會關聯再一起
- { name: serv.type, alias: serv2 } #掛在 serv.type 標籤中,暱稱叫 serv2
# setter 同 consturct 注入
#my_service_name: #如果 service 定義太長,可分成兩段寫,只是 名稱、class 要相同
#使用服務 (controller 內)
$my_service = $this->get('my_service_name'); //get() 屬於 container 方法
Note:所以,若要在 service 中使用 logger,只要在 yml 定義 arguments: ['@logger'],即可在 construct 接收使用
service tags (Dependency Injection Tags)
上面 service 定義的 tags,應用層面在官方 doc 很難懂,其實簡單來說有一個 main_service 需連續注入同類型物件,就需寫一隻 Pass,把 tag 關聯的所有 service 塞到 main_service。該 Pass class 需到 AppBundle->build() 註冊
$ruleManager = $this->get('app.rule_manager');
$ruleManager->addRule(new IsNumericRule());
$ruleManager->addRule(new GreaterThanRule());
$ruleManager->addRule(new LessThanRule());
$data = $ruleManager->filterData($data);
最佳範例:link
而 symfony 使用這種設計,讓你可以自行實踐服務後,掛到指定組件的 tag 上
The DependencyInjection Component
檔案結構一般組成
- xxxCompilerPass
- xxxManager
- serviceInterface
- service xN
- services.yaml 定義
- xxxManager
- service1
- service 2...
- AppBundle->build() 增加
Listener
relation m2massetic-bundle
可以一次載入多個 js, css,並掛載 minify
- 可搭配 uglify-js 來 minify
http://symfony.com/doc/current/assetic/uglifyjs.html#minify-your-assets - 若發布時,沒有成功
- cache:clear --env=xxx
- assets:install
- assetic:watch
- 仍然不行,手動砍掉 web/js/*
- 檢查同一個 minify xxx.js,該清單是否存在其他頁,需同時修改
symfony 3.0 取得 user role
$this->container->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_REMEMBERED');
取得該頁面權限 (access_control)
//service.yml
arguments: ['@security.access_map']
//listener
public function __construct (AccessMap $map) {
}
//function
$accessMap = $this->map->getPatterns($event->getRequest());
if(in_array(['ROLE_USER'], $accessMap)){
//需登入的頁面
}
Bundle public resource (js,css ..)
要使用 myBundle/Resources/public/ 下的東西,需使用 php bin/console assets:install 指令,將檔案複製到 web/bundle 中。這種設計在打包成 composer 讓人使用時,可以自動將 第三方bundle 的 resource copy 到 web 中。
因為是 copy ,所以每次改檔案就需執行一次
MongoDB 4.4 (doctrine) in macOS 11 after
- 升級到 Xcode 13.1
- 用 brew 安裝
- brew install mongodb/brew/[email protected]
- 開啟 terminal 就執行
- echo 'export PATH="/usr/local/opt/[email protected]/bin:$PATH"' >> /Users/你的帳號/.bash_profile
- 安裝 pear
- curl -O https://pear.php.net/go-pear.phar
- php -d detect_unicode=0 go-pear.phar
- 選擇 1,輸入 /usr/local/pear
- 選擇 4,數入 /usr/local/pear
- Enter 到底
- sudo pecl channel-update pecl.php.net
- 安裝 php mongodb extension
- brew install autoconf
- #brew install php7.3-dev
- sudo pecl install mongodb
- https://kb.objectrocket.com/mongo-db/how-to-install-the-mongodb-driver-for-php-292
MongoDB (doctrine) in Mac
- 安裝 php extension
- brew install homebrew/php/php71-mongodb
- PHP 7 務必裝
composer require "alcaeus/mongo-php-adapter" - composer.json (注意 ext-mongo)
- 看 phpinfo() 是否出現 mongodb 區塊,或到 php.ini 加入 extension
- 或 php --ini查看
"provide": { "ext-mongo": "^1.6" },
"require": {
"doctrine/mongodb-odm": "^1.0",
"doctrine/mongodb-odm-bundle": "^3.0"
},
//sudo nano /etc/php.ini.default
extension = /usr/local/opt/php71-mongodb/mongodb.so
//or
extension = mongodb.so
MongoDB in ubuntu (apache2)
- 先確認使用哪一版本的 php
- apt-get install php7.1-mongodb
- sudo pecl install mongodb
- 沒裝 pecl, phpize 需照以下安裝
- apt-get install php7.1-dev
- nano /etc/php/7.1/apache2/php.ini
- /etc/init.d/apache2 restart
*此錯誤就是沒裝 mongo extension:
Attempted to load class "Manager" from namespace "MongoDB\Driver $dm = $this->get('doctrine_mongodb')->getManager();
//新增
$user = new User();
$user->setName('wild');
$dm->persist($user);
$dm->flush();
//更新
$user = $dm->getRepository('myBundle:User')->findOneBy(array('email'=>'[email protected]'));
$user->setName('wild000');
$dm->persist($user);
$dm->flush();
Native Query
$container = $this->getContainer();
$dm = $container->get('doctrine_mongodb')->getManager();
$conn = $dm->getConnection()->getMongo();
$collection = $conn->selectCollection('table_name', 'collection_name');
//取得 PHP MongoClient 物件,直接參考 PHP 官方文件
find http://php.net/manual/en/mongocollection.find.php
aggregate http://php.net/manual/en/mongocollection.aggregate.php
Event Dispatcher/Subscriber/EventObject
事件的組成- 事件名稱
- 有人去訂閱
- 事情發生時,有人去派發
- 這之間,會用 Event物件帶著相關資訊
目錄
- myBundle/Event/ 放Event物件
- myBundle/EventListener/ 放 EventSubscriber 物件
訂閱事件
有幾種方法?- service.yml
- listener->addTag
- $dispatcher->addSubscriber()
- 在 Subscriber 內的 getSubscribedEvents 定義
在 service.yml 內,掛載
tags:
- { name: 名稱?, event: 要訂閱哪一個事件, method: 事件觸發後,回傳的function }
再使用以下指令,可查看是否訂閱成功
$ php bin/console debug:event-dispatcher [事件名稱]
//載入 service
$dispatcher = $this->get('event_dispatcher');
//建立一個 extend 自 event class 的 物件
$event = new GetResponseUserEvent($user, $request);
//調度
$dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_INITIALIZE, $event);
twig 常用 (link)
- 完整路徑 {{ path(route_name {'paramName':value} ) }} # http://localhost/abc
- 絕對路徑 {{ url(route_name, {'paramName':value} ) }} # /abc
- 取得語系 {{ app.request.locale }}
- {%- -%} 片段(Ex. form_theme)
- 比對字串 {%if 'aaa' in 'aaabc' %}
- 比對陣列元素 {% if 1 in [1, 2, 3] %}
- 預設值 {{ val | default(預設值) }}
- 文字串接
- string+variable {{ asset('assets/img/''~ i ~'.jpg') }}
- Twig參數 {% set myVal = myVal | default('vvv') -%}
- logical
- {% for key,val in list %}
- {% for key,val in list.pics|default([]) %} //該 key 有存在才 foreach
- {% if online == false %}
- {% if list.pics is defined %} 該 key 有無定義
- 格式化
- 日期 {{ obj.lastDay|date('Y-m-d H:i:s') }}
- 數字 {{ 1002.3425|number_format(2, '.', ',') }} //四捨五入
- JSON給js JSON.parse('{{ data|json_encode|escape('js') }}');
- 關閉 Escape {{ 'have tag' | raw }}
- \n轉br {{ note | nl2br }}
- 切割 {{ 'aa,bb' | split(',') }}
- 合併 {{ arrdata | join(',') }}
- 數學
- 陣列長度 {{ array | length }}
- 運算 {{ array | length / 10 }}
- 判斷 {{ array | length > 5 }}
- embed
global vars (link)
- app.user
- 在 ctrl 呼叫,存於 session?
$ts = $this->get('security.token_storage');
$ts->getToken()->setUser($user);
- app.request.get('route_param')
- app.session.get('key')
自訂 twig 參數
- config.yml
- twig.globals.myVal:123
Language
首先,語系套件支援數種資料來源,只需給與 loader 就能解析該來源,目前支援 php陣列、php文件、csv、json、yaml...等
系統預設使用 yaml,而預設的語系檔名為 messages.xx.yml,無須手動 load
Language code (link) 使用 ISO 3166-1 alpha 2 例: fr_FR
前面語系+後面大寫國家
YML
page.menu.about: '關於'
page.footer.cp: '{0} © {1} Inc. - %slogan%'
security:
login:
username: '使用者帳號'
page.footer.counter1: '一個人 %user%|多個人 %user%'
page.footer.counter2: '{-1}從缺|{0}一個人|]1,5] 五人以下|[6,Inf[ 六人以上'
*注意:多餘的空格(尤其是斷行處),會引發 parser 錯誤*注意:經過 google 轉換的"空格",需全部複寫回正常的"空格"
變更語系檔沒反應:
1.清快取、重啟
2.將階層式寫法改成 aa.bb.cc
3.異動 twig 觸發更新
4.增加其他 lang key/value 觸發更新
4.等待...
1.清快取、重啟
2.將階層式寫法改成 aa.bb.cc
3.異動 twig 觸發更新
4.增加其他 lang key/value 觸發更新
4.等待...
PHP
//取得 translator 服務
$translator = $this->get('translator');
//基本用法
$str = $translator->trans('page.menu.about')
//指定讀取 login.yml 語系檔
$str = $translator->trans('security.login.username', array(), 'login')
//填充參數,有兩種表示方式
//%name%,會對照 key 去帶入
//{0}~{n},去除 %xx% 寫法,剩餘的參數會編號帶入 (可能有誤!)
$str = $translator->trans('page.footer.cp', array('%slogan%'=>'good day', 2016, 'google');
//指定備用語系
//若系統預設語系為 zh-TW,當該索引缺少翻譯詞,會依以下順序找該詞
//.zh-TW.yaml -> .en-PH -> .en-SG -> .en
$translator->setFallbackLocales(array('en-PH','en-SG'));
//依狀況選詞
$translator->transChoice('page.footer.counter1', 1, array('%user', 'aaa'))
//1=一個人 aaa
//0,2~n=多個人 aaa
$translator->transChoice('page.footer.counter2', 0)
//-1=從缺
//0=一個人
//2=五人以下
//6=六人以上
Twig
//使用指定的翻譯檔 ex.login.yml (message.yml 不要指定,系統預設已經載入)
{% trans_default_domain 'login' %}
//基本用法
{{ 'resetting.email.subject'|trans }}
//帶入參數
{{ 'resetting.email.message'|trans({'%username%': user.name, '%confirmationUrl%': confirmationUrl}) }}
//帶入含 html 的參數
{{ 'key' | trans({'%alink%':'<a href="">'}) | raw }}
//依狀況選詞
{{ 'page.footer.counter1' | transchoice(1, {'%user%':'aaa'}) }}
{{ 'page.footer.counter2' | transchoice(5) }}
//使用特定翻譯檔
{{ 'key' | trans({}, 'other_lang_file') }}
讓 User 自訂語系- 前端送 language code 近來
- config parameter 設定 support 清單
- route 設定過濾
- 存到 session, cookie 的 _locale
- 建立 LocaleListener,在 kernel Request 建立時,將 session 中的 _locale 蓋掉預設值 (link)
- 若成功,$request->getLocale() 會變化
- 要自動判斷前端 browser 語系,可在 listener 增加判斷
- 在 chrome -> 設定 -> 進階 -> 語言
- 要長期保留 user 設定,儲存/讀取自 db
parameters:
locale: en
app.locales: en|zh_TW|zh_CN
locale_supported: ['en','zh_TW','zh_CN']
chang_lang:
path: /change_lang/{_locale}
defaults: { _controller: xxx }
requirements:
_locale: '%app.locales%'
//LocaleListener
$request->getPreferredLanguage() //推薦語系,來自 $_SERVER['HTTP_ACCEPT_LANGUAGE']
- controller
- $request->getSession()->set('_locale', $_locale) //語系存入Session
- $request->setLocale('en') //設定目前語系
- $request->getLocale() //取得目前語系
- $request->cookies->has('_locale');
- $request->cookies->get('_locale'); $response->headers->setCookie(new Cookie('_locale', $_locale));
- twig
- {{app.request.locale}}
#PHP端
//取得 lang 檔清單
$this->get('translator')->getCatalogue('ZH_TW')->getDomains();
//取得指定 domain 下的所有翻譯
$this->get('translator')->getCatalogue('ZH_TW')->all('MyLang');
可在 route 開一個 zh_tw.js 的路徑,撰寫,讓前端可以動態呼叫取用
Asset
- 在 css 後加上版本號,跳脫 client 端 browser 快取
- 使用 version
assets:
version: 'v3' //使用 asset('') 時,就會自動加到後面
- 有些 CDN 會擋 ?xxx,所以必須改寫檔名
- 當使用此參數,務必在 web server config,將 asset 覆寫回來
Form
畫面上有多少個不同的表單,可能就有多少個 formType基本的表單製作流程
- 建立 dataObject (非必要)
- 若有設定,則 add() 的 child 必須與欄位名稱相同
- 建立 formType、language
- createForm、createView 到畫面上
- 渲染
- 撰寫 CURD
注意
- 單一欄位只會出現一次(重複呼叫無用)
此架構方便建立表單畫面、驗證、回傳、CURD,可以直接完成整個循環並重複利用
而定義 theme 來變化畫面元件樣式
另外並非每次都需要用到全套
而定義 theme 來變化畫面元件樣式
另外並非每次都需要用到全套
- 前端使用 jquery.form,讓 ajax 取代 submit
- isValid() 前,再將資料塞入驗證,或者不使用
- Valid 驗證 success 條件
- 也可搭配 render(controller('function_name')) 將整套獨立出來使用
{ form_start(form, { 'action': path('xxx'), 'attr': { 'class': 'xxx') }}
//全部
{{ form_widget(form) }}
//個別
{{ form_widget(form.plainPassword.first) }}
{{ form_widget(form.plainPassword.second, {'attr':{'class':'form-control', 'placeholder':'再次輸入'}}) }}
//Label (在 formType 中的 label 參數,)
{{ form_label(form.plainPassword.first, null, {'label_attr':{}}) }}
//整行渲染 #error + label +input
{{ form_row(form.name, {'label': 'foo'}) }} //列出後會自動從
//其他
{{ form_errors() }}
{{ form_rest() }}
{{ form_end(form) }}
Form 物件中,有 model、norm、view Data 三個階層Form Type
- TextType 文字Input
- CountryType 直接帶入國家清單
- 使用 symfony intl.region Component
- 語系位置vendor/symfony/symfony/src/Symfony/Component/Intl/Resources/data/regions/xxx.json
- 沒有 zh_TW 的 hack
public function buildForm(FormBuilderInterface $builder, array $options){
if(strtolower($options['locale']) === 'zh_tw'){
$options['locale'] = 'zh_Hant';
}
\Locale::setDefault($options['locale']);
}
- 這邊使用兩個參數(multiple、expanded),組合 4 種 UI
- false,false 下拉選單select
- true,false 展開的下拉選單select
- false,true 多個radio
- true,true 多個checkbox
- 注意 form_theme 內判斷錯誤,會導致無法顯示已勾選
//Form::class
//加入此 function
public function configureOptions(OptionsResolver $resolver){
$resolver->setDefaults(array(
'newparam' => ''
));
}
public function buildForm(FormBuilderInterface $builder, array $options){
var_dump($options['newparam']);
}
//controller
$form = $this->createForm(xxxForm::class, $obj, array(
'action'=>'url',
'newparam'=>'abc'
));
資料格式轉換 (用於 DB 資料無法直接配對 Form 元件格式)
//Form::class
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->get('欄位名稱')->addModelTransformer(new CallbackTransformer(
function ($fromDB) {
//轉換
return $toUI;
},
function ($fromUI) {
//轉換
return $toDB;
}
));
}
定義 Form_theme
目錄下有提供預設 Form twig,可以藉由 use 並覆蓋指定 block 來達到畫面客製化
vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form/
- bootstrap_3_horizontal_layout.html.twig
- bootstrap_3_layout.html.twig
- form_div_layout.html.twig
- form_table_layout.html.twig
- foundation_5_layout.html.twig
{% use "form_div_layout.html.twig" %} //或其他 layout 名稱
{% block form_label -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' control-label')|trim}) -%}
{{- parent() -}}
{%- endblock form_label %}
{% form_theme myFormObject '@MyBundle/../profileFields.html.twig' %}
//取 formType 中的參數
form.vars.id,form.vars.value,
form.vars.multiple,form.vars.expanded ....
增加與表單物件不相關的欄位
Upload File
流程:實作:
- Form Type 中,add FileType 類型 的欄位 ex.photo
- 到 ODM 實踐欄位
- 回傳
public function setPhoto(UploadedFile $file){
//在此可以 [move file 到 local 其他位置] or [上拋 storage(Call api)]
$file->getRealPath() //完整路徑
$file->getClientOriginalExtension() //原始副檔名
}
//Controller 端,取得 UploadFile 物件
$form['column_name']->getData(); //方法一
$request->files->get('this_form')['column_name']; //方法二
}
- 欄位設定為 FileType
- getter/setter IO 分別為 UploadedFile / File 物件
- 檔案存在遠端,無法產生 File 物件,需另外寫一隻 getPhotoPath() 之類的 function
Command
使用 service- extends ContainerAwareCommand
- 使用 $this->getContainer()->get('service-name')
//configure()
->addArgument('arg1', InputArgument::REQUIRED, '敘述')
->addArgument('arg2', InputArgument::OPTIONAL, '敘述')
->addArgument('arg5', InputArgument::OPTIONAL, '敘述')
//cmd
$ php bin/console my:cmd v1 v2 v3
//execute()
$input->getArgument('arg1'); //=v1
$input->getArgument('arg2'); //=v2
$input->getArgument('arg5'); //=v3
選項 Option(--o1 -o2)
//configure()
->addOption('debug', 'd', InputOption::VALUE_NONE, '敘述')
//execute()
$input->getOption('debug')
//cmd
$ php bin/console my:cmd --debug //接到 true
$ php bin/console my:cmd -d //同上,有設定縮寫
$ php bin/console my:cmd --debug=123 //接到 123(測試有問題)
效能
- 若要使用 by page 的 {% javascripts '' %} minify
- 不要將「會變動」的 php 資料 echo 在 js 裡面
- 用 debug=false ,可在 debug mode,將 combine 的 js 合併
Symfony 3 入門
Reviewed by Wild
on
4/25/2017 03:16:00 下午
Rating:
沒有留言:
沒有Google帳號也可發表意見唷!