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

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

安裝
  1. app/config/parameters.yml 確定有填寫 mailer 相關參數
  2. 先在 記得建立 src/AppBundle/Entity/User.php
  3. 建立 table 若出現 Nothing to update,清一下 cache


hook
Override twig
  1. 方法一
    1. twig 檔案放置在 app/Resources/FOSUserBundle/views/
  2. 方法二
    1. twig 檔案放置在 myBundle/Resources/views/
    2. myBundle.php 加上 getParent()
    3. public function getParent(){
          return 'FOSUserBundle';
      }

Override controller

  • login 邏輯在 UserAuthenticationProvider/authenticate

Override form
  1. 先撰寫自己的 FormType 並看是否需繼承 parent
  2. 在 services.yaml 將服務掛載到 form.type 上
  3. 到 config 中的 fos_user 設定,registration 的 form.type 改用自己寫的
忘記密碼
  1. 大概流程
    1. 產生 hashcode
    2. 帶 hashcode 發 email
    3. 用網址帶的 HashCode 比對 2個欄位,來找出 User
      1. User->confirmationToken
      2. User->passwordRequestedAt
  2. hashcode 隨意
  3. request time 現在
系列 package
  • 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
  1. 只需填寫 Bundle namespace,Ex. MyCompany/ProjectBundle
  2. 除了 config 格式要挑選,其他 Enter 跳過
  3. Symfony 自動建立 Bundle structure、更新 AppKernel、routing

AppBundle folder 更換位置/重新命名

用 Bundle 概念,AppBundle 是一整包,在 app/ 去 kernel 註冊、並注入 router 等...
  1. 調整位置
  2. 更改 bundle 下,每隻 class 的 namesapce
  3. 如需要,更改 Bundle class 的檔名、ClassName
  4. 調整 app/AppKernel.php registerBundles()
  5. 調整 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 + 2PK
id:
    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
Cmd (測試 mailer 是否有通)
$ 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
pdo handler,須在 services 添加 session.handler.pdo,並在 database 手動建立 session table
redis handler,使用 sncRedisBundle 套件,設定方法如下
http://stackoverflow.com/questions/30299091/sncredisbundle-not-working-what-am-i-doing-wrong

登入無法記住
  1. config.yml
  2. 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
    
  3. php.ini
  4. ; 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.yaml
parameters:
    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() 註冊
當出現以下寫法,你可能就需要寫一個 CompilerPass
$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 m2m

assetic-bundle
可以一次載入多個 js, css,並掛載 minify
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

  1. 升級到  Xcode 13.1
  2. 用 brew 安裝
    1. brew install mongodb/brew/[email protected]
  3. 開啟 terminal 就執行
    1. echo 'export PATH="/usr/local/opt/[email protected]/bin:$PATH"' >> /Users/你的帳號/.bash_profile
  4. 安裝 pear
    1. curl -O https://pear.php.net/go-pear.phar
    2. php -d detect_unicode=0 go-pear.phar
    3. 選擇 1,輸入 /usr/local/pear
    4. 選擇 4,數入 /usr/local/pear
    5. Enter 到底
    6. sudo pecl channel-update pecl.php.net
  5. 安裝 php mongodb extension
    1. brew install autoconf
    2. #brew install php7.3-dev
    3. sudo pecl install mongodb
    4. https://kb.objectrocket.com/mongo-db/how-to-install-the-mongodb-driver-for-php-292

MongoDB (doctrine) in Mac

  1. 安裝 php extension
    1. brew install homebrew/php/php71-mongodb
  2. PHP 7 務必裝
    composer require "alcaeus/mongo-php-adapter" 
  3. composer.json (注意 ext-mongo)
  4. "provide": { "ext-mongo": "^1.6" },
    "require": {
        "doctrine/mongodb-odm": "^1.0",
        "doctrine/mongodb-odm-bundle": "^3.0"
    },
    
  5. 看 phpinfo() 是否出現 mongodb 區塊,或到 php.ini 加入 extension
    1. 或 php --ini查看
    //sudo nano /etc/php.ini.default
    extension = /usr/local/opt/php71-mongodb/mongodb.so
    //or
    extension = mongodb.so

MongoDB in ubuntu (apache2)

  1. 先確認使用哪一版本的 php
  2. apt-get install php7.1-mongodb
  3. sudo pecl install mongodb
    1. 沒裝 pecl, phpize 需照以下安裝
    2. apt-get install php7.1-dev
  4. nano /etc/php/7.1/apache2/php.ini
  5. /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

事件的組成

  1. 事件名稱
  2. 有人去訂閱
  3. 事情發生時,有人去派發
  4. 這之間,會用 Event物件帶著相關資訊

目錄

  1. myBundle/Event/ 放Event物件
  2. myBundle/EventListener/ 放 EventSubscriber 物件
訂閱事件
有幾種方法?

  1. service.yml
  2. listener->addTag
  3. $dispatcher->addSubscriber()
  4. 在 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
    • app.request.get('route_param')
  • app.session
    • app.session.get('key')
  • app.environment
  • app.debug
自訂 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.等待...

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 自訂語系
getLocale() 資料來自 session,但登出後 session 將被清掉,建議將 cookie 作為儲存的手段
  1. 前端送 language code 近來
    1. config parameter 設定 support 清單
    2. parameters:
          locale: en
          app.locales: en|zh_TW|zh_CN
          locale_supported: ['en','zh_TW','zh_CN']
      
    3. route 設定過濾
    4. chang_lang:
          path:     /change_lang/{_locale}
          defaults: { _controller: xxx }
          requirements: 
              _locale: '%app.locales%'
      
  2. 存到 session, cookie 的 _locale
  3. 建立 LocaleListener,在 kernel Request 建立時,將 session 中的 _locale 蓋掉預設值 (link)
  4. 若成功,$request->getLocale() 會變化
  5. 要自動判斷前端 browser 語系,可在 listener 增加判斷
    1. 在 chrome -> 設定 -> 進階 -> 語言
    //LocaleListener
    $request->getPreferredLanguage() //推薦語系,來自 $_SERVER['HTTP_ACCEPT_LANGUAGE']
    
  6. 要長期保留 user 設定,儲存/讀取自 db
  • 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}}
將 translations 轉成 json object
#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('') 時,就會自動加到後面
      
    • version_format
      • 有些 CDN 會擋 ?xxx,所以必須改寫檔名
      • 當使用此參數,務必在 web server config,將 asset 覆寫回來
    • version_strategy (link)
  • 2

Form

畫面上有多少個不同的表單,可能就有多少個 formType
基本的表單製作流程
  1. 建立 dataObject (非必要)
    1. 若有設定,則 add() 的 child 必須與欄位名稱相同
  2. 建立 formType、language
  3. createForm、createView 到畫面上
  4. 渲染
  5. 撰寫 CURD
注意
  • 單一欄位只會出現一次(重複呼叫無用)
此架構方便建立表單畫面、驗證、回傳、CURD,可以直接完成整個循環並重複利用
而定義 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']);
      }
      
  • BirthdayType 生日,自動帶 type=date,前端自動檢查,但 max 要用 html5 屬性來限制
  • EmailType 信箱,自動帶 type=email,前端自動檢查
  • UrlType 信箱,自動帶 type=url,前端自動檢查
  • FileType 檔案上傳,操作流程請見 Upload
  • CheckboxType 單選勾
  • ChoiceType 可選擇的
    • 這邊使用兩個參數(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

流程:




實作:
  1. Form Type 中,add FileType 類型 的欄位 ex.photo
  2. 到 ODM 實踐欄位
  3. 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']; //方法二
    }
    
  4. 回傳
注意:

  • 欄位設定為 FileType
    • getter/setter IO 分別為 UploadedFile / File 物件
    • 檔案存在遠端,無法產生 File 物件,需另外寫一隻 getPhotoPath() 之類的 function

Command

注意!console 指令預設都用 dev 在跑、吃 dev config。
正式環境務必使用 --env=prod 執行
使用 service
  1. extends ContainerAwareCommand
  2. 使用 $this->getContainer()->get('service-name')
參數 Argument (v1 v2)
//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 入門 Symfony 3 入門 Reviewed by Wild on 4/25/2017 03:16:00 下午 Rating: 5

沒有留言:

沒有Google帳號也可發表意見唷!

技術提供:Blogger.