2019年3月28日 星期四

[GCP] 讓Google Vision API幫你做ORC文字辨識(Python實例)


一、GCP設定
啟用方式
打開連結,點選『啟用』。

每個月有提供免費額度(詳細請看google的說明),每月使用的前 1,000 個單位免費,如果怕被收費的話記得關閉這個API。

關閉的方法
(1) 點選『管理』

(2) 點選『停用API』


二、安裝套件
(1) 安裝vision
pip3 install google-cloud-vision

(2) 安裝sdk
方法一、
pip3 install google-cloud-sdk
方法二、
sudo apt install snapd
sudo snap install google-cloud-sdk --classic


三、使用方式
(1) 輸入指令,獲得授權。
gcloud auth application-default login
點選出現的連結(用瀏覽器打開),選擇你的google帳戶登入。
登入google後,把出現的驗證碼回填。
記得!伺服器重開之後都要再重新登入。

(2) 新建檔案,執行程式。
# detect.py
import sys
import io
from google.cloud import vision

def detect_text_uri(uri):
    client = vision.ImageAnnotatorClient()
    image = vision.types.Image()
    image.source.image_uri = uri

    response = client.text_detection(image=image)
    texts = response.text_annotations
    print('Texts:')
    print(texts[0].description)

if __name__ == '__main__':
    detect_text_uri(sys.argv[1])
執行程式碼
python3 detect.py IMAGE_URL

[實測結果]
python3 detect.py https://www.eastcoast-nsa.gov.tw/image/6921/1024x768
圖片來源:https://www.eastcoast-nsa.gov.tw/image/6921/1024x768

輸出文字:


Google Vision Api Example
Vision API支援很多國的語言,辨識度也相當不錯!Google有針對Vision API提供很多範例的程式碼,你可以[下載]程式碼來玩玩看。
document_text 標示圖片中的文字 [連結]
python3 doctext.py resources/text_menu.jpg  -out_file result.png
將圖片中的文字標示出來。

解析圖片的內容、計算評分 web [連結]
python3 web_detect.py https://picsum.photos/400?image=111
除了解析圖片的內容,還會列出使用這個圖片的網站、與此圖片相同的圖片(不同路徑)、部分相同的圖片等等。


參考資料:
https://cloud.google.com/vision/overview/docs/
https://cloud.google.com/vision/docs/quickstart-client-libraries#client-libraries-install-python

2019年3月25日 星期一

Symfony4.2入門教學【第六篇】設定(Configuration)


Symfony可以安裝第三方套件(程序包、libraries等等),為專案帶來新的功能(service),透過預設放在目錄config/的設定檔案可以客製每一個套件。

Configuration: config/packages/

所有套件的設定都可以在config/packages/找到。舉例來說,framework程序包的設定檔案在config/packages/framework.yaml
// 省略程式碼
最上層的鍵(key)(這邊指的是framework)會是針對特定程序包的設定(這個例子裡指的是FrameworkBundle)。

設定檔格式
這份文章裡所有的設定檔例子都會有三種格式(YAML, XML and PHP),預設會使用YAML,但你可以選擇你最喜歡的來用,它們的效能表現沒有差別:
  • YAML格式: 簡單、整潔且可讀性高。
  • XML: 相較於YAML,在times和IDE autocompletion的支援度較高。
  • PHP: 非常強大,但相較於標準的設定格式,可讀性較低。

配置參考(Configuration Reference)和Dumping

有兩個方法可以知道有哪些鍵(key)可以設置:
  1. 查看『參考』(原文)這個章節
  2. 使用指令config:dump-reference
舉例來說,如何想要設定關於framework程序包,透過下列指令,你可以看到所有可用的選項羅列出來:
php bin/console config:dump-reference framework


The parameters Key: Parameters (Variables)

配置文件中最特別的最上層鍵,一種被稱為parameters,用來定義可以被其他設定檔引用的變數(variables),例如,當你安裝一個translation套件,一個locale的parameter就會被加在檔案config/services.yaml內。
// 省略程式碼
這個parameter在framework的設定檔(config/packages/translation.yaml)裡可以參考(reference):
// 省略程式碼
在設定檔內,你可以在parameters鍵底下定義任何想要的參數名稱(parameter name),若要參考parameter,在名稱的兩個加上百分比的符號 - 如:%locale%

你可以動態的設定參數,像是環境變數,請看『How to Set external Parameters in the Service Container

想要知道更多關於參數的資訊 - 包含如何在控制器裡參考這些參數 - 請看服務參數(原文)。

檔案.env和環境變數
載入檔案.env的內容成為環境變數(environment variables),在開發階段的時候,或設定環境變數對你來說很困難,都很方便。

當你安裝套件時,更多的環境變數會加到這個檔案,當然你還是可以新增自己所需要的。

透過特定的語句,在其他設定檔都可以引用環境變數。舉例來說,如果你安裝doctrine套件,檔案.env裡會多一個環境變數叫DATABASE_URL,在檔案config/packages/doctrine.yaml裡可以這樣引用參數:
# config/packages/doctrine.yaml
doctrine:
    dbal:
        url: '%env(DATABASE_URL)%'

        # The `resolve:` prefix replaces container params by their values inside the env variable:
        # url: '%env(resolve:DATABASE_URL)%'
若要取得更多關於環境變數的訊息,請看『環境變數』(原文)。

在2018年11月以前建立的專案,系統作了些微的調整,涉及檔案.env.dist,若要了解關於升級的資訊,請看『Nov 2018 Changes to .env & How to Update

檔案.env很特殊,因為它通常定義了在每個伺服器上需要更改的值,例如你本地端的開發環境,資料庫設定肯定和你的同事不同,檔案.env內包含所有環境變數之敏感、無法隱藏的默認值,且應該提交到你的儲存庫(Repository)。

如果要複寫這些根據伺服器或屬於敏感資料的環境變數,可以新建一個檔案.env.local,這將不會上傳到共享的儲存庫。事實上,Symfony自動產生的檔案.gitignore,避免將這些檔案上傳。

你也可以新增一些其他.env,一樣會被載入使用:
  • .env.{environment}: 例如.env.test將會在環境test載入使用,並上傳。
  • .env.{environment}.local: 例如.env.prod.local會在prod環境內載入,但不會上傳到儲存庫。
如果你打算在正式環境中設定正確的環境變數,.env仍然會載入,但你本地端的環境變數會覆蓋掉這些值。


環境&其他設定檔

你有一個應用程式,但不管你有沒有發覺,你需要讓他在不同的時候有不同的表現:
  • 開發(developing)時,你會希望應用程式會紀錄所有內容並顯示在漂亮的除錯工具上。
  • 在部屬到正式(production)環境後,你會希望應用程式在速度方便達到最佳化,且只需要顯示錯誤訊息。
如何讓一個應用程式有兩種不同的表現呢?正確答案是環境(environment)

你可能根本不知道dev環境是什麼就已經在使用了,在你部屬之後,你將會使用prod這個環境。

若要了解更多關於執行和控制每一個環境,請看『如何掌握和建立新環境』(原文)。


繼續前進!

恭喜!你已經了解基礎的Symfony,接著,按照指南了解Symfony的每個部分。查看:

還有更多主題

閱讀更多



文章原文:
https://symfony.com/doc/4.2/configuration.html

系列文章:

2019年3月24日 星期日

Symfony4.2入門教學【第五篇】模板(Templates)

如同上一篇文章的說明,控制器負責處理對Symfony應用的每一個請求,產生回應的內容通常是呈現模板(rendering a template)。

實際上,控制器將大部分繁重的工作委託給第三方,以便測試或重複利用程式碼。當控制器需要產生HTML、CSS或其他內容,這個工作會交給模板引擎(template engine)。

在這篇文章裡,您將學習如何編寫可用於將內容回傳給用戶,填寫電子郵件正文等的強大模板。你將會學到捷徑、巧妙地延伸模板的方法和如何重複利用模板程式碼。


模板

模板是一種能生成任何基本文字格式的文字檔(HTML, CSS, CSV, LaTex...),你應該很熟悉PHP模板 - 能讓PHP解讀的一種摻雜文字和PHP程式碼的文字檔。
<!DOCTYPE html>
<html>
    <head>
        <title>Welcome to Symfony!</title>
    </head>
    <body>
        <h1><?= $page_title ?></h1>

        <ul id="navigation">
            <?php foreach ($navigation as $item): ?>
                <li>
                    <a href="<?= $item->getHref() ?>">
                        <?= $item->getCaption() ?>
                    </a>
                </li>
            <?php endforeach ?>
        </ul>
    </body>
</html>
但Symfony提供更強大的模板語言稱Twig,Twig能使你寫出更簡潔易讀的模板,對web設計人員更友善,並在某些方面,比PHP模板更加強大:
<!DOCTYPE html>
<html>
    <head>
        <title>Welcome to Symfony!</title>
    </head>
    <body>
        <h1>{{ page_title }}</h1>

        <ul id="navigation">
            {% for item in navigation %}
                <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
            {% endfor %}
        </ul>
    </body>
</html>
Twig訂了三種特殊語句:
{{ ... }}
"Say something": 在模板上印出變數或表示式的結果。

{% ... %}
"Does something": 一個能控制模板邏輯的標籤(tag),可以用來執行陳述(statement),像是迴圈(for-loop)。

{# ... #)
"Comment something": 功能等同與PHP的句法/* comment */,用來新增多行或單行的註解,這些內容不會顯示在呈現的頁面上。

Twig也包含過濾器(filters),能在呈現頁面之前改變內容,下面的例子是讓title這個變數在呈現之前全部變成大寫:
{{ title|upper }}
預設情況下,這長串的tagsfiltersfunctions都可以在Twig使用,透過Twig Extension,你也可以克制自己的過濾器(filters)、函式(functions)等等,執行下面的指令看看有哪些:
php bin/console debug:twig
Twig程式碼看起來和PHP程式很像,只有一些微妙但美好的差異,下面的例子是用標準的標籤for和函式cycle()印出奇數、偶數交替使用不同class的10個div標籤:

在本文中,Twig和PHP模板範例都會有。

選擇Twig的原因?
Twig模板很簡單,並忽略不處理PHP標記,Twig模板系統設計的目的是用於表示式,而非程序邏輯。長期接觸後會慢慢愛上它並從中受益,當然也會都到各地web設計人員的青睞。

Twig還能達成PHP不能辦到的事情,像是控制空白、沙盒(sandboxing)、HTML自動轉譯(automatic HTML escaping)、手動內容輸出轉譯(manual contextual output escaping),和僅用於模板的自定義函數和過濾器。Twig包含許多功能,使得撰寫模板更加容易、簡潔,請看下面的範例,結合了環圈和邏輯陳述句if
<ul>
    {% for user in users if user.active %}
        <li>{{ user.username }}</li>
    {% else %}
        <li>No users found</li>
    {% endfor %}
</ul>

Twig模板快取
由於每個模板編譯成一個本地的PHP類(class)並進行緩存,使得Twig運行速度很快,而且這些事情都會自動執行,你什麼都不用做也不用你操心。開發時,Twig會聰明地在你做任何改變之後重新編譯,這代表Twig在正式環境中很快,但開發同時也很方便使用。


模板繼承與佈局

專案的模板通盛會共享一些共通元素,像是header、footer、sidebar等等,在Symfony裡比虛幻一個方式思考:模板可以讓另一個模板裝飾(decorate),這個概念相當於PHP類中的繼承,模板繼承允許你建立一個基本的佈局模板,其中包含定義為區塊(block)的常見元素(可以當作是有一些基本方法的PHP類),子模板能夠延伸基礎佈局,並覆寫父模板的任何區塊(可以想成PHP的子類也會覆蓋父類的某些方法)。

首先,建立一個基處佈局(base layout)的檔案:
{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Test Application{% endblock %}</title>
    </head>
    <body>
        <div id="sidebar">
            {% block sidebar %}
                <ul>
                    <li><a href="/">Home</a></li>
                    <li><a href="/blog">Blog</a></li>
                </ul>
            {% endblock %}
        </div>

        <div id="content">
            {% block body %}{% endblock %}
        </div>
    </body>
</html>

雖然關於模板繼承的討論將以Twig的形式進行,但Twig和PHP模板之間的理念是相同的。

這個模板定義基礎的HTML架構(skeleton)是一個兩大欄的頁面,在這個例子裡,定義了三個{% block %}區塊(titlesidebarbody),這個模板也可以直接被渲染,這麼做的話,titlesidebarbody三個區塊會在模板裡保持預設值。

子模板(child template)看起來可能會像是:
{# templates/blog/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}My cool blog posts{% endblock %}

{% block body %}
    {% for entry in blog_entries %}
        <h2>{{ entry.title }}</h2>
        <p>{{ entry.body }}</p>
    {% endfor %}
{% endblock %}

父模板(parent template)會存在目錄template/裡,所以路徑為base.thml.twig,模板命名慣例在『模板命名與路徑』(原文)有更完整的說明。

模板繼承的關鍵字為這個{% extends %}標籤,這會告訴模板引擎先解析這個設定好佈局和定義一些區塊的基礎模板,子模板會根據指向titlebody的區塊取代原本父模板的內容進行呈現,根據blog_entries的值,輸出結果如下所示:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>My cool blog posts</title>
    </head>
    <body>
        <div id="sidebar">
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog">Blog</a></li>
            </ul>
        </div>

        <div id="content">
            <h2>My first post</h2>
            <p>The body of the first post.</p>

            <h2>Another post</h2>
            <p>The body of the second post.</p>
        </div>
    </body>
</html>
注意一下,既然子模板沒有定義sidebar這個區塊,內容就會沿用父模板的設定,在{% block %}裡的內容總是當作預設值來使用。

想要的話,你可以使用多層的模板繼承,請看『如何用繼承模板管理Twig模板』(原文)了解更多資訊。

使用模板繼承時,有些小技巧請銘記在心:
  • 如果在模板中使用{% extends %},這個標籤必須放在模板的最前面。
  • 在基礎模板有越多{% block %}的標籤越好,記住!子模板不需要定義父模板內所有的區塊,所以你可以根據需求盡可能地在基礎模板中建立越多的區塊,並幫每個區塊定義合理的預設值,基礎模板有越多區塊,能使你的佈局更加彈性。
  • 假設你發現在多個模板內有重複的內容,這可能代表你需要把這部分的內容移到父模板的{% block %}內當作一個區塊。在某些情況下,有一個更好的解法是把這些內容放到新的模板,並引用(include)(請看Including other Templates)。
  • 如果你需要取得父模板區塊的內容,你可以用函式{{ parent() }},你如果想要增加父模板區塊的內容而不是整個覆寫它的話,這會是一個很好用的方法。
{% block sidebar %}
    <h3>Table of Contents</h3>

    {# ... #}

    {{ parent() }}
{% endblock %}



模板命名與路徑

預設情況下,模板會放在兩個不同的路徑:
templates/
應用專案中的目錄views有應用程序範圍的基本模板(例如你應用中的布局、應用程序包的模板)。

vendor/path/to/CoolBundle/Resources/views/
每一個第三方程序在目錄Resources/views/(和子目錄)有它的模板,當你決定分享你的程序包時,你應該將模板放到程序包而非templates/這個目錄。

大部分你使用的模板會放在目錄templates/裡,你的使用路徑是依據這個目錄的相對路徑,舉例來說,如果要render/extendtemplates/base.html.twig,你會用路徑base.html.twig;如果要render/extend檔案templates/blog/index.html.twig,你會用路徑blog/index.html.twig

引用程序包裡的模板
假設你需要引用程序包裡的模板,Symfony使用Twig命名空間(namespace)的語句(@BundleName/directory/filename.html.twig),這能允許使用多種模板,而每一個都在它特定的路徑:
  • @AcmeBlog/Blog/index.html.twig: 這個語句用來指定指定特定頁面的模板,這三部分的字串分別用斜線(/)分隔開來,代表:
    • Blog: 這是程序包的名稱去掉後綴的Bundle,這個模板在AcmdBlogBundle(例如:src/Acme/BlogBundle)
    • index.html.twig: 目錄,表示模板位於Resources/views/的子目錄中。
    • : 檔案名稱,檔案的名稱為index.html.twig
  • @AcmeBlog/layout.html.twig: 這個句法引用屬於AcmeBlogBundle的基本模板,因為少了中間"目錄"的部分(如Blog),這個模板位於AcmeBlogBundle裡的Resources/views/layout.html.twig

使用這個命名空間的語句,而非真正的檔案路徑,來覆寫任何程序包內的模板。

模板後綴(Template Suffix)
每一個模板名稱都有兩個擴展(extension),指定該模板的格式(format)引擎(engine)

檔案名稱格式引擎
blog/index.html.twigHTMLTwig
blog/index.html.phpHTMLPHP
blog/index.css.twigCSSTwig

預設情況下,任何Symfony模板可以用Twig或PHP寫,最後一部分的擴展(e.g. .twig.php)指定這兩種引擎該用哪一個,第一部分的擴展(e.g. ..html.css等等)是模板最終產生的格式,這跟引擎不一樣,它決定了Symfony要如何解讀這個模板,這是有一種策略,用來處理將相同資源呈現成HTML()、XML或任何其他格式,更多的資訊可以讀『如何運用不同輸出格式的模板』(原文)這個章節。


標籤和小幫手

你已經了解基本的模板,如何命名、如何繼承模板,你已經度過最困難的部分,在這個章節,你將會學習很多能執行最常見的模板任務的工具,像是引用其他模板、連結到其他頁面或引用影像。

Symfony帶有幾個專門的Twig標籤和函式,可以簡化模板設計師的工作。在PHP中,模板系統提供一個可擴展的輔助系統,可以在模板的內容(context)中提供有用的功能。

你已經看過幾個內建的Twig標籤,像是{% block %}{% extends %},接著你會學到更多。

引用其他模板
你將會常常在一些頁面上想要引用相同的模板或部分的程式,例如,一個應用中的"最新文章",這個顯示文章用的模板程式,可能可以用於顯示文章詳細訊息的頁面上、顯示最受歡迎文章的頁面上,或在最新文章列表中。

當你需要重複使用一大堆PHP程式,一般來說,你會將這些程式碼搬到一個新的PHP類(class)或函式,這個同樣適用於模板,將重複使用的模板程式碼建立成自己獨立的模板,可以讓任何其他模板引用。首先,建立一個要重複使用的模板:
{# templates/article/article_details.html.twig #}
<h2>{{ article.title }}</h2>
<h3 class="byline">by {{ article.authorName }}</h3>

<p>
    {{ article.body }}
</p>
請用函式{{ include() }}來達成讓其他模板引用這個模板。
{# templates/article/list.html.twig #}
{% extends 'layout.html.twig' %}

{% block body %}
    <h1>Recent Articles<h1>

    {% for article in articles %}
        {{ include('article/article_details.html.twig', { 'article': article }) }}
    {% endfor %}
{% endblock %}
請注意,模板名稱遵循相同的典型約定,article_details.html.twig這個模板使用我們傳過去的變數article,在這個情況下,你可以不需要這樣做,因為所有list.html.twig中可用的變數,一樣可以在article_details.html.twig中使用(除非你將with_context設定false)。

句法{'article': article}是一個標準的Twig語句(i.e. 一個帶有命名鍵的數組),如果你需要傳多個元數,會像是{'foo': foo, 'bar': bar}

連結其他頁面
在應用哩,建立一個連結到其他頁面對模板來說,是一個相當常見的功能,可以利用Twig函式patyh(或PHP裡的小工具router)來參照路由設定產生URL,而不是直接將URL寫死在模板裡。之後假設你需要修改特定頁面的URL,你只要更改路由的配置,模板就會自動生成新的URL。

首先,透過以下的路由設定要連結的"welcome"頁面:
// 省略程式碼
要連接到這個頁面,請使用Twig函式path()來使用這個路由:
<a href="{{ path('welcome') }}">Home</a>
如同預期,這會產生URL/。現在,來看看這個更複雜的路由:
// 省略程式碼
在這個例子裡,你需要指定路由名稱(article_show)和參數{slug}的值。使用這個路由,再次訪問前一個章節用到的recent_list.html.twig這個模板,就可以正確地連結到文章:
{# templates/article/recent_list.html.twig #}
{% for article in articles %}
    <a href="{{ path('article_show', {'slug': article.slug}) }}">
        {{ article.title }}
    </a>
{% endfor %}
你也可以用Twig函式url()來產生絕對路徑。
<a href="{{ url('welcome') }}">Home</a>

連結到Assets
模板也經常需要影像、JavaScript、樣式表(stylesheets)和其他assets(e.g. /images/logo.png),但Symfony提供一個更動態的選項:Twig函式asset()

請安裝套件asset來使用這個函式:
composer require symfony/asset
現在可以使用函式asset()
<img src="{{ asset('images/logo.png') }}" alt="Symfony!" />

<link href="{{ asset('css/blog.css') }}" rel="stylesheet" />
函式asset()的主要功能是讓你的應用更方便移植,如果你的專案在網頁伺服器的跟目錄(e.g. http://example.com),所以給予的路徑會像是/images/logo.png。但如果你的專案放在子目錄內(e.g. http://example.com/my_app),所有的assets路徑就會多了一個子目錄(e.g. /my_app/images/logo.png),函式asset()能解決這個問題,它可以藉由確認你的應用路徑,並相應的產生正確的路徑。

透過versionversion_formatjson_manifest_path的設定,函式assets提供多元的緩存破壞(cache busting)技術

如果需要產生assets的絕對路徑,可以使用Twig函式absolute_url(),像是:
<img src="{{ absolute_url(asset('images/logo.png')) }}" alt="Symfony!" />


在Twig引用Stylesheets和JavaScript

沒有任何網站是完全不需要用到Javscript或stylesheets(樣式表),藉由Symfony模板繼承的特點,這些assets可以很優雅地進行管理,

這個章節將教你如何在Symfony中引用樣式表和Javascript資源,如果你對編譯或創建這個assets有興趣的話,請查看『Webpack Encore documentation』,這個工具可以將webpack和其他現代的Javascript工具無縫集成到Symfony應用程序中。

我們新增兩個區塊來管理assets,一個叫做stylesheets,放在標籤head裡;另一個叫javascripts放在鄰近的標籤body裡,這些區塊將包含專案內所有樣式表和JavaScript。
{# templates/base.html.twig #}
<html>
    <head>
        {# ... #}

        {% block stylesheets %}
            <link href="{{ asset('css/main.css') }}" rel="stylesheet" />
        {% endblock %}
    </head>
    <body>
        {# ... #}

        {% block javascripts %}
            <script src="{{ asset('js/main.js') }}"></script>
        {% endblock %}
    </body>
</html>
這看起來跟一般的HTML很像,只是多了{% block %},你需要在子模板內引用其他的樣式表或javascript時是很方便。例如,假設有個聯絡頁面,且只有這個頁面需要引用一個樣式表contact.css,因此在連絡頁面的模板內,會像是:
{# templates/contact/contact.html.twig #}
{% extends 'base.html.twig' %}

{% block stylesheets %}
    {{ parent() }}

    <link href="{{ asset('css/contact.css') }}" rel="stylesheet" />
{% endblock %}

{# ... #}
在子模板哩,你可以覆寫stylesheets區塊,並放了你新的樣式表標籤到區塊內,如果你想要新增父模板區塊的內容(而不是真的取代掉它),你可以用Twig函式parent()來引用基礎模板的stylesheets區塊內所有內容。

你也可以引用在你的程序包Resources/public/內的assets,你需要執行指令php bin/console assets:install target [--symlink],作用是複製(符號鏈接 symlink)檔案到正確的路徑(目標是應用中預設的目錄"public/")。
<link href="{{ asset('bundles/acmedemo/css/contact.css') }}" rel="stylesheet" />
最後結果會是一個包含main.jsmain.csscontact.css兩個stylesheets的頁面。


引用請求、用戶或Session

Symfony在Twig提供一個全域變數app,可以用來取得現在的使用者、請求等等資訊。

請看『How to Access the User, Request, Session & more in Twig via the app Variable』了解更多。


輸出轉義(Output Escaping)

在呈現任何內容時,Twig會自動轉譯輸出,以防你受跨站腳本(Cross Site Scription, XSS)攻擊。

假設description等於I <3 product="" span="" this="">:

<!-- output escaping is on automatically -->
{{ description }} <!-- I &lt;3 this product -->

<!-- disable output escaping with the raw filter -->
{{ description|raw }} <!-- I <3 this product -->
PHP模板不會自動轉義內容。

更多細節請看『How to Escape Output in Templates』。


結語

模板系統只是Symfony眾多工具之一,且它的功能很簡單:讓我們可以給予動態和複雜的HTML輸出,可以讓我們在最後回傳給使用者、寄出email等等。


繼續前進!

沉浸於Symfony其他部分之前,先看看『Symfony4.2入門教學【第六篇】設定(Configuration)』(原文)。


文章原文:
https://symfony.com/doc/4.2/configuration.html

系列文章:

2019年3月23日 星期六

[Vue.js] 開發用和正式環境的參數設定方式(.env)


在開發階段時,通常會用指令 npm run dev 讓專案運作,當內容有任何修正時,就能快速的生效。完成開發後要放到正式環境時,再執行 npm run build 來產生最終的檔案,放到網頁伺服器上使用。

一般在開發階段時的後端API和正式環境用的API可能是不同的,因此可以利用.env來進行設定。

安裝vue-cli可以快速建置專案(安裝方式),在目錄config/裡有兩個檔案 dev.env.jsprod.env.js,顧名思義一個是在dev用的、一個是在production用的。

1. dev.env.js
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  ROOT_API: '"http://localhost/api"'
})

2. prod.env.js
'use strict'
module.exports = {
  NODE_ENV: '"production"',
  ROOT_API: '"https://example.com/api"'
}
在dev時會用 http://localhost/api,在production時會使用https://example.com/api


在VUE專案裡要使用這個參數:
process.env.API_LOCATION
不需要任何import就可以使用,例如:
mounted() {
  console.log(process.env.ROOT_API)
}
最後需要注意,如果已經在執行npm run dev,在dev.env.js修改可能不會被偵測到,請按ctrl+c離開這個指令再執行一次。


資料來源:
https://alligator.io/vuejs/working-with-environment-variables/

[Vue.js] 安裝axios對後端傳送Request (GET/POST/PUT/DELETE)


安裝指令
npm install axios

使用方式
import axios from 'axios'
// use axios in your project
// ...
1. GET
[URL] 字串、API位址
[HEADER] dict,例如:{'Content-Type': 'multipart/form-data'}
[PARAM] dict,例如:{'id': '1'},等同於?id=1
axios.post('[URL]', {headers:[HEADER], params: [PARAM]})
    .then(response => {
        console.log(response);
    })
    .catch(err => {
        console.log(err.response);
    });
2. POST
[DATA] 資料
axios.get('[URL]', [DATA], {headers:[HEADER]})
    .then(response => {
        console.log(response);
    })
    .catch(err => {
        console.log(err.response);
    });
3. PUT
格式與post相同
axios.put('[URL]', [DATA], {headers:[HEADER]})
    .then(response => {
        console.log(response);
    })
    .catch(err => {
        console.log(err.response);
    });
4. DELETE
axios.delete('[URL]', {headers:[HEADER], data:[DATA]})
    .then(response => {
        console.log(response);
    })
    .catch(err => {
        console.log(err.response);
    });

vue-resource 比較不同的是error handling,API回應中通常包含http_statsu、body、data等,success handling回傳的就是標準的回應,但error handling放在response這個key裡面。舉例要取得http_status,在success handling裡是response[http_status],但error handling是err.response[http_status]


[Sublime] SFTP參數說明


sublime的SFTP套件,可以透過sftp等方式與遠端的伺服器連線、檔案同步,如果不習慣用vim的人真的很方便,詳細的步驟請參考『[Sublime] SFTP同步遠端資料夾』,以下說明 sftp-config.json 的常用參數:
type 連線類型
預設為sftp,其他還有ftpftps

host 主機位址
填寫網域名(domain name)或IP。

user 使用者
使用者名稱

password 密碼
非必填欄位,沒填的話也會在連線同步的時候詢問密碼。

port 埠號
非必填欄位,可以指定埠號,未填寫的話會依照連線類型(type)走預設的埠號。

remote_path 遠端伺服器路徑
填寫遠端伺服器中目錄的絕對路徑。

ssh_key_file 金鑰檔案
如果使用ssh key來連線遠端伺服器時,請填寫使用金鑰的檔案位置。
p.s. 路徑中的目錄如果有中文的話,目前嘗試連線會失敗,不確定原因。

save_before_upload 同步前儲存
假設將本地端檔案同步到遠端時,若未儲存的資料,是否直接儲存並上傳,預設值為true

upload_on_save 儲存並同步
預設值為false,決定當本地端的檔案儲存時,是否同時同步上傳遠端伺服器。也就是預設的情況下,本地端的修改不會影響遠端伺服器;反之,當設為true時,本地端的修改會同步上傳遠端的伺服器。

sync_down_on_open 開啟專案時自動下載
當本地端的目錄被開啟時,自動檢查遠端伺服器是否有更新的資料,預設為false,並不會有任何動作;反之,則將更新的檔案下載到本地端。

confirm_downloads 確認下載
下載前詢問使用者是否執行,預設false

confirm_sync 確認同步
同步前詢問使用者是否執行,預設true

ignore_regexes 忽略的目錄或檔案
.gitignore的用途相同,可以用正規表示式(regular expression)來列出規則,決定哪些資料不要進行同步。


資料來源:
https://wbond.net/sublime_packages/sftp/settings



2019年3月1日 星期五

Symfony4.2入門教學【第四篇】控制器(Controllers)


所謂控制器是一個PHP函式,用來讀取Request物件中的資訊,並建立和回傳一個Response物件,這個回應(response)可以是HTML頁面、JSON、XML、供下載的檔案、重新定向(redirect)、404錯誤等等,控制器根據應用的需求,執行任何可能的邏輯去呈現畫面。

如果你還沒建立第一個頁面,請查看文章『建立第一個頁面』之後再回來吧!


簡易的控制器

雖然控制器可以是任何可呼叫的(函式、物件中的方法或Closure),但大多會是一個控制器類內的方法(a method inside a controller class):
// src/Controller/LuckyController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class LuckyController
{
    /**
     * @Route("/lucky/number/{max}", name="app_lucky_number")
     */
    public function number($max)
    {
        $number = random_int(0, $max);

        return new Response(
            'Lucky number: '.$number.''
        );
    }
}
控制器就是方法number(),被放在名為LuckyController的控制器類。

這個控制器非常容易理解:
  • line 2: Symfony利用PHP的命名空間(namespace)功能來命名整個控制器類。
  • line 4: Symfony也是利用PHP的命名空間功能來命名整個控制器類:關鍵字use輸入(import)一個控制器必要回傳的類(class)Response
  • line 7: 實作上,類名可隨便取,但按慣例會用Controller當作後綴字。
  • line 12: 由於有個{max}通配符在路由,操作方法(action method)會有一個$max的參數。
  • line 16: 控制器建立和回傳一個Response物件

對應URL到控制器
想要看到控制器呈現的結果,你必須透過路由將URL與其對應,這邊的設定是透過註解式路由,寫成@Route("/lucky/number/{max}")來達成對應。

想看頁面,請用瀏覽器到這個URL:
欲見更多關於路由資訊,請看路由



基礎的控制器和服務

Symfony為了讓大家輕鬆點,有一個基礎控制器AbstractController可以選擇使用,你可以延伸了解一些輔助方式(helper methods)。

在控制器類的上方加上use的語句,並修改LuckyController以擴展(extend)它:
// src/Controller/LuckyController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class LuckyController
class LuckyController extends AbstractController
{
    // ...
}
就是這樣!你現在已經接觸了像$this->render()這樣的方法,接下來還有很多其他的方法你會慢慢學到。

生成URL
generateUrl()方法是一個輔助方法,可以針對給定的路由產生URL:
$url = $this->generateUrl('app_lucky_number', ['max' => 10]);

重新導向(Redirecting)
如果你像要將使用者重新導向其他頁面,可以使用redirectToRoute()redirect()兩個方法:
use Symfony\Component\HttpFoundation\RedirectResponse;

// ...
public function index()
{
    // redirects to the "homepage" route
    return $this->redirectToRoute('homepage');

    // redirectToRoute is a shortcut for:
    // return new RedirectResponse($this->generateUrl('homepage'));

    // does a permanent - 301 redirect
    return $this->redirectToRoute('homepage', [], 301);

    // redirect to a route with parameters
    return $this->redirectToRoute('app_lucky_number', ['max' => 10]);

    // redirects to a route and maintains the original query string parameters
    return $this->redirectToRoute('blog_show', $request->query->all());

    // redirects externally
    return $this->redirect('http://symfony.com/doc');
}

redirect()方法並完全不會檢查重新導向的目標,如果你是導向由用戶提供的URL,可能會使得你的應用程式產生未經驗證重新定向的安全漏洞(unvalidated redirects security vulnerability)。

渲染模板(rendering template)
如果你要回傳HTML,你會想要用渲染模板,方法render()會渲染模板,並封裝成Response物件給你:
// renders templates/lucky/number.html.twig
return $this->render('lucky/number.html.twig', ['number' => $number]);
模板和Twig相關的說明,在文章『建立和使用模板』(原文)有更多的說明。

獲取服務(fetching services)
Symfony包含相當多有用的物件,稱為服務(service)。可以用來渲染模板、發送信件、查詢資料庫和任何你想得到的工作。

如果你要在控制器裡使用服務,請用它的類名(或介面(interface))宣告參數,Symfony會自動提供你所需的服務:
use Psr\Log\LoggerInterface;
// ...

/**
 * @Route("/lucky/number/{max}")
 */
public function number($max, LoggerInterface $logger)
{
    $logger->info('We are logging!');
    // ...
}
太棒了!

其他還有哪些服務類型可以使用?如果想知道,請在控制台執行指令debug:autowiring
php bin/console debug:autowiring

如果需要控制參數的確切值,可以使用其名稱綁定(bind)參數:
// 省略程式碼
和其他服務一樣,你可以在控制器中用常規構造函數注入(regular constructor injection)。

想要知道更多關於服務的資訊,請看服務容器(Service Container)這篇文章。


產生控制器

為了節省時間,你可以安裝Symfony Maker,並藉由它產生一個新的控制器類:
php bin/console make:controller BrandNewController

created: src/Controller/BrandNewController.php
如果想要產生針對Doctrine實體(entity)完整的CRUD,執行:
php bin/console make:crud Product

New in version 1.2: MakeBundle 1.2內介紹了指令make:crud


錯誤處理及404頁面

如果找不到東西,應該回傳一個404的回應,請拋(throw)一個特殊形態的例外(exception)來達成:
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

// ...
public function index()
{
    // retrieve the object from database
    $product = ...;
    if (!$product) {
        throw $this->createNotFoundException('The product does not exist');

        // the above is just a shortcut for:
        // throw new NotFoundHttpException('The product does not exist');
    }

    return $this->render(...);
}
方法createNotFoundException()只是一個NotFoundHttpException特殊物件的捷徑,最終會觸發Symfony內的一個404 HTTP回應。

如果你拋一個延伸的例外或HttpException的實例(instance),Symfony會自帶適當的HTTP狀態碼(status code),否則回應就會是HTTP status code 500。
// this exception ultimately generates a 500 status error
throw new \Exception('Something went wrong!');
每個情況下,錯誤頁面(error page)是顯示給一般使用者看,完整的除錯頁面(full debug error page)則是供開發者(i.e. 當你在除錯模式(Debug mode) - The parameters Key: Parameters (Variables))。

若想要客製化顯示給使用者看的錯誤頁面,請參考如何將錯誤頁面客製化的文章(How to Customize Error Pages article)。


回應物件作為控制器參數

如果想要讀取查詢參數、取得請求的標頭(header)或取得上船的檔案?所有的資訊都存在Symfony的Request物件中,要在你的控制器內取得,只要用請求類名宣告成參數:
use Symfony\Component\HttpFoundation\Request;

public function index(Request $request, $firstName, $lastName)
{
    $page = $request->query->get('page', 1);

    // ...
}
更多資訊請參考Request Object(原文)


管理Session

Symfony提供session的服務,可以讓你從請求中取得更多關於使用者的資訊,預設狀態下就可以使用session,不過只有在讀取或寫入的時候才會啟動。

Session儲存區和其他設定都是由config/packages/framework.yaml檔案內的framework.session configuration管控。

要取得session,請新增一個參數並宣告型態為SessionInterface
use Symfony\Component\HttpFoundation\Session\SessionInterface;

public function index(SessionInterface $session)
{
    // stores an attribute for reuse during a later user request
    $session->set('foo', 'bar');

    // gets the attribute set by another controller in another request
    $foobar = $session->get('foobar');

    // uses a default value if the attribute doesn't exist
    $filters = $session->get('filters', []);
}
儲存的屬性會放在session哩,跟使用者其他的session一起。

想要了解更多,請看Session

Flash Messages
你可以儲存一些特別的訊息在使用者的session,稱為"Flash",設計上,flash message只會使用一次: 一旦你檢索後就會自動從session裡面移除,這個特性讓flash message特別適合存放用戶通知。

例如:想像你需要處理一個表單(form)的遞交:
use Symfony\Component\HttpFoundation\Request;

public function update(Request $request)
{
    // ...

    if ($form->isSubmitted() && $form->isValid()) {
        // do some sort of processing

        $this->addFlash(
            'notice',
            'Your changes were saved!'
        );
        // $this->addFlash() is equivalent to $request->getSession()->getFlashBag()->add()

        return $this->redirectToRoute(...);
    }

    return $this->render(...);
}
在處理完這個請求後,控制器會設定flash message到session並重新導向,訊息的鍵(key)(這個例子裡,鍵就是notice)可以任意指定:你可以用這個key來檢索訊息。

在下一頁的模板中(如果放在你的基礎模板中會更好),用app.flashes()來讀取session內任何flash message:
{# templates/base.html.twig #}

{# read and display just one flash message type #}
{% for message in app.flashes('notice') %}
    
{{ message }}
{% endfor %} {# read and display several types of flash messages #} {% for label, messages in app.flashes(['success', 'warning']) %} {% for message in messages %}
{{ message }}
{% endfor %} {% endfor %} {# read and display all flash messages #} {% for label, messages in app.flashes %} {% for message in messages %}
{{ message }}
{% endfor %} {% endfor %}
常見使用noticewarningerror作為不同類型flash message的鍵(key),當然你也可以使用任何符合你需求的鍵。

你可以用方法peek(),替代存起來再檢索訊息。


請求和回應

如同上述,Symfony會傳一個Request物件給任何有宣告參數Request類的控制器:
use Symfony\Component\HttpFoundation\Request;

public function index(Request $request)
{
    $request->isXmlHttpRequest(); // is it an Ajax request?

    $request->getPreferredLanguage(['en', 'fr']);

    // retrieves GET and POST variables respectively
    $request->query->get('page');
    $request->request->get('page');

    // retrieves SERVER variables
    $request->server->get('HTTP_HOST');

    // retrieves an instance of UploadedFile identified by foo
    $request->files->get('foo');

    // retrieves a COOKIE value
    $request->cookies->get('PHPSESSID');

    // retrieves an HTTP request header, with normalized, lowercase keys
    $request->headers->get('host');
    $request->headers->get('content-type');
}
Request類有幾個公開屬性(public properties)和方法,可返回有關請求的所有資訊。

Request一樣,Response物件也有公開屬性headersResponseHeaderBag提供一些好用的方法來設定回應的標頭檔(header),標頭名稱(header names)一般標準使用Content-Type,等同於content-typecontent_type

控制器唯一的條件就是要回傳Response物件:
use Symfony\Component\HttpFoundation\Response;

// creates a simple Response with a 200 status code (the default)
$response = new Response('Hello '.$name, Response::HTTP_OK);

// creates a CSS-response with a 200 status code
$response = new Response('');
$response->headers->set('Content-Type', 'text/css');
有個特殊的類可以讓部分的回應簡單一點,下面會列舉一部份。想要了解更多關於RequestResponse(還有特殊類的Response)的資訊,請查看HttpFoundation component documentation

回傳JSON回應
如果要在控制器內回傳JSON,可以用輔助方法json(),這會回傳一個已經自動編碼(encode)的JsonResponse特殊物件。
// ...
public function index()
{
    // returns '{"username":"jane.doe"}' and sets the proper Content-Type header
    return $this->json(['username' => 'jane.doe']);

    // the shortcut defines three optional arguments
    // return $this->json($data, $status = 200, $headers = [], $context = []);
}
如果能在你的應用裡使用serializer service,可以幫你把資料連載(serialize)成JSON格式,不然也可以用函式json_encode

媒體文件回應(Streaming File Responses)
你可以在控制器內用輔助方式file()操作檔案:
public function download()
{
    // send the file contents and force the browser to download it
    return $this->file('/path/to/some_file.pdf');
}
輔助方式file()提供一些參數設定來決定表現行為:
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;

public function download()
{
    // load the file from the filesystem
    $file = new File('/path/to/some_file.pdf');

    return $this->file($file);

    // rename the downloaded file
    return $this->file($file, 'custom_name.pdf');

    // display the file contents in the browser instead of downloading it
    return $this->file('invoice_3241.pdf', 'my_invoice.pdf', ResponseHeaderBag::DISPOSITION_INLINE);
}


結語

當你建立頁面時,你最後會需要幫頁面寫一些邏輯的程式碼,在Symfony稱之為控制器,是一個PHP的函式能讓你執行任何操作,並回應一個Response物件給使用者。

為了簡化一點,你可能會延伸基礎控制器AbstractController的類,因為這樣可以讓你快速取得方法render()redirectToRoute()的捷徑。

在其他文章裡,你會學到如何在控制器裡使用特定的服務,幫助你從資料庫獲取物件、處理表單提交、處理緩存等等。


繼續前進!

接下來,來學習『Symfony4.2入門教學【第五篇】模板(Templates)』(rendering template with Twig)。


文章原文:
https://symfony.com/doc/4.2/controller.html

系列文章: